The OpenGL® Shading Language Language Version: 1.20 Document Revision: 8 07-Sept-2006 John Kessenich Version 1.1 Authors: John Kessenich, Dave Baldwin, Randi Rost Copyright © 2002-2006 3Dlabs, Inc. Ltd. This document contains unpublished information of 3Dlabs, Inc. Ltd. This document is protected by copyright, and contains information proprietary to 3Dlabs, Inc. Ltd. Any copying, adaptation, distribution, public performance, or public display of this document without the express written consent of 3Dlabs, Inc. Ltd. is strictly prohibited. The receipt or possession of this document does not convey any rights to reproduce, disclose, or distribute its contents, or to manufacture, use, or sell anything that it may describe, in whole or in part. This document contains intellectual property of 3Dlabs Inc. Ltd., but does not grant any license to any intellectual property from 3Dlabs or any third party. It is 3Dlabs' intent that should an OpenGL 2.0 API specification be ratified by the ARB incorporating all or part of the contents of this document, then 3Dlabs would grant a royalty free license to Silicon Graphics, Inc., according to the ARB bylaws, for only that 3Dlabs intellectual property as is required to produce a conformant implementation. This specification is provided "AS IS" WITH NO WARRANTIES WHATSOEVER, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, 3DLABS EXPRESSLY DISCLAIMS ANY WARRANTY OF MERCHANTABILITY, NON-INFRINGEMENT, FITNESS FOR ANY PARTICULAR PURPOSE, OR ANY WARRANTY OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION, OR SAMPLE. U.S. Government Restricted Rights Legend Use, duplication, or disclosure by the Government is subject to restrictions set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software clause at DFARS 252.227-7013 and/or in similar or successor clauses in the FAR or the DOD or NASA FAR Supplement. Unpublished rights reserved under the copyright laws of the United States. Contractor/manufacturer is 3Dlabs, Inc. Ltd., 9668 Madison Blvd., Madison, Alabama 35758. OpenGL is a registered trademark of Silicon Graphics Inc. 1 1. INTRODUCCIÓN .................................................................................................................. 4 1.1 RECONOCIMIENTOS 1.2 CAMBIOS 1.3 VISIÓN GENERAL 1.4 GESTIÓN DE ERRORES 1.5 CONVENIOS TIPOGRÁFICOS 2. DESCRIPCIÓN DE OPENGL SHADING ................................................................................. 6 2.1 VERTEX PROCESSOR 2.2 FRAGMENT PROCESSOR 3 BÁSICO ................................................................................................................................... 7 3.1 CARACTERES RESERVADOS 3.2 SOURCE STRINGS 3.3 PREPROCESSOR 3.4 COMENTARIOS 3.5 TOKENS 3.6 KEYBOARDS 3.7 IDENTIFICADORES 4. VARIABLES Y TIPOS ........................................................................................................... 13 4.1 TIPOS BASICOS 4.1.1 VOID 4.1.2 BOOLEANS 4.1.3. Integers 4.1.4 Floats 4.1.5Vectors 4.1.6Matrices 4.1.7Samplers 4.1.8 Estructuras 4.1.9 Matrices 4.1.10 Conversiones implícitas 4.2 Alcance 4.3 Cualificadores de almacenado 4.3.1Cualificador de almacenado por defecto 4.3.2 Const 4.3.3 Expresiones constantes 4.3.4 Atributo 4.3.5 Uniforme 4.3.6 Mutantes 4.4 Cualificadores de Parámetros 4.5 Precisión y Cualificadores de Precisión 4.6 Mutantes y el Cualificador de Mutantes 4.6.1 El Cualificador Invariante 4.6.2Invarianza de Expresiones Constantes 4.7 Orden de los Cualificadores 5. Operaciones y Expresiones................................................................................................... 27 5.1 Operadores 5.2 Operaciones de Matrices 5.3 Llamadas de Funciones 2 5.4 Constructores 5.4.1 Conversión y Constructores Escalares 5.4.2. Vectores y matrices de constructores. 5.4.3. Constructor de estructuras 5.4.4. Constructor de arrays 5.5. Componentes de los vectores 5.6. Elementos de matrices 5.7. Operaciones con arrays y estructuras 5.8. Asignaciones 5.9. Expresiones 5.10. Operaciones con vectores y matrices 6. Sentencias y estructuras ....................................................................................................... 36 6.1 Definición de funciones 6.1.1. Convenio de llamadas a funciones 6.2 Selección 6.3 Iteración 6.4 Saltos 7 Variables Internas .................................................................................................................. 42 7.1 Variables especiales en Vertex Shaders 7.2 Variables especiales en Fragment Shaders 7.3 Atributos especiales en Vertex Shaders 7.4 Constantes especiales 7.5 Variables Uniformes de Estado 7.6 Variables no Uniformes 8 Funciones Propias ................................................................................................................. 49 8.1 Funciones 8.2 Funciones 8.3 Funciones 8.4 Funciones 8.5 Funciones 8.6 Funciones 8.7 Funciones 8.8 Funciones 8.9 Funciones de ángulos y trigonométricas exponenciales comunes geométricas de matrices relacionales de vectores “Lookup” de texturas de Procesado Fragmentado de Ruido 9 Gramatica del lenguaje de Shading ........................................................................................ 59 10 Problemas............................................................................................................................ 67 3 1. INTRODUCCIÓN Este documento especifica la version 1.20 de OpenGL Shading Language. Requiere __VERSION__ sea 120, y #version aceptar 110 o 120. 1.1 RECONOCIMIENTOS Esta especificación esta basada en el trabajo de quienes contribuyeron a la version 1.10 del OpenGL Language Specification, el OpenGL ES 2.0 Language Specification, versión 1.10 y las siguientes contribuciones a esta versión: Nick Burns Chris Dodd Michael Gold Jeff Juliano Jon Leech Bill Licea-Kane Barthold Lichtenbelt Benjamin Lipchak Ian Romanick John Rosasco Jeremy Sandmel Robert Simpson Eskil Steenberg 1.2 CAMBIOS Cambios de la septima revisión, de la version 1.20 - Número 13 de la resolución es actualizado con el cambio acordado en la revisión 7 en cuanto al número de atributos de las franjas horarias es el número de columnas en una matriz. - Reiteró la aritmética operador comportamiento en la sección 5,9 de subcontratación, en lugar de balas demasiado largo de un párrafo. Cambios de la sexta revisión de la versión 1.20 - La gramática es renovada - método de apoyo para “.length( )” - constructores simplificados y reconocidos a través de type_specifier - el tipo de sintaxis para arrays soportado es “float [5]” y se añaden inicializadores para arrays. -Fijar la declaración sobre el número de huecos de las matrices. Se dice erroneamente que se utiliza el número máximo de filas y de columnas, pero se usa el número de columnas. - Suprimido el último párrafo de la sección 5.10 por redundante o inconscientemente repetido de la sección 5.9, y aseguramos todos los contenidos que fueron incorporados dentro del operador aritmetico-binario mantenidos en la sección 5.9. -Aclarar que, a pesar de ser invariantes los vértices de salida, la declaración de la “invariant” tienen que coincidir entre el vértice y el fragmento de las partes, para coincidir con los nombres. Cambios de la revisión 5 de la versión 1.20 - Eliminado notación mostrando adiciones y supresiones a la especificación, añadido el índice de contenidos, y limpiado algunas referencias cruzadas y alguna parte del texto que reacaía en cuestiones. Ningún cambio funcional. 4 Cambios de la revisión 4 a la versión 1.20 -Actualización de la gramática (todavía tiene que ser validado) -Eliminado las estructuras incorporadas a la pareja de ES, esperar a la primicia del tipo del operador de acceso incorporado. -Las expresiones constantes son calculadas de una forma invariante. -El uso de invariant y centroide debe coincidir entre el vértice y el fragmento de shaders. -Hecha la distinción de shaders como undidad de compilado y shader como ejecutable. -Clarificado no hay linea continua de caracterers, y que los comentarios no borren nuevas lineas. -Muchos cambios para reducir las diferncias con la especificación ES. Cambios de la revisión 3 de la versión 1.2 -Añadir la invariante de palabras clave y su soporte. -Permitir redimensionar el constructor de los arrays (Esto todavía hace explicito el tamaño de los arrays). -Exigir explicitamente el tamaño de los arrays para la asignación y la comparación. -Permitir el calibrado del tamaño del array de declaración a través del inicializador. -Diferentes unidades de compilación pueden ser de lenguajes distintos. -Añadido el estilo de C++ ocultando las reglas. -Reservado lowp, mediump, highp, y precission. Cambios de la revisión 1 en la versión 1.2 -Desabilitados otros valores de retorno del main. -Aclarar que ?: puede tener el mismo tipo el segundo y tercer operandos (por ejemplo la conversión no es requerida). -Decir que punteros sprite tienen que ser avilitados para conseguir valores de gl_PointCoord. -Separar y distinguir entre el entre el almacenamiento y el parámetro de clasificación lo que hace más fácil añadir la invariante al calificador. -#version 120 necesaria para usar la version 1.20. -mat2x3 significa dos columnas de tres filas. -matriz en construcción de matriz permitida. -añadido transpose(). -La marca de comparación tiene en cuenta el tipo de conversión, ya que la ambigüedad es un error. Cambios de la revisión 60 de la versión 1.10 -Aceptar "f", como parte de una constante de punto flotante. Ej "G de flotación = 3.5f". -Convertir directamente los tipos enteros a los tipos flotantes, en la medida de lo necesitado por el contexto. -Permitir inicializadores uniformes en las declaraciones. El valor se establece en tiempo de compilación. -Permitir construir llamadas a funciones dentro de los inicializadores constantes. -Soporte de matrices no cuadradas. -Añadir matrixProduct () [ahora outerProduct ()] para multiplicar el rendimiento de los vectores de una matriz. -Los arrays se convierten en objetos de la primera clase, con constructores, comparación, length(), etc. -Añadir gl_PointCoord para fragmentar shaders para consultar un fragmento de la posición de un punto dentro de sprites. -Soportada interpolación centroid en variaciones multi-sample. 1.3 VISIÓN GENERAL Este documento describe The OpenGL Shading Language, version 1.20. Independiente,la compilación de unidades escrito en este idioma se llaman shaders. Un programa es un conjunto completo de Shaders que son compilados y enlazados juntos. El objetivo de este documento es especificar a fondo el Lenguaje de programación. Los puntos de entrada se usan 5 para manipular comunicarse con los programas y los shaders son definidos en una especificación a parte. 1.4 GESTIÓN DE ERRORES Los compiladores, en general, aceptan programas que estan mal formados, debido a la imposibilidad de detectarlos todos. La portabilidad solo está garantizada para los programas bien formados tal y comos se describen en esta especificación. Los compiladores son alentados a detectar programas mal formados y a emitir mensajes de diagnostico, pero noe s obligatorio en todos los casos. Los compiladores tienen la obligación de devolver mensajes de error en relación con el léxico, gramática, o semántica incorrencta de los shaders. 1.5 CONVENIOS TIPOGRÁFICOS Las opciones de negrita, cursiva y fuente se han usado en esta especificación principalmente para mejorar la legibilidad. Para los fragmentos de código se utiliza una fuente de ancho fijo. Los identificadores están puestos en el texto en cursiva. Las palabras clave están introducidas en el texto en negrita. Los operadores son llamados por su nombre seguido su símbolo en negrita entre paréntesis. Para aclarar más la gramática del texto se usa negrita para literales y cursiva para nonterminals. La gramática oficial en la sección 9 “Shading Language Gramar” utiliza todos los terminales en mayuscula y no-terminales en minuscula. 2. DESCRIPCIÓN SHADING DE OPENGL El OpenGL Shading Language se trata realmente de dos lenguajes extrechamente relacionados. Estos leguajes son usados para programar el proceso contenido en el pipeline de procesamiento de OpenGL. A no ser que se indique en este documento, se hablará del lenguaje como uno solo aun a pesar de referirse a varios lenguajes. Los lenguajes especificos serán mencionados por el nombre del procesador objetivo: vertex o fragment. Cualquier OpenGL utilizado por el estado de shaders son automáticamente rastreados y puestos a disposición de shaders. Este mecanismo de seguimiento de estado automático permite a la aplicación utilizar los comandos de estado OpenGL, la gestión del Estado, y tienen los valores actuales de tal estado automáticamente disponibles para su uso en los shaders. 2.1 VERTEX PROCESSOR El procesador de vértices es una unidad programable que opera en los vértices y sus datos asociados. Las unidades escritas en el OpenGL Shading Language para funcionar en este procesador se llaman vertex shaders. Cuando un conjunto completo de vertex shaders son compilados y lincados ellos dan como resultado un vertex shader ejecutable, que se ejecuta en el vertex processor. El vertex processor funciona sobre un vértice a la vez. Además, no sustituye las operaciones gráficas que necesitan el conocimiento de varios vértices a la vez. El vértice shaders se ejecuta en el procesador de vértices que debe de calcular la posición homogénea del próximo vértice. 2.2 FRAGMENT PROCESSOR El fragment processor es una unidad programable que opera sobre el fragmento de valores y sus datos asociados. La compilación y el lincado de unidades en el fragment processor da como resultado fragment shaders que son ejecutados en el fragment processor. Un fragment shader no puede cambiar su posicion (x,y). El acceso a los fragmets vecinos no está permitido. Los valores usados para el computado de los fragment shaders son, en última instancia utilizados para 6 actualizar el frame-buffer de la memoria o el tecture-memory, en función del estado actual de OpenGL y del comando OpenGL que generó el fragment. 3 BÁSICO 3.1 CARACTERES RESERVADOS El conjunto de caracteres usados por el OpenGL Shading Language es un subconjunto del código ASCII. Incluye los siguientes caracteres: - - Las letras a-z, A-Z y el subrayado ( _ ). Los números 0-9. Los símbolos punto (.), más (+), guión (-), barra (/), asterisco (*), porcentaje (%), entre paréntesis angulares (< y >), corchetes ([y]), paréntesis ((y)), llaves ((y)), intercalación (^), barra vertical (|), comercial (&), de tildes (~), igualdad (=), signo de exclamación (!), dos puntos (:), punto y coma (;), coma (,), y signo de interrogación (?). El número signo (#) para uso del procesador. Espacio en blanco: el espacio, el tabulador horizontal, tabulador vertical, la forma de alimentación, el carro de retorno, y de línea. Las líneas son importantes para el compilador y los mensajes de diagnóstico, y para el procesador. Están terminados por el carro de retorno o de línea. Si se utilizan los dos juntos se cuentan como una única línea de terminación. Para el resto de este documento, todas las combinaciones son referenciadas como una nueva línea. No hay el carácter de línea continua. En general, el lenguaje de uso de este tipo de caracteres es senseble a mayúsculas y minúsculas. No hay cadena de caracteres o tipo string, por lo que no son citados. No existe carácter de final de archivo, sino que este es indicado por su tamaño, no por ningún carácter. 3.2 SOURCE STRINGS La fuente para un sencillo shader es un arrar de strings de caracteres del conjunto de caracteres. Un shader sencillo es hecho de la concatenación de estos strings. Cada string puede contener multimples líneas, separadas por salto de línea. No necesariamente tienen que estar presentes los saltos de línea en un string; una única línea puede ser formada por multiples strings. Los saltos de línea y otros caracteres se insertan por la aplicación cuando se concatenan los strings para dar lugar al nuevo shader. Múltiples shaders pueden ser lincados para dar lugar a un único programa. Los mensajes de diagnóstico devueltos de la compilación de un shader deben identificar tanto el número de línea dentro de una string como la fuente del mensaje aplicado al string. La fuente de las cadenas se cuentan consecutivamente con la primera cadena que ha de ser 0. El número de líneas es uno más que el número de saltos de línea que han sido procesados. 3.3 PREPROCESSOR Hay un preprocesador que procesa la fuente de los strings como una parte de la copilación. La lista completa de directivas de preprocesado es la siguiente: # #define #undef #if #ifdef #ifndef #else #elif #endif 7 #error #pragma #extension #version #line El siguiente operador está también disponible defined Cada numero signo (#) puede ser precedido en su línea solo por espacios o tabuladores horizontales. También puede ser seguida de espacios y tabs horizontales, anteior a la directiva. Cada directiva es terminada por un salto de línea. El preprocesamiento no cambia el número o la ubicación relativa de los saltos de línea de un un string fuente. El número signo (#) en una línea por si mismo es ignorado. Cualquier directiva que no aparezca en la lista de arriba causa un mensaje de diagnóstico y hace que la aplicación de shaders se trate como mal formada. #define y #undef funcionalmente son definidos como en C++ la definición del preprocesado con macros, con y sin parámetros macro. Las siguientes predefinidas macros están disponibles __LINE__ __FILE__ __VERSION__ __LINE__ reemplazará un decimal constante que es uno más que el número de saltos de línea anterior en la actual cadena origen. __FILE__ reemplazará un decimal constante fuente que dice el número actual de cadenas que se están tramitando. __VERSION__ reemplazará un decimal que refleja la version del OpenGL Shading Language. En la versión descrita en este manual se sustituiría __VERSION__ por 120. Todos los nombres de macros que usan el doble subrayado descrito anteriormente ( __ ) están reservadas para un uso futuro como nombres de macros predefinidas. Todos los nombres de macros prefijados con “GL_” (“GL” seguido por un único carácter de subrayado) también están reservados. #if, #ifdef, #ifndef, #else, #elif, y #endif son defenidos de la misma forma que el preprocesado de C++. Las expresiones siguientes #if y #elif están restringidas para operandos con litorales enteros constantes, además de identificadores consumidos por el operador definido. Los caracteres constantes no son compatibles. Los operadores disponibles son los siguientes: 8 El operador defined puede ser usado de cada una de las formas siguientes: defined identifier defined ( identifier ) No hay un número signo basado en operadores (sin #, #@, ##, etc), ni existe un operador sizeof. La semántica de la aplicación de los operadores a un literal entero en el preprocesado corresponden a la norma del preprocesador de C++, no a los que están en el OpenGL Shading Language. La evaluación de las expresiones, por tanto dependerá del preporcesador de acogida y no del Shading Language. #error hará a la implementación poner un mensaje de diagnostico dentro del log de informacion del shader (ver la información del API externa para ver como se accede al log de un shader). El mensaje serán los tokens siguientes a la directiva #error hasta el primer salto de línea. En la implementación se debe entonces considerar si el shader está malformado. #pragma permite a la aplicación depender del control de compilador. Si en una implementación no se reconocen los tokens siguientes a #pragma entonces el programa la ignorará. Los siguientes pragmas son definidos como parte del lenguaje. #pragma STDGL El programa STDGL se usa para reservar pragmas para un uno futuro en las revisiones de este lenguaje. No puede usar una aplicación pragma cuyo primer token es STDGL. #pragma optimize(on) #pragma optimize(off) se pueden usar para activar o desactivar la ayuda en el desarrollo y depuración de shaders. Solo puede usarse fuera de definiciones de funciones. Por defecto, está activada la optimización de todos los shaders. La depuración pragma #pragma debug(on) 9 #pragma debug(off) puede ser usado para permitir la compilación y anotación de un shader con información de la depuración. Solo se puede usar fuera de la definición de funciones. Por defecto, está desactivado. Con los shaders debería declararse la versión del lenguaje en el que están escritos. Se especifica con #version number donde number debe ser el numero correspondiente a la versión del lenguaje, de la misma forma que hemos visto más arriba para __VERSION__. Por tanto, la directiva “#version 120” nos indicaría que en ese shader se ha usado la versión 1.20 del lenguaje. Cualquien número de versión del lenguaje que no sea compatible con el compilador generará un error. La versión 1.10 no necesita ser especificada dentro del shader, y por defecto si no se especifica la version del shader, este será tratado como si fuera de la versión 1.10. El comportamiento de los shaders de la version 1.10 no se verá afectado por los cambios introducidos en la versión 1.20. Diferentes shaders (unidades de copilación) que estén unidos en el mismo programa no tienen porque tener la misma versión; pueden ser una mezcla de la versión 1.10 con la versión 1.20. La directiva #versión debe suceder en un shader antes que cualquier otra cosa excepto los comentarios y los espacios en blanco. Por defecto, estos lenguajes deben emitir en tiempo de compilación los errores sintácticos, gramaticales y semánticos que no se ajusten a la especificación. Cualquier comportamiento prorrogado debe estar especificado (habilitado). Las directivas que controlan el comportamiento del compilador con respecto a las extensiones son declaradas con la directiva #extension #extension extension_name : behavior #extension all : behavior donde extension_name corresponde al nombre de la extensión. Los nombres de las extensiones no están documentados en esta especificación. El símbolo all significa que el comportamiento es aplicado a todas las extensiones del compilador. El comportamiento puede ser solo uno de los siguientes Comportamiento require enable warn Efecto Comportarse según lo especificado por la extension extension_name. Da un error sobre la #extension si la extensión no es soportada, o si all está especificado. Comportarse según lo especidicado por la extensión extension_name. Advierte sobre la #extension si es soportada. Da un error sobre la #extension si todo es especificado. Comportarse según lo especificado por la extensión extension_name, excepto emitir advertencias sobre algún uso detectado de esa extensión a no ser que dicho uso sea soportado o requerido por otras extensiones. Si todo está especificado entonces advertir sonbre todos los usos detectados de cualquier extensión usada. Advertir sobre la #extension si la 10 disable extension_name no es compatible. Comportarse (incluyendo el envio de errores y advertencias) como si la extension extension_name no formase parte de la definición del lenguaje. Si todo está especificado entonces el comportamiento debe volver al de no extendido de la versión básica del lenguaje que está siendo compilado. Advertir sobre la #extension si la extension_name no es soportada. La directiva extension es un simple mecanismo de bajo nivel para configurar el comportamiento de cada extension. No definir políticas como en las cuales las combinaciones son apropiadas, deben ser definidas en otros sitios. El orden de las directivas es importante realizarlo conforme al comportamiento de cada extension: las directivas que ocurren más tarde sobreescriben a las vistas antes. La variante all coloca el comportamiento de todas las extensiones, sobreescribiendo todas las previamente emitidas directivas extension, pero solo para los comportamientos warn and disable. El estado inicial del compilador es como si la directiva #extension all : disable fuese emitida, diciendo al compilador que todo error y toda advertencia deben ser informadas de acuerdo a esta especificación, ignorando cualquier extension. Cada extensión puede definir el ámbito permitido granularmente. Si no se dice nada, la granularidad es de un shader (esto quiere decir una unidad simple de compilación) y la directivas de extensión deben ocurrir antes que cualquier simbolo del no-preprocesado. Si es necesario el lincador puede hacer valer granularidades mayores que una única unidad de compilación, en el cual cada caso de shader complejo tendrá que contener necesariamente directivas de extensión. La expansión macro no se realiza en las líneas que contengan directivas #extension y #version. #line puede tener sustitución macro, de una de las dos siguientes formas: #line line #line line source-string-number donde line y source-string-number son expresiones enteras constantes. Después de procesar esta directiva (incluyendo el salto de línea) la implementación se comportará como si estuviera compilando la línea de número linea+1 y el string fuente de número source-string-number. El posterior string fuente será numerado secuenciamente, hasta que otra directiva #line sobreescriba esa numeración. 3.4 COMENTARIOS Los comentarios son dilimitados por /* y */, o por // y un retorno de línea. Los delimitadores de inicio de comentario (/* o //) no se reconocen como delimitadores de comentarios dentro de uno mismo, por tanto los comentarios no se pueden anidar. Si un comentario reside por entero en una sola línea, es sintácticamente tratado como un espacio solo. Los retornos de línea no son eliminados por los comentarios. 3.5 TOKENS Este lenguaje es una secuencia de tokens (símbolos), un token puede ser token: 11 keyword identifier integer-constant floating-constant operator ;{} 3.6 KEYBOARDS Las siguientes son las palabras clave de este lenguaje, y no pueden ser usadas para cualquir otro propósito definido por este documento: attribute const uniform varying centroid break continue do for while if else in out inout float int void bool true false invariant discard return mat2 mat3 mat4 mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4 vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 sampler1D sampler2D sampler3D samplerCube sampler1DShadow sampler2DShadow struct Las siguientes son palabras clave reservadas para uso futuro. Usandolas el resultado será un error: asm class union enum typedef template this packed goto switch default inline noinline volatile public static extern external interface long short double half fixed unsigned lowp mediump highp precision input output hvec2 hvec3 hvec4 dvec2 dvec3 dvec4 fvec2 fvec3 fvec4 sampler2DRect sampler3DRect sampler2DRectShadow sizeof cast namespace using Además, todos los identificadores que contengan dos subrayados consecutivos ( __ ) son reservados para un futuro puedan ser palabras clave. 3.7 IDENTIFICADORES Los identificadores son usados para los nombres de las variables, nombres de funciones, nombres de estructuras, y selectores de campos (los selectores de campos sellecionan componentes de matrices y vectores similar al selector de campos de estructuras, tratado en la sección 5.5 “Componentes de los vectores” y la sección 5.6 “Componentes de las matrices”). Los identificadores tienen la forma 12 identifier nondigit identifier nondigit identifier digit nondigit: one of _abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ digit: one of 0123456789 Los identifecadores que comienzan con “gl_” son reservados para uso de OpenGL, y no pueden ser declarados en un shader como una variable o una función. 4. VARIABLES Y TIPOS Todas las variables y funciones deben ser declaradas antes de ser utilizadas. Los nombres de las variables y de las funciones son identificadores. No hay tipos por defecto. Todas las declaraciones de variables y funciones deben tener un tipo declarado, y opcionalmente calificadores. Una variable se declara especificando su tipo seguido por uno o más nombres separados por comas. En muchos casos, una variable puede ser inicializada como parte de su declaración utilizando el operador de asignación (=). En la gramática al final de este documento, se proporciona una referencia completa de la sintaxis de la declaración de variables. Tipos definidos por el usuario se pueden definir usando struct para agregar una lista de los tipos actuales en un solo nombre. El Lenguaje de Sombreado OpenGL es de tipo seguro. No existen conversiones entre tipos implícitos, con la excepción de que un valor puede aparecer cuando un tipo de punto flotante se espera, y se convertiría en un valor de coma flotante. Exactamente cómo y cuándo puede ocurrir esto se describe en la Sección 4.1.10 "Conversiones Implécitas", y como referencia en otras secciones de esta especificación. 4.1 TIPOS BASICOS El Opengl Shading Language es compatible con los siguientes tipos de datos básicos: Type void bool int float vec2 vec3 vec4 bvec2 bvec3 bvec4 ivec2 ivec3 ivec4 Meaning para funciones que no devuelven un valor un tipo condicional, toma un valor o verdadero o falso entero con signo flotantes sencillos vector de 2 componentes punto flotantes vector de 3 componentes punto flotantes vector de 4 comonentes punto flotantes vector de 2 comonentes Boolean vector de 3 comonentes Boolean vector de 4 comonentes Boolean vector de 2 comonentes enteros vector de 3 comonentes enteros vector de 4 comonentes enteros 13 mat2 mat3 mat4 mat2x2 mat2x3 mat2x4 mat3x2 matriz 2x2 de punto flotantes matriz 2x3 de punto flotantes matriz 2x4 de punto flotantes algo como mat2 matriz con 2 columnas y 3 filas de punto flotantes matriz con 2 columans y 4 filas de punto flotantes matriz con 3 filas y dos columnas de punto flotantes algo como mat3 matriz de 3 filas y cuatro columnas de punto flotantes matriz de 4 filas y dos columnas de punto flotantes matriz de 4 filas y 3 columnas de punto flotantes algo como mat4 un manejador para acceder a texturas 1D un manejador para acceder a texturas 2D un manejador para acceder a texturas 3D un manejador para acceder a un cubo mapeado de texturas un manejador para acceder a una textura 1D con comparación un manejador para acceder a una textura 2D con comparación mat3x3 mat3x4 mat4x2 mat4x3 mat4x4 sampler1D sampler2D sampler3D samplerCube sampler1DShadow sampler2DShadow Además un shader puede agregar estos usando arrays y estructuras para formar tipos más complejos. 4.1.1 VOID Funciones que no devuelven un valor debe ser declarado nulo. No hay ningún tipo de retorno por defecto la función. El vacío de palabras clave no se puede utilizar en cualquier otras declaraciones (con excepción de las listas de parámetros formales vacíos). 4.1.2 BOOLEANS Para hacer una ejecución condicional en el código más fácil de expresar, el tipo bool es compatible. No hay expectativa de que el hardware soporte directamente las variables de este tipo. Es un tipo específico el booleano, la ocurrencia de sólo uno de dos valores, ya sea verdadera o falsa. Dos palabras verdaderas y falsas puede ser utilizado como literal Boolean constantes. Se declaran las operaciones booleanas, y, opcionalmente, inicializada como en la aplicación de ejemplo: bool success; // declare “success” to be a Boolean bool done = false; // declare and initialize “done” El lado derecho del operador de asignación (=) puede ser cualquier expresión cuyo tipo es bool. Las expresiones utilizadas para los saltos condicionales (si, por,?: Mientras, hacer-mientras) debe evaluar el tipo bool. 14 4.1.3. Integers Los enteros vienen predefinidos en el lenguaje para ayudar al programador. A nivel de hardware, su representación real podría contribuir a la eficiencia de los bucles y de los índices de los arrays, así como referenciar las unidades de textura. Por ello no es requerido que los enteros implementados en el lenguaje coincidan con su representación hardware. Ni siquiera que soporte el rango de los enteros. Dada su misión como mera ayuda, tan sólo se exigen dieciséis bits de precisión, así como la representación del signo; tanto para los lenguajes de vértice como los de fragmento. Una implementación del OpenGL Shading Language podría convertir los enteros a floats para operar con ellos. Se permite que una implementación utilice más de dieciséis bits de precisión para manipular los enteros, si bien la naturaleza de dicha implementación no tendría que ser soportada por otras máquinas. Aquellos shaders que sobrepasen los dieciséis bits de precisión podrían no ser portátiles (portables). Los enteros son declarados y opcionalmente inicializados con expresiones de tipo entero, como se muestra en el siguiente ejemplo: int i, j = 42; Las constantes de tipo entero pueden ser expresadas en formato decimal (base 10), octal (base 8) o hexadecimal (base 16) como sigue: integer-constant: decimal-constant octal-constant hexadecimal-constant decimal-constant: nonzero-digit decimal-constant digit octal-constant: 0 octal-constant octal-digit hexadecimal-constant: 0x hexadecimal-digit 0X hexadecimal-digit hexadecimal-constant hexadecimal-digit digit: 0 nonzero-digit nonzero-digit: uno de 123456789 octal-digit: uno de 01234567 hexadecimal-digit: uno de 0123456789 abcdef ABCDEF 15 No se permiten espacios en blanco entre los números que definen una constante, incluyendo después del 0 o del 0x o 0X. Si el signo “-“ precede la constante éste será interpretado como el operador unario de negación aritmética, y no como parte de la constante. No hay sufijos con letra. 4.1.4. Floats Los flotantes están disponibles para el uso de cálculos escalares. Las variables de coma flotante se definen como viene ejemplificado a continuación: float a, b = 1.5; Como valor de entrada a una de las unidades de procesado, un valor en coma flotante se espera que tenga la representación del estándar IEEE para simple precisión y rango dinámico. No es obligatorio que la precisión durante el procesado se ajuste a las especificaciones del IEEE, pero sí a las especificadas por la especificación de OpenGL 1.4. Análogamente, el tratamiento a condiciones como la división por cero deberían desembocar en un resultado sin especificar, pero en ningún caso ser causa de aborto de una ejecución. Las constantes en punto flotante son definidas como sigue: floating-constant : fractional-constant exponent-partopt floating-suffixopt digit-sequence exponent-part floating-suffixopt fractional-constant : digit-sequence . digit-sequence digit-sequence . . digit-sequence exponent-part : e signopt digit-sequence E signopt digit-sequence sign : uno de +– digit-sequence : digit digit-sequence digit floating-suffix: uno de fF Un punto decimal (.) no es necesario si la parte del exponente está presente. No puede haber ningún espacio en blanco interrumpiendo una secuencia de coma flotante. Si el signo “-“ precede la constante éste será interpretado como el operador unario de negación aritmética, y no como parte de la constante. 4.1.3. Vectors El lenguaje OpenGL Shading Language incluye tipos de datos correspondientes con vectores de dos, tres y hasta cuatro dimensiones en coma flotante, enteros o booleanos. Los vectores de variables en coma flotante pueden ser usadas para almacenar gran cantidad de informaciones muy importantes en los gráficos en computación, como colores, normales, posiciones, coordinadas de texturas, etc. Los vectores booleanos pueden ser usados para comparaciones entre vectores numéricos. Definir vectores como parte del lenguaje de shaders permite una relación directa entre el lenguaje y su representación en el hardware de procesado gráfico. En general, las aplicaciones podrán aprovechar este paralelismo, ganando así en eficiencia. Algunos ejemplos de la declaración de vectores son: 16 vec2 texcoord1, texcoord2; vec3 position; vec4 myRGBA; ivec2 textureLookup; bvec3 less; La inicialización de vectores se puede realizar a través de constructores, que serán explicados en breve. 4.1.4. Matrices El lenguaje OpenGL Shading Language tiene predefinidas matrices con valores en coma flotante de 2x2, 2x3, 2x4, 3x2, 3x3, 3x4, 4x2, 4x3, 4x4. El primer número de este tipo es el número de columnas, y el segundo el de filas. Son ejemplos de declaración de matrices: mat2 mat2D; mat3 optMatrix; mat4 view, projection; mat4x4 view; // una forma alternativa de declarar una matriz 4x4 mat3x2 m; // una matriz con tres columnas y dos filas La inicialización de las matrices se hace mediante constructores (descritos en la sección 5.4 1 “Constructores”) ordenando por columnas de mayor a menor . 4.1.7. Samplers Las variables de tipo Sampler son herramientas para trabajar con texturas. Son usadas con las funciones predefinidas que actúan sobre las texturas (descritas en la sección 8.7 “Funciones de Búsqueda de Textura”) para indicar a cuál de ellas acceder. Sólo pueden ser declaradas como parámetros de funciones o variables uniformes (ver la sección 4.3.5 “Uniforme”). Salvo el indexado de las matrices, la selección del campo de la estructura y los paréntesis, los samplers no pueden ser operandos en una expresión, ni tratarse como l-valores, ni parámetros de salida o entrada y salida de las funciones, ni pueden ser asignados dentro de una función. Como constantes, ellas son inicializadas sólo con la API de OpenGL, no pueden ser declaradas o inicializadas en un shader. Como parámetros de función, sólo los samplers deberían ser pasados a los samplers de comparación de tipos. Esto aporta robustez en la comparación de los accesos de las texturas de los shaders y el estado de las texturas de OpenGL antes de que se ejecute un shader. 4.1.8. Estructuras Los tipos definidos por el usuario pueden ser creados añadiendo otros tipos ya definidos dentro de una estructura utilizando la palabra clave struct. Por ejemplo: Struct light{ float intensity; vec3 position; } lightVar; En este ejemplo, light es el nombre de un nuevo tipo, y lightVar es una variable del tipo light. Para declarar variables del nuevo tipo se utiliza este nombre (sin la palabra clave struct). light lightVar2; De modo más formal, las estructuras son declaradas como a continuación. De todas formas, la gramática se ve en la sección 9, “Gramática del Lenguaje de Shaders”. 1 En el manual original el orden viene explicado como “in column-major order” 17 struct-definition : qualifieropt struct nameopt { member-list } declaratorsopt ; member-list : member-declaration; member-declaration member-list; member-declaration : basic-type declarators; donde name es el tipo definido por el usuario, pudiendo ser usado para declarar variables de éste nuevo tipo. El name comparte el mismo espacio de nombre que las otras variables, tipos y funciones, quedando sujeto a las mismas reglas. El qualifier opcional sólo se aplica a cualquier declarators, y no forma parte del tipo definido por name. Las estructuras deben tener al menos un miembro. Los miembros declarados no contienen ningún cualificador, ni contienen campos de bits. Los tipos de los miembros deben estar ya definidos (no hay referencias previas). Los miembros declarados no pueden contener inicializadores, aunque sí pueden contener matrices. Esas matrices deben tener un espacio especificado, y el tamaño debe ser una expresión entera constante mayor que cero (ver la sección 4.3.3 “Expresiones Constantes”). Cada nivel de la estructura tiene su propio espacio de nombre para su nombre dado en los declaradores de miembro; tales nombres necesitan ser unicos solamente del espacio del nombre. Las estructuras anónimas no se permiten, ni tampoco las estructuras embebidas. struct S { float f; }; struct T { S; // Error: no se permiten estructuras anónimas struct { ... }; // Error: no se permiten estructuras embebidas S s; // Ok: las estructuras anidadas con nombre son permitidas }; Las estructuras pueden ser inicializadas en la declaración usando constructores, que serán explicados en la sección 5.4.3 “Constructores de Estructuras”. 4.1.9. Matrices Las variables del mismo tipo pueden ser agrupadas en matrices declarando un nombre seguido de unos paréntesis ([]) encerrando opcionalmente un tamaño. Cuando se especifica un tamaño de la matriz en la declaración éste debe ser una expresión de tipo entero (ver la sección 4.3.3 “Expresiones Constantes”) mayor que cero. Si una matriz es indexada con una expresión que no es una expresión entera constante, o si la matriz es pasada como argumento a una función, entonces su tamaño debe ser declarado antes de su uso. Se permite declarar una matriz sin un tamaño y más tarde redeclarar el mismo nombre como un array del mismo tipo indicando el tamaño. No se permite declarar una matriz con un tamaño para, posteriormente (en el mismo shader), indexar la misma matriz con una constante de tipo entero mayor o igual que la declarada. Tampoco se permite indexar una matriz con una expresión entera constante negativa. Las matrices declaradas como parámetros formales en la declaración de una función deben especificar su tamaño. No está fefinido el comportamiento ante la indexación de una matriz con una expresión no constante que sea mayor o igual que el tamaño del array, o bien sea menor que cero. Sólo las matrices de una dimensión podrían ser declaradas. Todos los tipos y estructuras básicas pueden ser agrupadas en matrices. Algunos ejemplos son: float frequencies[3]; uniform vec4 lightPosition[4]; light lights[]; const int numLights = 2; 18 light lights[numLights]; Un tipo de matriz puede ser creado especificando el tipo seguido de los paréntesis cuadrados ([]) e incluyendo el tamaño: float[5]; Este tipo puede ser usado en cualquier lugar donde otro tipo puediera ser usado, incluyendo el valor de retorno de una función: float[5] foo() {} como constructor de una matriz: float[5](3.4,4.2,5.0,5.2,1.1) como un parámetro anónimo: void foo(float[5]) y como un modo alternativo de declarar una variable o un parámetro de función: float[5] a; Es un error declarar matrices de matrices: float a[5][3]; //ilegal float[5] a[3]; //ilegal Las matrices pueden tener inicializadores formados por constructores de matrices: float a[5] = float[5](3.4,4.2,5.0,5.2,1.1) float a[5] = float[](3.4,4.2,5.0,5.2,1.1) Las matrices sin signo pueden ser explícitamente dimensionadas por un inicializador en su declaración: Float a[5]; ... float b[] = a; //b es de tamaño 5 float b[5] = a; //es lo mismo De todos modos, el dimensionamiento de las matrices no puede ser asignado. Resulta raro que los inicializadores y las asignaciones parecieran tener diferentes semánticas. Las matrices saben el numero de elementos que contienen. Éste puede ser obtenido usando el método length: a.length(); // devuelve 5 para las declaraciones anteriores El método length no puede ser invocado en una matriz que no ha sido explícitamente dimensionada. 19 4.1.10. Conversiones implícitas En determinadas situaciones una expresión y su tipo pueden ser convertidas a otro tipo diferente. La siguiente tabla muestra todas las conversiones implícitas: Tipo de expresión entero ivec2 ivec3 ivec4 Puede ser convertida implícitamente a flotante vec2 vec3 vec4 No hay conversiones implícitas entre matrices o estructuras. Por ejemplo, una matriz de enteros no podría ser convertida a una matriz de flotantes. Cuando se realiza una conversión implícita no se realiza una reinterpretación de su valor, sino una conversión al valor equivalente en el otro tipo. Por ejemplo, el entero 5 será convertido al valor en coma flotante 5,0. Las conversiones de la tabla superior son realizadas sólo en los casos indicados en otras secciones de este manual. 4.2. Alcance El alcance de una variable se establece en el momento de su declaración. Si es declarada fuera de todas las definiciones de funciones, ésta tiene un alcance global, existiendo desde el momento que es declarada hasta la terminación del shader en el que ha sido declarada. Si es declarada en un bucle for o while, ésta durará hasta la salida del bucle. En cualquier caso, si es declarada en una sentencia simple tendría alcance en la sentencia compuesta de la que forme parte. Si es declarada como parámetro de una función ésta tendrá alcance dentro de la función. El cuerpo de una función tiene alcance sólo dentro de la misma. La sentencia if no permite la declaración de variables en su interior, de modo que no puede hablarse de su alcance. Dentro de una declaración, el alcance de una variable comienza inmediatamente antes del inicializador si éste estuviese presente o inmediatamente después de que su nombre haya sido declarado en el caso contrario. Algunos ejemplos son: int x = 1; { int x = 2, y = x; // y es inicializada a 2 } struct S { int x; }; { S S = S(0,0); // S sólo es visible como estructura y constructor S; // S ahora es visible como variable } int x = x; // Error si x no ha sido previamente definido Todos los nombres de variables, de tipos de estructuras y de funciones para un determinado alcance comparten un mismo espacio de nombres. Los nombres de las funciones pueden ser redeclarados en el mismo alcance, con un número de parámetros igual o diferente, sin que esto 20 sea un error. Una matriz con tamaño implícito puede ser redeclarada en el mismo alcance como una matriz del mismo tipo base. En cualquier caso, dentro de una unit, un nombre declarado no puede ser redeclarado en el mismo alcance; ello conllevaría un error de redeclaración. Si un alcance anidado redeclara un nombre usado en otro alcance éste oculta todos los usos de ese nombre. No hay forma de acceder a éste nombre oculto ni de hacer que sea visible sin salir del alcance que lo ha escondido. Las funciones predefinidas tienen un alcance dentro de otro alcance diferente al alcance global de usuario(donde son definidas las variables globales). Es decir, un alcance global de shaders donde el usuario escribe las variables globales y las funciones, está anidado en otro alcance donde vienen las funciones predefinidas. Cuando el nombre de una función es redefinido en un alcance anidado oculta aquellas funciones que tenían el mismo nombre en alcances diferentes. La declaración de funciones (prototipo) no pueden ser definidas dentro de otras funciones. Tienen que definirse en el alcance global, o, para las funciones predefinidas, fuera del alcance global. Las shared globals son variables globales declaradas con el mismo nombre en unidades compiladas independientes (shaders) del mismo lenguaje (de vértice o de fragmento) que son ensambladas para hacer un programa. Las shared globals comparten el mismo, y deben ser declaradas con el mismo tipo. Compartirán el mismo lugar de almacenamiento. Las matrices compartidas globales deben tener el mismo tipo base y el mismo tamaño explícito. Una matriz con tamaño implícito en un shader puede pasar a tener el tamaño explícito por otro shader. Si ninguno de los shaders tiene un tamaño explícito para la matriz ésta adquirirá el mayor tamaño implícito. Los escalares deben tener exactamente el mismo tipo y la misma definición de tipo. Las estructuras deben tener el mismo nombre, la misma secuencia de nombres y definiciones de tipo, y los campos asociados a cada nombre deben de ser del mismo tipo. Esta regla se aplica recursivamente a los tipos anidados. Todos los inicializadores para un shared global tienen que tener el mismo valor, o se producirá un error en el ensamblado. 4.3. Cualificadores de almacenado La declaración de variables podría tener un cualificador de almacenado delante del tipo. Vienen descritos como sigue: Cualificador < none: default > const attribute uniform varying centroid varying Significado lectura/escritura en memoria local o parámetro de entrada de una función una constante en tiempo de compilación o un parámetro de sólo lectura unión entre un shaders de vértice y OpenGL para datos de vértices valor invariable durante el procesado de la primitiva, forman la unión entre un shader, OpenGL y la aplicación unión entre un shader de vértice y uno de fragmento para la interpolación de datos Las variables globales sólo pueden usar como cualificadores const, attribute, uniform, varying o centroid varying. Las variables locales sólo pueden usar el cualificador de almacenado const. Los parámetros de función sólo pueden usar el cualificador const. Los cualificadores de parámetros son comentados con más detalle en la sección 6.1.1 “Convención de Llamadas a Funciones”. Los tipos de retorno de funciones y los campos de estructuras no usan cualificadores de almacenado. 21 No existen tipos de datos para la comunicación desde la ejecución de un shader a su próxima ejecución (para la comunicación entre fragmentos o vértices). Esto debería prevenir la ejecución en paralelo del mismo shader en múltiples vértices o fragmentos. Los inicializadores sólo pueden ser usados en variables globales sin cualificador de almacenado, con el cualificador const o con el cualificador uniform. Las variables globales sin cualificador de almacenado que no estén inicializadas en su declaración o por la aplicación no serán inicializadas por OpenGL, o mejor dicho serán entendidas en el main() como valores indefinidos. 4.3.1. Cualificador de almacenado por defecto Si no hay ningún cualificador presente en una variable global entonces ésta no puede ser usada en la aplicación o en otros shaders que se estén ejecutando en procesadores distintos. Para variable global o local sin cualificar, la declaración servirá para reservar memoria del procesador que corresponda. Esta variable permitiría las operaciones de acceso a lectura y escritura para esta memoria destinada. 4.3.2. Const Las llamadas constantes en tiempo de ejecución pueden ser declaradas usando el cualificador const. Cualquier variable con el cualificada como constante es de sólo lectura para ese shader. Declarar las variables como constantes permite codificar shaders de modo mucho más descriptivo que usando constantes numéricas. El cualificador const puede usarse con cualquier tipo de datos básico. Es un error asignar una constante fuera de su declaración; tiene que ser inicializada con ésta. Por ejemplo: const vec3 zAxis = vec3 (0.0,0.0,1.0); Los campos de las estructuras no pueden ser cualificados con const, si bien las estructuras pueden ser declaradas con const y ser inicializadas con un cosntructor. Los inicializadores para declaraciones constantes deben ser expresiones constantes, como se explica en la sección 4.3.3 “Expresiones Constantes”. 4.3.3. Expresiones constantes Una expresión constante es una de: • • • • • un valor literal (por ejemplo, 5 o true) una variable local o global cualificada con const (sin incluír los parámetros de función) una expresión formada por operadores y operandos que constituyen una expresión constante, incluyendo tomar el elemento de una matriz, un campo de una estructura constante, o componentes de un vector constante un constructor cuyos argumentos son todos expresiones constantes la llamada a una función predefinida cuyos argumentos son expresiones constantes, a excepción de las funciones de apariencia de texturas, las funciones de ruido y ftransform. Las funciones predefinidas dFdx, dFdy y fwith deben devolver 0 cuando son evaluadas dentro de un inicializador con un argumento que es una expresión constante Las llamadas a funciones definidas por el usuario (funciones no predefinidas) no pueden usarse como expresiones constantes Una expresión constante literal es una expresión que se evalúa a un entero escalar. 22 Las expresiones constantes serán evaluadas serán evaluadas siempre del mismo modo para que en distintos shaders las mismas expresiones den lugar a los mismos resultados. Ver la sección 4.6.1 “El Cualificador Constante” para más detalles sobre cómo crear expresiones constantes. 4.3.4. Atributo El cualificador attribute se usa para declarar variables que son pasadas a un shader de vértice desde OpenGL a una base para vértices. Es un error declarar una variable como atributo en cualquier shader que no sea de tipo vértice. Las variables atributo son de sólo lectura para el shader de vértice. Los valores de dichas variables son pasadas al shader de vértice desde la API de vértice de OpenGL como parte de una matriz de vértice. Tales valores transportan los atributos de vértice al shader de vértice y se espera que cambien cada vez que se ejecuta un shader de vértice. El cualificador de atributo sólo puede ser usado con flotantes, vectores de flotantes y matrices, mientras que no puede ser usado en estructuras. Ejemplos de declaraciones: attribute vec4 position; attribute vec3 normal; attribute vec2 texCoord; Todos los atributos de vértice estándares de OpenGL tienen nombres de variables predefinidos para permitir una mayor integración entre los programas de OpenGL y las funciones de vértice. Ver la sección 7 “Variables Predefinidas” para ver la lista completa de los nombres de atributos predefinidos. Se espera que el hardware gráfico tenga un pequeño número de lugares destinados al paso de los atributos de vértice. Por lo tanto, el lenguaje GLSL define las varialbes que no sean matrices como si tuviesen espacio para hasta cuatro valores en coma flotante. Éste es un límite de implementación en el número de variables de tipo atributo que pueden ser usadas. Si éste fuese excedido se produciría un error de ensamblado (las varialbes atributo declaradas no usadas no se tienen en cuenta a la hora de verificar el límite). Un atributo flotante cuenta de la misma forma que un vector de tipo vec4, por lo que podría contemplarse el agrupar de cuatro en cuatro los atributos flotantes en un vec4 a fin de aprovechar mejor el hardware subyacente al shader. Una matriz como atributo usaría varios lugares de de atributo; tantos como número de columnas tenga. Las variables atributo son necesarias para tener un alcance global y deben ser declaradas fuera del cuerpo de las funciones antes de ser usadas. 4.3.5. Uniforme El cualificador uniform se usa para declarar variables cuyos valores son los mismos durante la misma primitiva que se está procesando. Todas las variables uniformes son de sólo lectura y son inicializadas externamente bien durante el ensamblado o bien por medio de la API. El valor inicial durante el ensamblado puede ser el valor del inicializador, de estar éste presente; o 0, si no lo estuviera. Los tipos muestreados no pueden tener inicializadores. Son ejemplos de declaración: uniform vec4 ligthPosition; uniform vec3 color = vec3(0.7,0.7,0.2); El cualificador uniforme puede utilizarse con cualquier tipo de datos básico, declarando una estructura, una matriz o cualquier otra variable. 23 Hay un límite debido a la implementación en la cantidad de almacenamiento de uniformes que pueden ser usados para cada tipo de shader y, de excederse, causaría un error en tiempo de compilación o de ensamblado. Las variables uniformes que son declaradas pero no usadas no se tienen en cuenta a la hora de verificar el límite. El número de variables uniformes definidas por el usuario y predefinidas que son usadas en un shader se emplean para determinar si se excede el citado límite. Si se ensamblan juntos varios shaders éstos compartirán el espacio reservado a las uniformes. Por tanto, los tipos e inicializadores de variables uniformes con el mismo nombre deberían coincidir en todos los shaders a ensamblar para crear un ejecutable. Se permite que algunos shaders aporten su propio inicializador para una variable mientras que otros no lo aporten, pero todos los inicializadores deben ser iguales. 4.3.6. Mutantes Las variables mutantes proporcionan la interfaz entre los shaders de vértice, los de fragmento y la 2 funcionalidad arreglada entre ellos. Los shaders de vértice procesan valores de vértice (como el color, la textura, las coordinadas, etc) y las escriben en variables declaradas con el cualificador varying. Además, tal vez lea variables declaradas como varying, devolviendo los mismos valores si fueran escritos. Leer un mutante en un shader de vértice devuelve valores indefinidos si éstos fuesen leídos antes de ser escritos. Por definición, los mutantes son establecidos por vértice e interpolados respecto a la perspectiva sobre la primitiva que está siendo renderizada. En single-sampling, el valor es interpolado al centro del pixel, y el cualificador centroid, de estar presente, sería ignorado. En multi-sampling y el varying no está cualificado por el centroid, el valor debe ser interpolado al pixel del centro, en cualquier lugar en el pixel o en uno de los pixels de muestra. Si estuviese presente el centroid entonces el valor deberá ser interpolado que descanse tanto en el pixel como en en la primitiva que esté renderizándose, o bien en uno de los pixeles de muestra que están dentro de la primitiva. Debido a la menor regularidad de ubicación de los centroides, sus derivadas podrían ser menos precisos que las variables mutantes que no tengan centroide. Cuando se usa la palabra reservada centroid ésta debe preceder al cualificador varying. Un shader de fragmento podrían leer de variables mutantes y su valor sería el valor interpolado de las variables, como una función de la posición de los fragmentos en la primitiva. Un shader de fragmento no puede escribir en un mutante. El tipo y presencia de los cualificadores centroid y invariant de variables mutantes con el mismo nombre declarados en en los shaders de vértice y fragmentos ensamblados debe coincidir, o se producirá un error de ensamblado. Sólo aquellos mutantes usadas en shaders de fragmento ejecutable deben escribirse a través de un shader de vértice ejecutable. Declarar mutantes superfluos en un shader de vértice es admitido. Ejemplos de declaración de mutantes: varying vec3 normal; centroid varying vec2 TextCoord; invaring centroid varying vec4 Color; El cualificador varying sólo se puede aplicar a flotantes, vectores de flotantes, o matrices de flotantes. Las estructuras no pueden ser mutantes. 2 En el manual original se refiere a ello como “fixed functionality” 24 Si no hay ningún shader de vértice activo, el camino de datos de funcionalidad corregida de OpenGL procesará valores de los mutantes predefinidos que serán consumidos por el shader de fragmento ejecutable. Igualmente, si no hubiese ningún shader de fragmento activo sería el shader de vértice el responsable de procesar y escribir los mutantes que son necesitados por el camino de datos de funcionalidad corregida de OpenGL. Los mutantes se necesitan para tener un alcance global, y deben ser declarados fuera de los cuerpos de funciones antes de su primer uso. 4.4. Cualificadores de Parámetros Los parámetros pueden tener los siguientes cualificadores: Cualificador <none : default> in out inout Significado lo mismo que in para parámetros de función recibidos por una función para parámetros de función devueltos por una función, pero no inicializados cuando son pasados para parámetros de función que son recibidos y devueltos Los cualificadores de parámetros son explicados con mayor detalle en la sección 4.1.1 “Convenciones de Llamadas a Funciones”. 4.5. Precisión y Cualificadores de Precisión Número de sección reservado para los cualificadores de precisión (reservado para usarlo posteriormente). 4.6. Mutantes y el Cualificador de Mutantes En esta sección los mutantes hacen referencia a la posibilidad de conseguir diferentes valores de la expresión en diferentes programas. Por ejemplo, dados dos shaders de vértice, en diferentes programas, para el mismo uso de gl_Position, no son exactamente los mismos valores cuando el programa se ejecuta. Es posible, debido a la compilación independiente de los dos shaders, que los valores asignados a gl_Position no sean los mismos durante la ejecución. En este ejemplo esto podría causar problemas de alineación en la geometría de un algortimo que se ejecute en varios pasos. En general, dicha variación entre los shaders es permitida. Cuando tal varianza no existe se dice que la variable es invariante. 4.6.1. El Cualificador Invariante Para asegurar que una variable devuelta es invariante es necesario utilizar el cualificador invariant. Incluso se puede usar este cualificador después de declarar la variable. invariant gl_Position; // hacer la posición gl_Position invariante varying vec3 Color; invariant Color; // hacer el Color existente invariante o como parte de una declaración cuando una variable es declarada: invariant varying vec3 Color; 25 El cualificador invariante puede preceder a cualquier cualificador de almacenamiento (varying) cuando son combinados en una declaración. Sólo las variables de salida de un shader de vértice son candidatas a ser invariantes. Esto excluye las variables definidas por el usuario, los mutantes predefinidos de lado de vértice y las variables especiales de vértice gl_Position y gl_PointSize. Para los mutantes que estén saliendo de un shader de vértice a un shader de frangmento con el mismo nombre la palabra clave invariant debe estar presente en ambos shaders. Dicha palabra puede estar precedida de una lista de indentificadores previamente declarados separada por una 3 coma . Cualquier uso de los invariantes debe ser de alcance global, y antes del mismo la variable debería de estar previamente declarada. Para garantizar la inmutabilidad de una variable de salida en dos programas deben cumplirse las siguientes condiciones: • La variable de salida está declarada como invariante en ambos programas • Deben tener los mismos valores todas aquellas variables de entrada que contribuyan por medio de expresiones y de flujo de control a la asignación de la variable de salida • El formato de texturas, valores de texel y el filtrado de texturas son establecidos de la misma forma para todas las llamadas de funciones de texturas que contribuyan al valor de la variable de salida • Todos los valores de entrada son operados de la misma forma. Todas las operaciones en las expresiones de consumo y en cualquier expresión intermedia deben ser las mismas, con el mismo orden de operandos y la misma asociatividad, a fin de tener el mismo orden de evaluación. Las variables intermedias y las funciones deben ser declaradas con el mismo tipo con los mismos cualificadores de precisión, explícitos o implícitos. Cualquier flujo de control que afece al valor de salida debe ser el mismo, y cualquier expresión evaluada para determinar dicho flujo deben estar sujetos a estas reglas • Todos los datos de flujo y control de flujo destinados a establecer la variable invariante de salida deben de estar en una única unidad de compilación Básicamente, todos los flujos y controles de flujo que afecten al valor de salida deben coincidir. Inicialmente, por defecto, todas las variables de salida pueden ser mutantes. Para forzar a que todas las variables de salida sean invariantes puede usarse la directiva #pragma STDGL invariant(all) antes de todas las declaraciones del shader. Si esta directiva es usada después de la declaración de alguna variable o de alguna función el comportamiento de la directiva no está definido. Es un error utilizar esta directiva en un shader de fragmento. Generalmente la invarianza se consigue en detrimento de la flexibilidad en las optimizaciones, así que la puesta en marcha puede ser peor a causa de la invarianza. Por tanto el uso de esta directiva es entendida como una tarea de depuración, para evitar que se declaren individualmente todas las salidas como invariantes. 4.6.2. Invarianza de Expresiones Constantes La invarianza debe de estar garantizada para las expresiones constantes. El resultado de la evaluación de una expresión constante debe ser siempre el mismo si éste apareciese de nuevo en el mismo o en otro shader. Esto implica a la misma expresión apareciendo en shaders tanto de fragmento como de vértice o la misma expresión apareciendo en diferentes shaders de fragmento o vértice. 3 En el manual original la frase dice “The invariant keyword can be followed by a comma separated list of previously declared identifiers” 26 Las expresiones constantes deben tener el mismo resultado cuando se opera con ellas en todas las situaciones descritas anteriormente. 4.7. Orden de los Cualificadores Cuando hay varios cualificadores presentes éstos deben seguir un orden estricto. Este orden es el siguiente: Cualificador invariante cualificador de almacenamiento Cualificador de almacenamiento cualificador de parámetro 5. Operaciones y Expresiones 5.1. Operadores El GLSL tiene los siguientes operadores. Reservar estas marcas es ilegal. Precedencia Clase de Operadores 1 (la mayor) establecimiento de prioridad 2 función de llamada a una matriz, constructor de campos de estructuras o selector de método, incremento y decremento prefijo y postfijo 3 incremento y decremento unario(la tilde es reservada) 4 multiplicativo (el módulo es reservado) 5 aditivo 6 desplazamiento de bits(reservado) 7 relacional 8 igualdad 9 y de bit (reservado) 10 o exclusiva de bit (reservado) 11 o inclusiva de bit (reservado) 12 y lógica 13 o exclusiva 14 O inclusiva 15 selección 16 asignaciones aritméticas (el módulo, el desplazamiento y los de bit están reservados) 17(la menor) secuencia Operadores () [] () . ++ -- Asociatividad Ninguna de izquierda a derecha ++ -+-~! */% de derecha a izquierda +<< >> < > <= >= == != & ^ | && ^^ || ?: = += -= *= /= %= <<= >>= &= ^= |= , de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de izquierda a derecha de derecha a izquierda de derecha a izquierda de izquierda a derecha de izquierda a derecha No hay un operador de dirección ni un operador de desreferenciado. Tampoco hay operador de conversión de tipos; se usan los constructores en su lugar. 5.2. Operaciones de Matrices Éstas están descritas en la sección 5.7 “Operaciones de Estructuras y Matrices”. 27 5.3. Llamadas de Funciones Si una función devuelve un valor, entonces la llamada de la función podrá ser usada como una expresión, cuyo tipo será el usado para declarar o definir la función. Las definiciones de funciones y las convenciones de llamada se explican en la sección 6.1 “Definiciones de Funciones”. 5.4. Constructores Los constructores usan la sintaxis de las funciones de llamada, donde el nombre de la función es un tipo y la llamada hace al objeto de ése tipo. Los constructores pueden usarse en expresiones y en inicializaciones. (ver la sección 9 “Gramática del Lenguaje de Shading” para más detalles). Los parámetros son utilizados para inicializar el valor construido. Los constructores pueden ser usados para convertir un tipo escalar a otro tipo escalar, o para construir tipos de mayor rango a partir de tipos de menor rango, o reducir un tipo de mayor rango a uno de menor rango. En general, los constructores no son funciones predefinidas con prototipos determinados. Para las matrices y las estructuras debe haber exactamente un argumento por cada elemento o campo. Para los demás tipos los argumentos deben de dar un número suficiente de elementos para realizar la inicialización, y es un error incluír más argumentos de los que pueden ser usados. Las reglas se detallan a continuación. Los prototipos actualmente citados abajo son sólo un subconjunto de ejemplos. 5.4.1. Conversión y Constructores Escalares La conversión entre diferentes tipos escalares se hace como los siguientes ejemplos indican: int(bool); // convierte un valor booleano en un entero int(float); // convierte un valor flotante en un entero float(bool); // convierte un valor booleano en un flotante float(int); // convierte un valor entero en un flotante bool(int); // convierte un valor entero en un booleano bool(float); // convierte un valor flotante en un booleano Cuando los constructores se utilizan para convertir un flotante a un entero la parte fraccional del valor en coma flotante se desprecia. Cuando un constructor se utiliza para convertir un entero o un flotante en un booleano, 0 y 0.0 son convertidos a false y cualquier otro valor es convertido a true. Cuando un constructor se usa para convertir un booleano a un entero o a un flotante false es convertido a 0 o 0.0 y true es convertido a 1 o 1.0. Los constructores identidad, como float(float) también se permiten, aunque no tienen utilidad. Los constructores escalares sin parámetros escalares pueden usarse para tomar el primer elemento de un tipo no escalar. Por ejemplo, el constructor float(vec3) seleccionará el primer elemento del parámetro vec3. 5.4.2. Vectores y matrices de constructores. Estos pueden ser usados para crear vectores o matrices a partir de un conjunto de escalares, vectores y matrices. Esto incluye la capacidad de reducir el tamaño de vectores. 28 Si hay un parámetro escalar para convertirlo en un vector, inicializaremos todos los componentes del constructor del vector con sus valores escalares. Por otro lado si queremos conseguir una matriz se inicializará la diagonal de la matriz y el resto de valores quedarán a cero. Si un vector es construido por múltiples escalares, vectores, matrices o una mezcla de estos, los componentes del vector serán construidos en el mismo orden que los argumentos. Los argumentos se leerán de izquierda a derecha y se trabajará cada argumento y no se comenzará con el siguiente hasta acabar con el anterior. De forma similar se construye una matriz a partir de escalares y vectores. Los componentes de la matriz serán construidos y trabajados por columnas. En este caso debería haber la condición de que el argumento fuese inicializado para todos los valores del constructor. Es un error considerar más argumentos de los necesarios más allá del argumento usado. Si una matriz es construida a partir de otra matriz, entonces cada componente (columna i, fila j) en el resultado se corresponde con otro par i,j. de los valores de inicialización. Los otros componentes serán inicializados a la matriz identidad. Si dan a un argumento de la matriz un constructor de la matriz, es un error tener cualquier otro argumento. Si el tipo básico (boolean, entero, o decimal) de un parámetro de un constructor no empareja el tipo básico del objeto construido, las reglas de construcción escalares son usadas para convertir los parámetros. Estos son algunos usos del constructor de vectores: vec3(float) // Inicializa cada componente con el decimal vec4(ivec4) // Convierte un ivec4 a u vec4 vec2(float, float) // Incializa vec2 con 2 decimales ivec3(int, int, int) // Incializa un ivec3 con 3 enteros bvec4(int, int, float, float) // Convierte 4 valores a un vector de boolean vec2(vec3) // Elimina la tercera componente del vec3 vec3(vec4) // Elimina la cuarta componente del vec4 vec3(vec2, float) // vec3.x = vec2.x, vec3.y = vec2.y, vec3.z = decimal vec3(float, vec2) // vec3.x = decimal, vec3.y = vec2.x, vec3.z = vec2.y vec4(vec3, float) vec4(float, vec3) vec4(vec2, vec2) Algunos ejemplos de esto son: vec4 color = vec4(0.0, 1.0, 0.0, 1.0); vec4 rgba = vec4(1.0); // Pone cada componente a 1.0 vec3 rgb = vec3(color); // Elimina la cuarta componente Para inicializar los elementos de la diagonal de una matriz dejando los otros a cero: mat2(float) mat3(float) mat4(float) Para inicializar una matriz a partir de vectores o escalares especificos, los componentes se asignan a los elementos de la matriz por columnas: mat2(vec2, vec2); // Una columna por argumento mat3(vec3, vec3, vec3); // Una columna por argumento mat4(vec4, vec4, vec4, vec4); // Una columna por argumento mat3x2(vec2, vec2, vec2); // Una columna por argumento mat2(float, float, // Primera columna float, float); // Segunda columna mat3(float, float, float, // Primera columna float, float, float, // Segunda columna 29 float, float, float); // tercera columna mat4(float, float, float, float, // Primera columna float, float, float, float, // segunda columna float, float, float, float, // tercera columna float, float, float, float); // Cuarta columna mat2x3(vec2, float, // primera columna vec2, float); // segunda columna Exite una amplia gama de posibilidades, para construir una matriz a partir de vectores y escalares, tan larga como componentes son necesarios para inicializarla Para construir una matriz a partir de una matriz: mat3x3(mat4x4); // Escoge la matriz superior izquierda de 3x3 de la mat4x4 mat2x3(mat4x2); // Escoge la matriz superior izquierda de 2x2 de la mat4x4, ultima fila a 0,0 mat4x4(mat3x3); // coloca la mat3x3 en la parte superior izquierda, pone el componente inferior derecho a 1, y el resto a 0 5.4.3. Constructor de estructuras Una vez que una estructura es definida, y dan a su tipo un nombre, un constructor está disponible con el mismo nombre para construir dicha estructura. Por ejemplo: struct luz { float intensidad; vec3 posicion; }; luz luzVariable = luz(3.0, vec3(1.0, 2.0, 3.0)); Los argumentos del constructor serán usados para poner los campos de la estructura, usando un argumento por campo. Cada argumento debe ser del mismo tipo que el campo que este pone, o ser un tipo que puede ser convertido al tipo del campo según la Sección 4.1.10" Conversiones Implícitas. " Los constructores de estructuras pueden ser usados como inicializadores o en expresiones. 5.4.4. Constructor de arrays El tipo array puede usarse como nombre de constructor, el cual tambien puede usarse en expresiones o inicializaciones. Por ejemplo: const float c[3] = float[3](5.0, 7.2, 1.1); const float d[3] = float[](5.0, 7.2, 1.1); float g; ... float a[5] = float[5](g, 1, g, 2.3, g); float b[3]; b = float[3](g, g + 1.0, g + 2.0); Debe haber exactamente el mismo numero de argumentos como tamaño del array al ser construido. Si el tamaño no esta presente en el constructor, entonces el array tendrá como tamaño el numero de argumentos. Los argumentos son asignados en orden, comenzando por el cero hasta el final del array. Cada argumento debe ser del mismo tipo que los elementos del array, o ser de un tipo que pueda ser convertido de acuerdo con la sección: 4.1.10 “Conversiones implicitas” 30 5.5. Componentes de los vectores Los nombres de los componentes de un vector se denotan por una letra minúscula. Por convenio, se entiende que las letras mayúsculas se asocian con otro componente relacionados con posiciones, colores y texturas. Se puede acceder a la componente de un vector siguiendo el nombre de la variable con un punto y el nombre de la componente. Estos son los nombres para las componentes: {x,y,z,w} {r,g,b,a} {s,t,p,q} Acceso a vectores que representan puntos o normales Vectores que representan colores Vectores que representan texturas Los nombres de las componentes x,r, y s son, por ejemplo, sinominos para referirnos a la primera componente. Notese que la tercera componente de las texturas, en OpenGL, ha sido renombra como p para evitar la confusion con r (de rojo) en el color. Es un error acceder a componentes que van más allá de las que tenemos declarados, por ejemplo: vec2 pos; pos.x // is legal pos.z // is illegal La sintaxis para seleccionar componentes permite que multiples componentes sean selecionadas añadiendo sus nombres (desde la misma seleccion) después del punto( . ). vec4 v4; v4.rgba; // es un vec4 y es como usar como v4, v4.rgb; // es un vec3, v4.b; // es un decimal, v4.xy; // es un vec2, v4.xgba; // es un error- El nombre de la componente no pertenece al mismo conjunto El orden de las componentes puede ser diferente o incluso estar replicados: vec4 pos = vec4(1.0, 2.0, 3.0, 4.0); vec4 swiz= pos.wzyx; // swiz = (4.0, 3.0, 2.0, 1.0) vec4 dup = pos.xxyy; // dup = (1.0, 1.0, 2.0, 2.0) Esta notación es más concisa que la sintaxis de constructor. Para formar un valor de r, puede ser aplicado a cualquier expresión que causa un valor de r. La notación de grupo componente puede aparecer al lado izquierdo de una expresión. vec4 pos = vec4(1.0, 2.0, 3.0, 4.0); pos.xw = vec2(5.0, 6.0); // pos = (5.0, 2.0, 3.0, 6.0) pos.wx = vec2(7.0, 8.0); // pos = (8.0, 2.0, 3.0, 7.0) pos.xx = vec2(3.0, 4.0); // Error - 'x' usado dos veces pos.xy = vec3(1.0, 2.0, 3.0); // Error- Desajuste entre un vec2 y un vec3 Para formar un valor de l, debe ser aplicado a un valor de l del tipo de vector, no contener ningunos componentes dobles, y esto causa un valor de l escalar o del tipo de vector, dependiendo del número de componentes especificados. La sintaxis de arrays puede también ser aplicada a vectores con índices numéricos: vec4 pos; pos[2] se refiere al tercer elemento de pos y equivale a pos.z. Esto permite que las variables sean indexadas en un vector, como un acceso generico. Cualquier expresión de enteros puede ser usada. 31 La primera componente es el indice 0. Leyendo o escribiendo en un vector usando cualquier expresión que tenga un valor negativo o mayor que el tamaño del array produce un error. Cuando indexamos con expresiones no constantes, su comportamiento no esta definido cuando el valor es negativo o mayor que el tamaño del vector. 5.6. Elementos de matrices Los elementos de las matrices pueden ser accedidos usando la sintaxis de los arrays. Aplicando un subíndice a una matriz considera la matriz como un conjunto de columnas, y selecciona solo una, cuyo tipo es un vector del mismo tamaño de la matriz. La columna de más a la izquierda es la columna 0. Un segundo índice actuaría sobre la columna,. Entonces, dos subíndices seleccionan uno las columnas y otro la fila. mat4 m; m[1] = vec4(2.0); // Incializa la segunda columna con valor 2. m[0][0] = 1.0; // Elemento superior izquierda valor 1.0 m[2][3] = 2.0; // Cuarto elemento de la segunda columna con valor 2.0 El comportamiento se considera indefinido cuando se accede a un elemento que esta fuera de los limites de la matriz con una expresión no constante. Es un error acceder a un elemento de una matriz que esta fuera de los limites de esta con una expresión constante 5.7. Operaciones con arrays y estructuras Los campos de un estructura y el procedimiento “length” de los arrays se llaman usando un punto ( .) En total, solo los siguientes operadores son usados para operar con arrays y estructuras: Seccionar un campo Igualdad Asignación Acceso con índices(solo arrays) . == != = [] Los operadores de asignación y igualdad son solo permitidos si los dos operandos tienen el mismo tamaño y son del mismo tipo. Las tipos de estructuras deben ser de la misma declaración de la estructura. Los dos operadores en los arrays deben tener el mismo tamaño explícito. Cuando usamos el operador de igualdad, dos estructuras son iguales si y solo si todos los campos son exactamente iguales, y dos arrays son iguales si y solo si, todos los elementos del array son exactamente iguales. Los elementos de los arrays son accedidos usando el operador []. Un ejemplo de acceso en un array es: difusseColor+= IntensidadLuz[3] * NdotL; El índice de los arrays empieza en 0. El acceso a los elementos usa un expresión que trabaja con tipos enteros. El comportamiento es indefinido cuando el índice vinculado a un array es menor que 0 o mayor o igual que el tamaño con el que fue declarado. Los arrays pueden ser accedidos también con el operador ( .) y el método “length” para conocer la longitud del array. IntensidadLuz.length() // devuelve el tamaño del array 32 5.8. Asignaciones La asignación de valores a nombres de variables son hechos con el operador ( = ), como: lvalor=expresión El operador de asignación almacena el valor de la expresión en la variable lvalor. Expresión y lvalor deben ser del mismo tipo, o la expresión debe tener su tipo en la sección 4.1.10 “Conversiones implícitas” para convertir al tipo de lvalor. En este caso la conversión implícita debe realizarse antes de la asignación. Cualquier otro tipo de conversión deseada debe ser especificado en el constructor. Lvalor tiene que ser modificable. Las variables que se crean a partir de tipos, estructuras o arrays, campos de estructuras, lvalor con el selector de campos (.) aplicado para seleccionar componentes sin campos repetidos, lvalor con paréntesis y lvalor referenciado con el subíndice del array ([]). Otras expresiones binarias, nombres de funciones, y constantes que no pueden ser l-valor. El operador ternario (?: tan bien se permite como un l-valor. La expresión que esta a la izquierda de la asignación es evaluada antes de la expresión de la derecha de la asignación. Otros operadores de asignación son: La asignación aritmética de suma ( += ), resta ( -= ), multiplicación ( *= ) y división ( /= ). La expresión lvalor op=expresión Equivale a la expresión: lvalor = lvalor op expresión; y lvalor y la expresión deben satisfacer los requisitos en la semántica tanto para op como para igualdad. La asignación en conjuntos, desplazamiento a la derecha (>>=), desplazamiento a la izquierda (<<=), perteneciente(incluido) a ( |= ), y exclusivo ( ^=) son reservados para mas adelante. Leer una variable antes de escribirla ( o inicializarla) esta permitido, sin embargo su valor es indefinido. 5.9. Expresiones Las expresiones en el lenguaje de shading se crean a partir de las siguiente: Constantes de tipo bolean, enteras o decimales, todo tipo de vectores y todo tipo de matrices Constructores de todo tipo Nombres de variables de todo tipo El nombre de un array con el método para obtener su longitud. Nombres de subíndices de arrays Llamadas a funciones que devuelven un valor. Campos seleccionados y resultados de acceso a arrays por subíndice. Expresiones entre paréntesis. Algunas expresiones se pueden expresar entre paréntesis. Los paréntesis se usan para agrupar operaciones. Las operaciones entre paréntesis se realizan con prioridad frente a las que no los llevan. La aritmética de operadores binarios, suma (+), resta ( - ), multiplicación (*) y división ( / ) operan con enteros y escalares en punto flotante, vectores y matrices. Si uno de los operadores esta en punto flotante y el otro no, entonces se aplican las conversiones de la sección 4.1.10 “conversión implícita” al operando no decimal. Todas las operaciones binarias tienen como resultado un número en punto flotante o entero. La operación se realiza después de la conversión de tipos. Tras la conversión se pueden dar los siguientes casos: 33 Los dos elementos sean escalares. En este caso la operación se realiza, resultado: escalar. o Si un operando es escalar y el otro es un vector o una matriz, Se realiza la operación escalar independientemente a cada elemento de la matriz o del vector. resultado: Vector o matriz con el mismo tamaño. o Si los dos son vectores del mismo tamaño entonces la operación se realiza cada elemento con su homologo en el otro vector (elemento a elemento) Resultado: Vector del mismo tamaño. o Si la operación es suma, o resta y trabajamos con dos matrices del mismo tamaño entonces la operación se realiza elemento a elemento dando como resultado otra matriz del mismo tamaño. o Con la operación de multiplicar, siendo ambos operadores matrices o vectores. El operando de la derecha se considera un vector columna y el de la izquierda como un vector fila. En todo caso, es necesario que el numero de columnas del operando de la izquierda sea el mismo que el numero de filas del operando de la derecha. De esta forma la multiplicación realiza una multiplicación algebraica lineal, obteniéndose como resultado un objeto que va a tener el mismo numero de filas que el elemento de la izquierda y las mismas columnas que el elemento de la derecha. En la sección 5.10 “Operaciones con vectores y matrices” se explica con mas detalle con se operan estos. Todos los otros casos son erróneos. o La división por cero no produce una excepción pero el resultado da un valor indefinido. Usar las funciones definidas: dot, cross, matrixCompMult y outerProduct, para conseguir, respectivamente, producto ‘dot’ de vectores, producto ‘cross’ de vectores, multiplicación elemento a elemento de matrices, y el producto de matrices de una columna por una fila. La operación modulo (%) se reserva para mas adelante. La operación unitaria de cambio de signo ( - ), la de incrementar o decrementar (++ , --) funcionan tanto para enteros como para decimales(incluyendo vectores y matrices)Todos los operadores unitarios trabajan elemento a elemento. El resultado mantiene el mismo tipo que el operando. Para incrementar y decrementar, la expresión tiene que ser un valor que se pueda asignar a un l-valor. Pre-incrementar y pre-decrementar sumando o restando 1 o 1.0 al contenido de la expresión que ellos operan, y el valor del incremento o decremento es el valor resultante de la modificación. Otra opción es incrementar o decrementar el valor después de realizar la operación, pero el resultado de la expresión es el valor resultante antes de ejecutar el incremento o decremento. Las operaciones de comparación, mayor que (>). menor que (<), mayor o igual que (>=) y menor o igual que (<=) solo operan con escalares enteros o punto flotante. El resultado es un escalar boolean. Los tipos de los operandos deben hacer coincidir, o las conversiones de la Sección 4.1.10" Conversiones Implícitas " serán aplicadas al operando entero, para que después los tipos deben coincidir. Para realizar una comparación elemento a elemento en un vector se puede usar las funciones predefinidas lessThan, lessThanEqual,greaterThan y greaterThanEqual. El operador de igualdad(==) y el de desigualdad(!=) operan con cualquier tipo de dato. El resultado se da en un booleano escalar. Si los tipos no coinciden entonces se debería aplicar una conversión de la sección 4.1.10, aplicándolo en un operando que pueda hacer coincidir, en este caso la conversión se realiza. para vectores, matrices, estructuras y arrays todos los elementos o campos de un operando deben tener la misma correspondencia con el elemento o campo del otro operando para ser considerados iguales. Para 34 obtener un vector de igualdad elemento a elemento de dos vectores usar la funcion equal y notEqual. El operador binario and(&&), or (||) y el or exclusivo (^^) solo operan con expresiones booleanas y tiene como resultado una expresión bolean. And(&&) solo será evaluado la parte derecha de la operación si la parte izquierda tiene un valor trae. Or (||) solo será evaluada la parte derecha de la operación si la parte izquierda de esta tiene un false. El or exclusivo siempre son evaluadas ambas partes. El operador lógico not(!). Este operador solo funciona en expresiones booleanas y tiene como resultado otra. para usarlo en vectores hay que utilizar la función predefinida not La secuencia ( , )opera en expresiones que devuelven el tipo y el valor de desde la parte mas a la derecha en la lista separada por comas. Todas las expresiones son evaluadas, en orden, de izquierda a derecha. La operación ternaria (?:) Esta operación se aplica a tres expresiones (exp1 ? exp2 : exp3). Evalua la primera expresión, cuyo resultado debería ser un valor boolean, Si es cierto, pasa a evaluar la segunda expresión, en otro caso evalua la tercera. Solo una de la segunda y tercera expresión es evaluada. La segunda y tercera expresión puede ser de cualquier tipo, pudiendo hacer falta una conversión de la seccion 4.1.10. El tipo del resultado obtenido es el tipo de toda la expresión. El operador and(&), or(|) or exclusivo(^),not, desplazamiento a la derecha (>>) , desplazamiento a la izquierda (<<). Estos son reservados para mas adelante. Para una completa espeficicacion de la sintaxis de expresiones ver la seccion 9 “Gramatica del lenguaje Shading” 5.10. Operaciones con vectores y matrices Con unas pocas es excepciones, las operaciones son elemento a elemento. Normalmente, cuando una operación se realiza sobe un vector o matriz, se opera independientemente con cada elemento de la matriz o vector. Por ejemplo: vec3 v,u; float f; v = u +f; Equivaldría a: v.x = u.x + f; v.y = u.y + f; v.z = u.z + f; y vec3 v, u, w; w = v + u; Equivaldría a: w.x = v.x + u.x; w.y = v.y + u.y; w.z = v.z + u.z; de la misma forma para mas operaciones con enteros y decimales. La excepcion es la multiplicación de un vector por una matriz, o una matriz por un vector, o producto de matrices. No se evalúa elemento a elemento, se realiza una multiplicación algebraica lineal: 35 vec3 v, u; mat3 m; u = v * m; Equivale: u.x = dot(v, m[0]); // m[0] columna izquierda de m u.y = dot(v, m[1]); // dot(a,b) es el producto entre a y b u.z = dot(v, m[2]); u = m * v; Equivale: u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; y mat3 m, n, r; r = m * n; Equivale: r[0].x = m[0].x * n[0].x + m[1].x * n[0].y + m[2].x * n[0].z; r[1].x = m[0].x * n[1].x + m[1].x * n[1].y + m[2].x * n[1].z; r[2].x = m[0].x * n[2].x + m[1].x * n[2].y + m[2].x * n[2].z; r[0].y = m[0].y * n[0].x + m[1].y * n[0].y + m[2].y * n[0].z; r[1].y = m[0].y * n[1].x + m[1].y * n[1].y + m[2].y * n[1].z; r[2].y = m[0].y * n[2].x + m[1].y * n[2].y + m[2].y * n[2].z; r[0].z = m[0].z * n[0].x + m[1].z * n[0].y + m[2].z * n[0].z; r[1].z = m[0].z * n[1].x + m[1].z * n[1].y + m[2].z * n[1].z; r[2].z = m[0].z * n[2].x + m[1].z * n[2].y + m[2].z * n[2].z; De forma similar para otros tamaños de matrices y vectores. 6. Sentencias y estructuras Los bloques usados principalmente en el lenguaje de Shading en OpenGL son: Estados y declaraciones Definición de funciones Selecciones (if – else) Bucles (for, while, y do-while) saltos (discard,return,break y continue) En general, las estructuras de un shader son como sigue: Unidad-translación: Declaración global Unidad de translación declaración global Declaración global Definición de la función Declaración Es decir, un shader es una secuencia de declaraciones y cuerpos de funciones. Los cuerpos de las funciones se definen así: 36 Definición de la función: Función prototipo {lista de Sentencia } Lista de Sentencia: Sentencia Lista de Sentencia Sentencia Sentencia Sentencia compuesto Sentencia simple Los lazos son usados en las secuencias de grupo de estados en estados compuestos. Sentencia compuesto {lista de Sentencia } Sentencia simple: declaracion Expresión Selección Bucle Salto La declaración simple, la expresión, y las sentencias de salto acaban en un punto y coma. Esto es ligeramente simplificado, y la gramática completa especificada en la Sección 9 " Gramática del lenguaje Shading " debería ser usada como la especificación definitiva. Las declaraciones y expresiones ya han sido comentadas. 6.1 Definición de funciones Como indica la gramática, un shader valido es una secuencia de sentencias globales y definición de funciones. Una función es declarada como muestran los siguientes ejemplos: // prototipo DevuelveTipo nombreFuncion (tipo0 arg0, …, tipoN argN); y una función se define: // definición tipoDevuelve nombreFuncion ((tipo0 arg0, …, tipoN argN); { // hace algun computo return ValorDevuelto; } Donde indica TipoDevuelto debe incluirse un tipo de dato. Cada uno de los tipoN deben incluir un tipo y pueden opcionalmente incluir un parámetro constante o variable. Una función se llama usando el nombre seguido de una lista de parámetros entre paréntesis. Los arrays pueden ser argumentos y también un tipo de dato devuelto. En ambos casos, el array debe tener su tamaño especificado. Un array se pasa o se devuelve usando su nombre, sin corchetes y su tamaño debe estar especificado en la definición de la función. Las estructuras también se permiten como argumentos. También se puede devolver una estructura. 37 Ver la sección 9.”Gramtica del lenguaje Shading para referencias concretas en la sintaxis de definición y declaración de funciones. Todas las funciones deben ser declaradas con un prototipo o definidas con un cuerpo antes de que les llamen. Por ejemplo: float mifunc (float f, // f parametro de entrada out float g); // g parametro de salidar Las funciones que no devuelven ningún valor deben ser declaradas como vacío. Las funciones que no aceptan ninguno argumento de entrada no tienen que usar vacío en la lista de argumento porque requieren prototipos (o definiciones) y por lo tanto no hay ninguna ambigüedad cuando una lista de argumento vacía " () " es declarada. Proporcionan " (vacío) " como una lista de parámetro por convenio. Los nombres de función pueden ser sobrecargados. Esto permite que el mismo nombre de una función puede usarse para múltiples funciones, siempre que tengan la lista de argumentos distintas. Si coinciden el nombre de la función y los argumentos, entonces su tipo devuelto también coincidirá. Ningunos calificadores son incluidos comprobando si existe coincidencia de tipos, la función correspondiente está basado sólo en el parámetro que escriben. También se rehúsan nombres para las funciones predefinidas. Cuando se produce una llamada, se busca una coincidencia en el parámetro formal de la función escrita. Esto incluye que también tiene que coincidir el tamaño de un array. Si se encuentra esta coincidencia, la otra definición se ignora y la coincidente se usa. En otro caso, si no se encuentra una igual, entonces se aplicarán las conversiones implícitas de la sección 4.1.10, para los argumentos de llamada si puede hacer que coincidan los tipos. En este caso, es un error de semántica si existen múltiples caminos para que al aplicar la conversión de los argumentos actuales puedan ir por varias definiciones de la función. Por ejemplo, la función predefinida para el producto escalar tiene los siguiente prototipos: float dot (float x, float y); float dot (vec2 x, vec2 y); float dot (vec3 x, vec3 y); float dot (vec4 x, vec4 y); Las definiciones de los usuarios pueden tener varias declaraciones pero solo una definición. Un Shader puede redefinirse con funciones predefinidas. Si una función predefinida se vuelve a declarar en un shader (i.e. un prototipo es visible) antes de llamarlo, entonces el enlace solo se intentara resolver en el conjunto de los shaders que estén dentro de este. la función main (principal) se usa como el punto de comienzo de un shader ejecutable. Un shader no necesita contener una funcion llamada main pero uno dentro del conjunto de shaders tiene que enlazarse con un shader ejecutable. La función no coge argumentos, no devuelve un valor, y se debe declarar con el tipo vacio (void). void main() { … } La función main puede contener usos de return. Ver la sección 6.4. “Saltos” para más detalle. Es un error declarar o definir una función main con otro tipo de nombre o tipo. 6.1.1. Convenio de llamadas a funciones Las funciones se llaman por el valor que devuelven. Esto significa que los argumentos de entrada se copian dentro de la función al llamarla, y los argumentos de salida se copian al que los llamo antes de salir de la función. La función trabaja con copias locales de los parámetros, así no hay solapamiento de los valores de las variables dentro de una función. Cuando se llama, los 38 argumentos de entrada son evaluados en orden, de izquierda a derecha. Sin embargo no esta definido cual es el orden en el que se copian los argumentos de salida. Para controlar que los parámetros son copiados en y/o hacia fuera por una definición de función o la declaración: La palabra clave IN se usa para denotar que un parámetro ha sido copiado dentro pero no fuera. La palabra clave OUT se usa para denotar si un parámetro ha sido copiado fuera pero no dentro. Esto se debería usar siempre para evitar copias innecesarias. La palabra clave INOUT se usa para denotar si un parámetro ha sido copiado tanto dentro como fuera. Un parámetro de una función declarado sin tal calificador significa lo mismo que si fuese declarado con in. En una función, se puede escribir en parámetros de entrada. Solo las copias de la función son modificadas. Esto se puede prevenir declarando los parámetros como constantes. Cuando llamamos a una función, las expresiones que no evalúa un l-valor no pueden ser pasadas como parámetros declarados como out o inout No se permite calificar al tipo que devuelve la función. funcion prototipo : tipo nombreFunc(const-qualifier parameter-qualifier type name array-specifier, ... ) type : cualquier tipo basico, tipo array, nombre estructura, o definición de estructura const-qualifier : vacio const parameter-qualifier : vacio in out inout nombre : vacio identificador array-specifier : vacio [ entero-constante-expresion] Sin embargo, el calificador const no se puede usar con out o inout. Es usado para declarar funciones (excepto prototipos) y definir funciones. De ahí que la definición de funciones puede tener argumentos sin nombres. La recursividad no esta permitida, no estáticamente. La recursividad estática está presente si el gráfico de llamada de función estático del programa contiene ciclos. 6.2 Selección La estructura condicional en el lenguaje GSLS se consigue mediante if o if-else: if ( exp-booleana ) código para exp-booleana verdadera o if ( exp-booleana ) código para exp-booleana verdadera 39 else código para exp-booleana falsa Si la expresión booleana se evalúa como verdadera el código correspondiente será ejecutado. Por el contrario, si la evaluación es falsa solo se ejecutará el código correspondiente a la rama del else si este existese. Cualquier expresión cuyo resultado después de evaluada sea un valor booleano puede ser usado como condición en esta estructura. No se permite el tipo Vector como expresión booleana. Las sentencias condicionales pueden ser anidadas. 6.3 Iteración Los bucles For, While y Do se construyen como sigue: for ( exp-comienzo; exp-condicion; exp-bucle ) código iterativo while ( exp-condicion ) código iterativo do código iterativo while ( exp-condicion ) Mirar sección 9 “Gramática GSLS” para la especificación definitiva de los bucles. Primero, el bucle for evalúa la exp-comienzo, luego evalúa exp-condición. Si la expcondición resulta verdadera, se ejecuta el cuerpo del bucle. Después de que este sea ejecutado, el bucle for evaluara la exp-bucle, y luego volverá a comenzar hasta que expcondición sea falso. Cuando esto sucede, el bucle finaliza pasando por el cuerpo del mismo sin ejecutarlo. Las variables modificadas en la exp-bucle mantienen su valor una vez se finaliza la ejecución del bucle. Por el contrario, las variables modificadas en la expcomienzo o en la exp-condición solo se mantienen mientras se ejecuta el cuerpo del bucle for. El bucle while evalúa primero la exp-condición. Si es verdadera, el cuerpo es ejecutado. Este se repite hasta que la exp-condición es falsa, saliendo del bucle. Las variables declaradas en el cuerpo de un bucle while son únicamente válidas durante la ejecución del mismo. El bucle do-while ejecuta inicialmente el cuerpo del bucle para luego comproba la expcondición. Esto se repite hasta que la expresión es falsa, momento en el que el bucle finaliza. El resultado de evaluar exp-condición debe ser un booleano. Exp-condición y exp-comienzo pueden ser utilizadas para declarar una variable, excepto en el bucle do-while, que no puede declararlas en su exp-condición. Estas variables tienen validez hasta la última instrucción del cuerpo del bucle. Estos tres tipos de bucles pueden ser anidados. 40 Los bucles infinitos están permitidos y sus consecuencias son dependientes de la plataforma sobre la que se ejecuten. 6.4 Saltos Su expresión es: jump_stat: continue; break; return; return expresión; discard; // solo en el lenguaje fragment shader No existe el “goto” ni ningún tipo de control no estructurado como en otros leguajes. Los saltos continue son únicamente utilizados en bucles. Su función es la de evitar el cuerpo restante del bucle en el que está incluido. Para bucles while y do-while, este salto lleva al programa a la siguiente evaluación de las exp-condición de los bucles. En bucles for, el salto es a la exp-bucle, seguida de la exp-condición. El salto break solo puede ser usado en bucles. Cuando se encuentra un break en un bucle, este finaliza directamente sin ningún tipo de comprobación de sus expresiones. La palabra clave discard esta únicamente permitida en fragment shaders. Puede ser usado para abandonar la operación actual en el actual fragmento. Esto provoca el descarte del fragmento y consecuentemente la no actualización de cualquier buffer. Típicamente, discard es utilizado con algún tipo de construcción condicional del tipo: if ( intensity < 0.0) discard; Un fragment shader puede testear el valor alpha y descartarse basándose en este test. Por el contrario, debemos indicar que el test de cobertura se produce después que el fragmento sea ejecutado, y este puede cambiar el valor alpha. El salto return produce la salida inmediata de la función actual. Si contiene una expresión, esta fijará el valor devuelto por la función. La función main puede utilizar el return. Esto produce la salida del programa de la misma forma en que lo haría una función que ha alcanzado su final. Esto no implica el uso de discard en un fragment shader. El uso de return en el main antes de definir las salidas tendrá el mismo comportamiento que alcanzar el final del main sin haberlas definido. 41 7 Variables Internas 7.1 Variables especiales en Vertex Shaders Algunas operaciones en OpenGL continúan manteniendo su operatividad entre el procesador de vértices y el de fragmentos. Otras, por el contrario, suceden después del procesador de fragmentos. Los shaders se comunican con OpenGL a través de sus variables. La variable gl_Position está disponible únicamente en el Vertex Language y su función es la de describir homogéneamente la posición de los vértices. Toda ejecución de un vertex shader bien formado tiene necesariamente que escribir un valor en esta variable. Puede ser escrito en cualquier momento durante la ejecución del mismo. También podría ser escrito por un vertex shader después de que este fuese escrito. Este valor será usado en la fase primitiva de ensamblado, clipping, culling y otras operaciones funcionales que operan sobre las primitivas luego de que el procesador del vértice suceda. El compilador podría generar un diagnostico de error si detecta que la variable no ha sido escrita, o ha sido accedida para lectura antes de ser cubierta, pero no todos los casos son detectables. Su valor será indefinido si el vertex shader se ejecuta y no cubre su valor. La variable gl_PointSize está disponible únicamente en el Vertex Language y su función primitiva es la de especificar el tamaño de punto para su rasterización. Se mide en pixels. La variable gl_ClipVertex está también únicamente disponible en el Vertex Language y nos provee de un lugar en el que los shaders podrán escribir las coordenadas que luego serán utilizadas en los planes de recorte del usuario. El usuario debe asegurarse que el recorte del vértice y el propio vértice se encuentren en el mismo espacio de coordenadas. Estos planes del usuario solo serán operativos mediante transformaciones lineales. No se puede asegurar lo que ocurrirá en caso de ser no lineales. Las variables de comunicación anteriores son declaradas intrínsecamente de la forma: vec4 gl_Position; float gl_PointSize; vec4 gl_ClipVertex; // tiene que ser escrita // debe ser escrita // debe ser escrita Si gl_PointSize o gl_ClipVertex no son escritas, sus valores son indefinidos. Cualquiera de estas variables podrán ser accedidas por el shader después de escribir en él, para recuperar el valor de las mismas. Hacer esto antes de ser escrito tendrá un comportamiento no definido. En caso de escribir las variables múltiples veces, el último valor será el que se almacene. Estas variables tienen una ámbito global. 7.2 Variables especiales en Fragment Shaders La salida de la ejecución de un fragment shader será procesada en el final del pipeline de OpenGL. Las salidas de los fragment shaders serán comunicadas al pipeline mediante las variables gl_FragColor, gl_FragData y gl_FragDepth, a menos que se ejecute un discard. 42 Estas variables podrían ser escritas más de una vez. Si esto ocurre, el último valor asignado será el llevado al pipeline. Los valores dados a las variables podrán ser accedidos después de cubiertos. La lectura previa a la asignación devolverá valores indefinidos. Para asignar un valor a la variable de profundidad gl_FragDepth este valor debe ser obtenido mediante la lectura de la variable gl_FragCoord.z, descrita más abajo. La variable gl_FragColor establece el color que será utilizado a la hora de establecer el color al fragmento en el pipeline de OpenGL. Una vez este valor se consume a la hora de “pintar” el fragmento, y si otro código de ejecución del shader no actualiza el valor de esta variable, este quedará indefinido. Si el frame buffer está configurado como un index color buffer el comportamiento es indefinido cuando usamos un fragment shader. gl_FragDepth establecerá el valor de profundidad para el shader que se ejecute en ese momento. Si el buffer de profundidad esta activado, y el shader no cubre la variable descrita, el valor actual de la variable(proveniente de otro código no perteneciente al shader)será utilizado como valor de profundidad. Si un shader de forma estática asigna un valor a gl_FragDepth, y hay un camino de ejecución a través del shader que no fija el valor de la variable, entonces el valor de la profundidad será indefinido para ejecuciones que recorran dicho camino. (Un shader contiene una asignación estática a una variable x si, antes del pre-procesado, el shader contiene una sentencia que escribiría a x, que se ejecutará independientemente de si la sentencia está en el flujo de ejecución o no) La variable gl_FragData es un array. Escribiendo gl_FragData[n] se especifica el fragmento de datos que será ejecutado en el pipeline para los datos n. Si en esta ejecución se consume el valor del fragmento de datos, esta variable pasa a tener un valor indefinido. Si un shader asigna estáticamente un valor a gl_FragColor, no puede asignar un valor a cada uno de los elementos del array gl_FragData. Igualmente, si asigna un valor a cada uno de los elementos del array gl_FragData, no puede asignarle ningún valor a gl_FragColor. Esto es, un shader solo puede asignar un valor a una de estas dos variables, pero no a ambas. La ejecución de multiples shaders enlazados tiene que ser controlada para que esto no suceda. No puede dejar las variables en estado inconsistente. Si un shader ejecuta la orden discard, el fragmento es descartado, y los valores de las variables anteriormente explicadas es irrelevante. La variable gl_FragCoord es de acceso para solo lectura y su misión es fijar los valores x,y,z y l/w para el fragmento. Si estamos trabajando con un multi-sampling, este valor puede ser para dentro de cualquier pixel, o uno de las muestras de fragmento. El uso de las variaciones de centroid no permite restringir estos valores para estar dentro de la primitiva. Este valor es el resultado de la funcionalidad en OpenGL que interpola las primitivas después del procesado de vértices para generar fragmentos. La componente z es la profundidad que será aplicada al shader si no se ha especificado un valor en la variable gl_FragDepth. Esto es muy útil cuando un shader interpreta gl_FragDepth en algunos casos y en otros lo calcula sin recurrir a la variable. Los fragment shaders tienen acceso a otra variable de solo lectura, gl_FrontFacing. Su valor será verdadero si el fragmento pertenece a una primitiva que está enseñando su cara frontal. Un uso de esto es la emulación de una iluminación de dos caras seleccionando uno o dos colores calculados por el vertex shader. 43 Las variables accesibles desde un fragment shader tienen unos tipos intrínsecos que se detallan a continuación: vec4 bool vec4 vec4 float gl_FragCoord; gl_FrontFacing; gl_FragColor; gl_FragData[gl_MaxDrawBuffers]; gl_FragDepth; Sin embargo, no se comportan como variables de almacenamiento. Su funcinamiento es el descrito anteriormente. Tienen ámbito global. 7.3 Atributos especiales en Vertex Shaders Los siguientes atributos son parte del OpenGL vertex language y pueden ser usados desde un código de este tipo para leer y modificar estas variables de OpenGL. Las referencias a páginas se refieren a la especificación del lenguaje OpenGL 1.4 // // Atributos Vertex, p. 19. // attribute vec4 gl_Color; attribute vec4 gl_SecondaryColor; attribute vec3 gl_Normal; attribute vec4 gl_Vertex; attribute vec4 gl_MultiTexCoord0; attribute vec4 gl_MultiTexCoord1; attribute vec4 gl_MultiTexCoord2; attribute vec4 gl_MultiTexCoord3; attribute vec4 gl_MultiTexCoord4; attribute vec4 gl_MultiTexCoord5; attribute vec4 gl_MultiTexCoord6; attribute vec4 gl_MultiTexCoord7; attribute float gl_FogCoord; 7.4 Constantes especiales Las siguientes constantes son válidas tanto en vertex shaders como fragment shaders // // Constantes dependientes de la implementación. En el ejemplo siguiente, // los valores son los mínimos permitidos para esos máximos // const int gl_MaxLights = 8; // GL 1.0 onst int gl_MaxClipPlanes = 6; // GL 1.0 const int gl_MaxTextureUnits = 2; // GL 1.3 const int gl_MaxTextureCoords = 2; // ARB_fragment_program const int gl_MaxVertexAttribs = 16; // ARB_vertex_shader const int gl_MaxVertexUniformComponents = 512; // ARB_vertex_shader const int gl_MaxVaryingFloats = 32; // ARB_vertex_shader const int gl_MaxVertexTextureImageUnits = 0; // ARB_vertex_shader const int gl_MaxCombinedTextureImageUnits = 2; // ARB_vertex_shader const int gl_MaxTextureImageUnits = 2; // ARB_fragment_shader 44 const int gl_MaxFragmentUniformComponents = 64;// ARB_fragment_shader const int gl_MaxDrawBuffers = 1 // ARB_draw_buffers propuestos 7.5 Variables Uniformes de Estado Como ayuda para acceder al estado de procesado de OpenGL, las siguientes variables han sido incluidas dentro del lenguaje de shaders. Todas las referencias son a la especificación 1.4 del lenguaje. // // Estado de la matriz p. 31, 32, 37, 39, 40. // uniform mat4 gl_ModelViewMatrix; uniform mat4 gl_ProjectionMatrix; uniform mat4 gl_ModelViewProjectionMatrix; uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords]; // // Matriz de estado derivada que devuelve inversas y transpuestas // de las matrices de arriba. Matrices en mal estado pueden resultar en // impredecibles valores de sus matrices inversas. // uniform mat3 gl_NormalMatrix; // transpuesta de la inversa triangular // superior izquierda de la gl_ModelViewMatrix uniform mat4 gl_ModelViewMatrixInverse; uniform mat4 gl_ProjectionMatrixInverse; uniform mat4 gl_ModelViewProjectionMatrixInverse; uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords]; uniform mat4 gl_ModelViewMatrixTranspose; uniform mat4 gl_ProjectionMatrixTranspose; uniform mat4 gl_ModelViewProjectionMatrixTranspose; uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords]; uniform mat4 gl_ModelViewMatrixInverseTranspose; uniform mat4 gl_ProjectionMatrixInverseTranspose; uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose; uniform mat4 gl_TextureMatrixInverseTranspose[gl_MaxTextureCoords]; // // Escalado normal p. 39. // uniform float gl_NormalScale; // // Rango de profundidad en las coordenadas de la ventana p. 33 // struct gl_DepthRangeParameters { float near; // n float far; // f float diff; // f - n }; uniform gl_DepthRangeParameters gl_DepthRange; // // Clip planes p. 42. 45 // uniform vec4 gl_ClipPlane[gl_MaxClipPlanes]; // // Tamaño de punto p. 66, 67. // struct gl_PointParameters { float size; float sizeMin; float sizeMax; float fadeThresholdSize; float distanceConstantAttenuation; float distanceLinearAttenuation; float distanceQuadraticAttenuation; }; uniform gl_PointParameters gl_Point; // // Estado del material p. 50, 55. // struct gl_MaterialParameters { vec4 emission; // Ecm vec4 ambient; // Acm vec4 diffuse; // Dcm vec4 specular; // Scm float shininess; // Srm }; uniform gl_MaterialParameters gl_FrontMaterial; uniform gl_MaterialParameters gl_BackMaterial; // // Estado de las luces p 50, 53, 55. // struct gl_LightSourceParameters { vec4 ambient; // Acli vec4 diffuse; // Dcli vec4 specular; // Scli vec4 position; // Ppli vec4 halfVector; // Derived: Hi vec3 spotDirection; // Sdli float spotExponent; // Srli float spotCutoff; // Crli // (range: [0.0,90.0], 180.0) float spotCosCutoff; // Derived: cos(Crli) // (range: [1.0,0.0],-1.0) float constantAttenuation; // K0 float linearAttenuation; // K1 float quadraticAttenuation; // K2 }; uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights]; struct gl_LightModelParameters { vec4 ambient; // Acs }; uniform gl_LightModelParameters gl_LightModel; // 46 // Estados derivados de los productos de luces y material // struct gl_LightModelProducts { vec4 sceneColor; // Derived. Ecm + Acm * Acs }; uniform gl_LightModelProducts gl_FrontLightModelProduct; uniform gl_LightModelProducts gl_BackLightModelProduct; struct gl_LightProducts { vec4 ambient; // Acm * Acli vec4 diffuse; // Dcm * Dcli vec4 specular; // Scm * Scli }; uniform gl_LightProducts gl_FrontLightProduct[gl_MaxLights]; uniform gl_LightProducts gl_BackLightProduct[gl_MaxLights]; // // Entorno de textura y generation, p. 152, p. 40-42. // uniform vec4 gl_TextureEnvColor[gl_MaxTextureUnits]; uniform vec4 gl_EyePlaneS[gl_MaxTextureCoords]; uniform vec4 gl_EyePlaneT[gl_MaxTextureCoords]; uniform vec4 gl_EyePlaneR[gl_MaxTextureCoords]; uniform vec4 gl_EyePlaneQ[gl_MaxTextureCoords]; uniform vec4 gl_ObjectPlaneS[gl_MaxTextureCoords]; uniform vec4 gl_ObjectPlaneT[gl_MaxTextureCoords]; uniform vec4 gl_ObjectPlaneR[gl_MaxTextureCoords]; uniform vec4 gl_ObjectPlaneQ[gl_MaxTextureCoords]; // // Niebla p. 161 // struct gl_FogParameters { vec4 color; float density; float start; float end; float scale; // Derived: 1.0 / (end - start) }; uniform gl_FogParameters gl_Fog; 7.6 Variables no Uniformes Este tipo de variables no tiene un correspondencia uno a uno entre el vertex language y el fragment language. Se nos presentan en dos conjuntos, uno para cada lenguaje. Sus relaciones se muestran más abajo. Las siguientes variables están disponibles para ser utilizadas con vertex shader. En particular, una de ellas deberá ser escrita si el fragmento correspondiente al shader o si el pipeline la usa a ella o a un estado derivado de ella. En otro caso, el comportamiento será indefinido. varying vec4 gl_FrontColor; varying vec4 gl_BackColor; varying vec4 gl_FrontSecondaryColor; varying vec4 gl_BackSecondaryColor; 47 varying vec4 gl_TexCoord[]; // como máximo será gl_MaxTextureCoords varying float gl_FogFragCoord; Para gl_FogFragCoor, el valor escrito será usado como valor “c” en la página 160 de la especificación de OpenGL 1.4. Por ejemplo, si la coordenada z deseada del fragmento en el espacio de visión es “c”, entonces este será el valor que el código del vertex shader debería almacenar en gl_FogFragCoord. Como con todos los arrays, los índices usados para acotar gl_TextCoord tienen que ser una expresión integral constante, o este array tendrá que ser redeclarado por el shader con un tamaño. El tamaño podrá ser como máximo del valor contenido en la variable gl_MaxTextureCoords. Usando índices próximos al 0 se ayuda a preservar recursos. Las siguientes variables está disponibles para lectura dentro de un fragmento shader. gl_Color y gl_SecondaryColor son los mismos nombres de atributos pasados al lenguaje vertex, aunque no se producirá conflicto de nombres debido a que estas variables solo son visibles dentro un fragmento shader. varying vec4 gl_Color; varying vec4 gl_SecondaryColor; varying vec4 gl_TexCoord[]; // como máximo será gl_MaxTextureCoords varying float gl_FogFragCoord; varying vec2 gl_PointCoord; Los valores de gl_Color y gl_SecondaryColor serán derivados y cubiertos automáticamente por el sistema desde gl_FrontColor, gl_BackColor, gl_FrontSecondaryColor, y gl_BackSecondaryColor basándose en la cara que sea visible en cada momento. Si la funcionalidad de pipeline fijo se usa para el procesado de vértices, entonces gl_FogFragCoord será la coordenada z en el espacio de visión, o la interpolación de la coordenada de la niebla, como se describe en la sección 3.10 de la especificación de OpenGL 1.4. Los índices del array gl_TexCoord son como se describe arriba en el texto referente a los vertex shaders. Los valores en gl_PointCoord son coordenadas bidimensionales que indican donde dentro de una primitiva de punto se localiza el fragmento actual. Su rango va desde 0.0 a 1.0 sobre el punto. Si la primitiva no es de punto, el valor leído de gl_PointCoord es indefinido. 48 8 Funciones Propias OpenGL define un conjunto de funciones propias para el trabajo y operaciones con escalares y vectores. Muchas de estas funciones pueden usarse en más de un tipo de shader, pero algunas de ellas son una forma de mapeo directa sobre el hardware y por eso son útiles para un tipo específico de sombreado. Estas funciones se pueden dividir en 3 categorías: · · · Provienen al usuario de alguna funcionalidad hardware, como por ejemplo el acceso a un mapeo de textura. No hay manera de emular dicho comportamiento mediante un shader. Funciones que representan una operación trivial, que a pesar muy sencillas de escribir por el usuario, tienen una representación directa y podrían tener un soporte directo en hardware. Es un problema muy complicado para el compilador mapear expresiones a complejas instrucciones de ensamblado. Funciones que representan una operación gráfica sobre el hardware que es posible acelerar en algún punto. Las funciones trigonométricas entran en esta categoría. Muchas de estas funciones tienen un nombre similar a las del lenguaje C, pero soportan entradas de vectores además de escalares. El usuario debe intentar usar las funciones que implementa el lenguaje en lugar de crear las suyas propias, dado que las propias están implementadas para un rendimiento óptimo. A pesar de lo anterior, las funciones pueden ser fácilmente reemplazadas por el código del usuario de forma muy sencilla. Basta con redefinir la función con el mismo nombre y los mismos parámetros. En las funciones especificadas a continuación, cuando la entrada de argumentos (y la correspondiente salida) puede ser float, vec3 o vec4, genType se usa como argumento. Para cualquier función específica redefinida, el tipo debe ser igual tanto en los parámetros como en las salidas. Para mat ocurre lo mismo, puede ser cualquier tipo de matriz básica. 49 8.1 Funciones de ángulos y trigonométricas Los parámetros especificados como ángulo se asumen en sus unidades correspondientes, los radianes. Si en algún caso se produce una división por cero, el resultado será indefinido. A continuación se detallan las funciones por componentes Sintaxis genType radians (genType grados) Descripción Convierte grados a radianes genType degrees (genType radianes) Convierte radianes a grados genType sin (genType angulo) Función seno genType cos (genType angulo) Función coseno genType tan (genType angulo) Función tangente genType asin (genType x) Arco seno. Devuelve el ángulo cuyo seno es x. El rango de valores devueltos está entre [-π/2, π /2] Si |x| > 1 el resultado es indefinido genType acos (genType x) Arco seno. Devuelve el ángulo cuyo coseno es x. El rango de valores devueltos está entre [0, π] Si |x| > 1 el resultado es indefinido genType atan (genType y, genType x) Arco tangente. Devuelve el ángulo cuya tangente es y/x. El signo de x e y es usado para determinar en que cuadrante está el ángulo. El rango de valores devuelto por la función es [-π, π]. Si x e y son ambos 0, el resultado es indefinido. genType atan (genType y_over_x) Arco tangente. Devuelve el ángulo cuya tangente es y sobre x. El rango de valores devuelto por la función es [-π/2, π/2]. 50 8.2 8.3 Funciones exponenciales Sintaxis Descripcion genType pow (genType x, genType y) Devuelve el valor de x elevado al valor de y El resultado es indefinido si x < 0 El resultado es indefinido si x = 0 e y <= 0 genType exp (genType x) Devuelve la exponecial natural de x genType log (genType x) Devuelve el logaritmo neperiano de x El resultado es indefinido si x <= 0 genType exp2 (genType x) Devuelve 2 elevado al valor de x genType log2 (genType x) Devuelve el valor del logaritmo en base 2 de x El resultado es indefinido si x <= 0 genType sqrt (genType x) Devuelve √x El resultado es indefinido si x < 0 genType inversesqrt (genType x) Devuelve 1 / √x El resultado es indefinido si x <= 0 Funciones comunes Syntax Description genType abs (genType x) Devuelve x si x >= 0, en otro caso devuelve -x genType sign (genType x) Devuelve 1.0 si x > 0, 0.0 si x = 0, o -1.0 si x < 0 51 Sintaxis Descripción genType floor (genType x) Devuelve el valor entero más próximo por defecto o igual a x genType ceil (genType x) Devuelve el valor entero más próximo por exceso o igual a x genType fract (genType x) Devuelve x – floor(x) genType mod (genType x, float y) Módulo. Devuelve x – y * floor (x/y) genType mod (genType x, genType y) Módulo. Devuelve x – y * floor (x/y) genType min (genType x, genType y) genType min (genType x, float y) Devuelve y si y < x, en otro caso devuelve x genType max (genType x, genType y) genType max (genType x, float y) Devuelve y si x < y, en otro caso devuelve x genType Devuelve min (max (x, minVal), maxVal) clamp (genType x, genType minVal, genType maxVal) genType clamp (genType x, float minVal, float maxVal) Si minVal > maxVal el resultado es indefinido genType mix (genType x, genType y, genType a) genType mix (genType x, genType y, float a) Devuelve la ecuación lineal de x e y Por ejemplo, x·(1 – a) + y · a genType step (genType edge, genType x) Devuelve 0.0 si x < edge, sino devuelve 1.0 genType step (float edge, genType x) genType genType Devuelve 0.0 si x <= edge0 y 1.0 si x >= edge1 y realiza el polinomio de Hermite entre 0 y 1 cuando edge0 < x < edge1. Esto es útil en casos cuando se desea una función que ponga un umbral para detectar transiciones con saltos. Si edge0 >= edge1 los resultados serán indefinidos smoothstep (genType edge0, genType edge1, genType x) smoothstep (float edge0, float edge1, genType x) 52 8.4 Funciones geométricas Estas funciones operan con vectores y como vectores, no como componentes Sintaxis Descripción float length (genType x) Devuelve la longitud del vector x float distance (genType p0, genType p1) Devuelve la distancia entre p0 y p1 Equivalente a length(p0 – p1) float dot (genType x, genType y) Devuelve el producto de x e y x[0] · y[0] + x[1] · y[1] + … vec3 cross (vec3 x, vec3 y) Devuelve el producto cruzado de x e y x[1] · y[2] – y[1] · x[2] x[2] · y[0] – y[2] · x[0] x[0] · y[1] – y[0] · x[1] genType normalize (genType x) Devuelve un vector unitario en la misma dirección de x vec4 ftransform() Solamente en vertex shaders. Esta función asegurará que el valor del vértice de entrada será transformado de la forma que produzca exactamente el mismo resultado que la misma funcionalidad en el lenguaje OpenGL. Su intención es la de ser utilizada para calcular gl_Position, i.e, gl_Position = ftransform() Esta función debe ser usada, por ejemplo, cuando una aplicación está renderizando la misma geometría en pasos separados, y un paso utiliza una funcionalidad cuyo camino está fijado y otro paso utiliza shaders programables Si dot(Nref, I) < 0 devuelve N, en otro caso -N genType faceforward(genType N, genType I, genType Nref) 53 Sintaxis Descripción genType reflect (genType I, genType N) Devuelve la dirección de reflexión del vector incidente I sobre la superficie orientada por N I – 2 * dot(N,I) * N N debe estar ya inicializado para obtener el valor deseado genType refract(genType I, genType N, float eta) Para el vector de incidencia I y la superficie normal N, y el radio de índices de refracción eta, devuelve el vector de refracción. El resultado es calculado por k = 1.0 – eta * eta * (1.0 – dot(N,I) * dot(N,I)) if(k < 0.0) return genType(0.0) else return eta * I – (eta * dot(N,I) + sqrt(k)) * N Los vectores I y N deben estas normalizados para obtener los resultados deseados 8.5 Funciones de matrices Sintaxis Descripción mat matrixCompMult (mat x, mat y) Multiplica la matriz x por la matriz y elemento a elemento, por ejemplo, result[i][j] es el producto escalar de x[i][j] por y[i][j]. Nota: para obtener la multiplicación algebraica lineal, usar el operador de multiplicación (*). mat2 outerProduct(vec2 c, vec2 r) mat3 outerProduct(vec3 c, vec3 r) mat4 outerProduct(vec4 c, vec4 r) Devuelve el primer parámetro c como un vector columna (matriz con una columna) y el segundo parámetro r como un vector fila (matriz con una fila) y realiza una multiplicación algebraica lineal de c * r, generando una matriz cuyo número de filas es el numero de componentes en c y cuyo número de columnas es el número de componentes en r. mat2x3 outerProduct(vec3 c, vec2 r) mat3x2 outerProduct(vec2 c, vec3 r) mat2x4 outerProduct(vec4 c, vec2 r) mat4x2 outerProduct(vec2 c, vec4 r) mat3x4 outerProduct(vec4 c, vec3 r) mat4x3 outerProduct(vec3 c, vec4 r) mat2 transpose(mat2 m) mat3 transpose(mat3 m) mat4 transpose(mat4 m) Devuelve una matriz la cual es la matiz transpuesta de m. La matriz de entrada no se modifica. mat2x3 transpose(mat3x2 m) mat3x2 transpose(mat2x3 m) mat2x4 transpose(mat4x2 m) 54 mat4x2 transpose(mat2x4 m) mat3x4 transpose(mat4x3 m) mat4x3 transpose(mat3x4 m) 8.6 Funciones relacionales de vectores Operadores relacionales y de igualdad (<, <=, >, >=, ==, !=) están definidos (o reservados) para producir resultados Booleanos escalares. Para resultados vectoriales, usar las siguientes funciones implementadas. Debajo, “bvec” toma valores de tipo bvec2, bvec3, o bvec4, “ivec” toma valores de tipo ivec2, ivec3, o ivec4, y “vec” toma valores de tipo vec2, vec3, o vec4. Para todos estos casos, los tamaños de los vectores de entrada y salida en una llamada en particular deben coincidir. Sintaxis Descripción bvec lessThan(vec x, vec y) bvec lessThan(ivec x, ivec y) Devuelve para cada elemento la comparación x<y. bvec lessThanEqual(vec x, vec y) bvec lessThanEqual(ivec x, ivec y) Devuelve para cada elemento la comparación x<=y. bvec greaterThan(vec x, vec y) bvec greaterThan(ivec x, ivec y) Devuelve para cada elemento la comparación x>y. bvec greaterThanEqual(vec x, vec y) bvec greaterThanEqual(ivec x, ivec y) Devuelve para cada elemento la comparación x>=y. bvec equal(vec x, vec y) bvec equal(ivec x, ivec y) bvec equal(bvec x, bvec y) Devuelve para cada elemento la comparación x==y. bvec notEqual(vec x, vec y) bvec notEqual(ivec x, ivec y) bvec notEqual(bvec x, bvec y) Devuelve para cada elemento la comparación x!=y. bool any(bvec x) Devuelve true solo si todos los componentes de x son true. bool all(bvec x) Devuelve true si algún componente de x es true. bvec not(bvec x) Devuelve para cada elemento el complemento lógico de x. 8.7 Funciones “Lookup” de texturas Las funciones “Lookup” de texturas se pueden usar con vertex y fragment shaders. Sin embargo, el nivel de detalle no esta definido por funcionalidades fijas para vertex shaders, hay pequeñas diferencias en operación entre vertex y fragment texturas lookup. Las funciones en la tabla inferior proporcionan acceso a texturas a través de samplers, como están definidas en el API de OpenGL. Las propiedades de la textura tales como tamaño, formato de pixel, numero de dimensiones, método de filtrado, numero de niveles de mip-map, profundidad de comparación, y otras están también definidas por la llamadas al Api de OpenGL. Tales propiedades son tomadas en cuenta a medida que la textura es accedida a través de las funciones implementadas que se definen abajo. Si se realiza una llamada de textura que no es una sombra a un sampler que representa una textura profunda con profundas comparaciones activadas, entonces los resultados son indefinidos. Si se realiza una llamada de textura de sombra a un sampler que representa una textura profunda con comparaciones profundas desactivadas, entonces los resultados son indefinidos. Si se realiza 55 una llamada de textura de sombra a un sampler que no representa una textura profunda, entonces los resultados son indefinidos. En todas las funciones situadas debajo, el parámetro bias es opcional para los fragment shaders. El parámetro bias no está aceptado en un vertex shader. Para un fragment shader, si bias esta presente, se añade al calculado del nivel de detalle previo para mejorar la operación de acceso a la textura. Si el parámetro bias no existe, entonces la implementación automáticamente selecciona el nivel de detalle: Para una textura que no esta “mip-mapped”, esta se usa directamente. Si esta “mip-mapped” y ejecutándose en un fragment shader, la LOD computada por la implementación es usada para hacer la consulta de la textura. Si esta “mip-mapped” y ejecutándose en un vertex shader, entonces se usa la textura base. Las funciones con “Lod” están permitidas solo en un vertex shader. Para las funciones etiquetadas con “Lod”, Lod es usado directamente como nivel de detalle. Sintaxis Descripción vec4 texture1D (sampler1D sampler, float coord [, float bias] ) vec4 texture1DProj (sampler1D sampler, vec2 coord [, float bias] ) vec4 texture1DProj (sampler1D sampler, vec4 coord [, float bias] ) vec4 texture1DLod (sampler1D sampler, float coord, float lod) vec4 texture1DProjLod (sampler1D sampler, vec2 coord, float lod) vec4 texture1DProjLod (sampler1D sampler, vec4 coord, float lod) Usa la textura coordinada coord para hacer un “lookup” de textura en la textura 1D tratada a sampler. Para la versión descriptiva (“Proj”), la textura coordinada coord.s es dividida por la ultima componente de coord. vec4 texture2D (sampler2D sampler, vec2 coord [, float bias] ) vec4 texture2DProj (sampler2D sampler, vec3 coord [, float bias] ) vec4 texture2DProj (sampler2D sampler, vec4 coord [, float bias] ) vec4 texture2DLod (sampler2D sampler, vec2 coord, float lod) vec4 texture2DProjLod (sampler2D sampler, vec3 coord, float lod) vec4 texture2DProjLod (sampler2D sampler, vec4 coord, float lod) Usa la textura coordinada coord para hacer un “lookup” de textura en la textura 2D tratada a sampler. Para la versión descriptiva (“Proj”), la textura coordinada (coord.s, coord.t) es dividida por la ultima componente de coord. La tercera componente de coord es ignorada por la variante vec4 coord. vec4 texture3D (sampler3D sampler, vec3 coord [, float bias] ) vec4 texture3DProj (sampler3D sampler, vec4 coord [, float bias] ) vec4 texture3DLod (sampler3D sampler, vec3 coord, float lod) vec4 texture3DProjLod (sampler3D sampler, vec4 coord, float lod) Usa la textura coordinada coord para hacer un “lookup” de textura en la textura 3D tratada a sampler. Para la versión descriptiva (“Proj”), la textura coordinada es dividida por coord.q. vec4 textureCube (samplerCube sampler, vec3 coord [, float bias] ) vec4 textureCubeLod (samplerCube sampler, Usa la textura coordinada coord para hacer un “lookup” de textura en la textura de mapa de cubo tratada a sampler. La dirección de coord es usada 56 vec3 coord, float lod) para seleccionar la cara para hacer un “lookup” de textura bidimensional, como se describe en la sección 3,8,6 en la versión 1,4 de la especificación de OpenGL. vec4 shadow1D (sampler1DShadow sampler, vec3 coord [, float bias] ) vec4 shadow2D (sampler2DShadow sampler, vec3 coord [, float bias] ) vec4 shadow1DProj (sampler1DShadow sampler, vec4 coord [, float bias] ) vec4 shadow2DProj (sampler2DShadow sampler, vec4 coord [, float bias] ) vec4 shadow1DLod (sampler1DShadow sampler, vec3 coord, float lod) vec4 shadow2DLod (sampler2DShadow sampler, vec3 coord, float lod) vec4 shadow1DProjLod(sampler1DShadow sampler, vec4 coord, float lod) vec4 shadow2DProjLod(sampler2DShadow sampler, vec4 coord, float lod) Usa la textura coordinada coord para hacer un “lookup” de comparación de profundidad en la textura profunda tratada a sampler, como se describe en la sección 3,8,14 de la versión 1,4 de la especificación de OpenGL. La tercera componente de coord (coord.p) es usada como el valor R La textura tratada a sampler debe ser una textura profunda, o los resultados son indefinidos. Para la versión descriptiva (“Proj”) de cada función, la textura coordinada es dividida por coord.q,obteniendo un valor R profundo de coord.p/coord.q. La segunda componente de coord es ignorada para las variantes “1D” . 8.8 Funciones de Procesado Fragmentado Las Funciones de Procesado Fragmentado están solo disponibles en los fragment shaders. Derivadas pueden ser muy costosas computacionalmente hablando y/o numéricamente inestables. Sin embargo, una implementación en OpenGL puede aproximar las verdaderas derivadas usando un rápido, pero no completamente exacto derivado computo. El comportamiento esperado de una derivada es especificado usando “forward/backward differencing”. Forward differencing: F xdx−F x ~ dFdx xdx 1a F xdx−F x dFdx x ~ ---------------------------1b dx Backward differencing: F x−dx−F x ~−dFdx x dx 2a F x−F x−dx dFdx x ~ ---------------------------dx 2b Con “single-sample rasterization”, dx <= 1.0 en las ecuaciones 1b y 2b. Para multi-sample rasterization, dx < 2.0 en las ecuaciones 1b y 2b. dFdy es aproximado de modo similar, con y reemplazando x. sujeto a las siguientes condiciones: 1. El método puede usar aproximaciones lineales. Tales aproximaciones lineales implican las derivadas de orden mas alto, dFdx(dFdx(x)) y superiores, son indefinidas. 57 2. EL método puede asumir que la función evaluada es continua. Sin embargo las derivadas en el cuerpo de un condicional no uniforme, son indefinidas. 3. EL método puede diferir por fragmento, sujeto a la limitación que el método puede variar por las coordenadas de la ventana, no las coordinadas de la pantalla. EL requisito invariante descrito en la sección 3,1 de la especificaron de OpenGL 1.4 es mas débil para los cálculos derivados, porque el método puede ser una función de una localización fragment Otras propiedades que son deseables, pero no requeridas, son: 4. Las funciones deben ser evaluadas en el interior de una primitiva (interpolada, no extrapolada). 5. Las funciones para dFdx deben ser evaluadas mientras se mantiene y constante. Las funciones para dFdy deben ser evaluadas mientras se mantiene x constante. Sin embargo, mezcladas derivadas de orden elevado, como dFdx(dFdy(y)) y dFdy(dFdx(x)) son indefinidas. 6. Derivadas de argumentos constantes deben ser 0. En algunas implementaciones, variando grados de derivadas la exactitud puede ser obtenida consultando los consejos GL (sección 5.6 de la especificación OpenGL 1.4), permitiendo al usuario obtener calidad de imagen en contra de velocidad. Sintaxis Descripción genType dFdx (genType p) Devuelve la derivada en x usando diferenciación local para el argumento de entrada p. genType dFdy (genType p) Devuelve la derivada en y usando diferenciación local para el argumento de entrada p. Estas dos funciones son comúnmente usadas para estimar el filtro de anchura usado para las texturas “anti-alias procedural”. Estamos asumiendo que la expresión esta siendo evaluada en paralelo en un array SIMD de modo que un punto cualquiera dado el valor de la función es conocido en los puntos representados por el array SIMD. LA diferenciación local entre los elementos del array SIMD pueden por lo tanto ser usados para derivar dFdx, dFdy, etc. genType fwidth (genType p) Devuelve la suma de la derivada absoluta en x e y usando la diferenciación local para el argumento de entrada p, p.e.: abs (dFdx (p)) + abs (dFdy (p)); 8.9 Funciones de Ruido Las funciones de ruido están disponibles para ambos fragment y vertex shaders. Son funciones estocásticas que pueden ser usadas para incrementar la complejidad visual. Los valores retornados por las siguientes funciones de ruido proporcionan la apariencia de aleatoriedad, pero no son verdaderamente aleatorias. Las funciones de ruido se definen a continuación para las siguientes características: El/los valor/es retornados están siempre en el rango [-1.0,1.0], y cubren al menos el rango [-0.6,0.6], con una distribución similar a la Gaussiana. 58 El/los valor/es retornados tienen un promedio general de 0.0 Son repetibles, en ese caso particular como valores de entrada siempre producirán el mismo valor retornado. Son estáticamente invariantes bajo la rotación (p.e. no importa como el dominio es rotado, tiene el mismo carácter estático) Tienen una invarianza estática bajo la translación (p.e. no importa como es trasladado el dominio, tiene el mismo carácter estático) Típicamente dan diferentes resultados bajo translación La frecuencia espacial esta concentrada, centrada en algún sitio entre 0.5 y 1.0. Son C1 continuas en cualquier punto (p.e. la primera derivada es continua) Sintaxis Descripción float noise1 (genType x) Devuelve un 1D ruido valor basado en el valor de entrada x. vec2 noise2 (genType x) Devuelve un 2D ruido valor basado en el valor de entrada x. vec3 noise3 (genType x) Devuelve un 3D ruido valor basado en el valor de entrada x. vec4 noise4 (genType x) Devuelve un 4D ruido valor basado en el valor de entrada x. 9 Gramatica del lenguaje de Shading La gramática es alimentada de la salida de análisis léxico. Las señales devueltas del análisis léxico son: ATTRIBUTE CONST BOOL FLOAT INT BREAK CONTINUE DO ELSE FOR IF DISCARD RETURN BVEC2 BVEC3 BVEC4 IVEC2 IVEC3 IVEC4 VEC2 VEC3 VEC4 MAT2 MAT3 MAT4 IN OUT INOUT UNIFORM VARYING CENTROID MAT2X2 MAT2X3 MAT2X4 MAT3X2 MAT3X3 MAT3X4 MAT4X2 MAT4X3 MAT4X4 SAMPLER1D SAMPLER2D SAMPLER3D SAMPLERCUBE SAMPLER1DSHADOW SAMPLER2DSHADOW STRUCT VOID WHILE IDENTIFIER TYPE_NAME FLOATCONSTANT INTCONSTANT BOOLCONSTANT FIELD_SELECTION LEFT_OP RIGHT_OP INC_OP DEC_OP LE_OP GE_OP EQ_OP NE_OP AND_OP OR_OP XOR_OP MUL_ASSIGN DIV_ASSIGN ADD_ASSIGN MOD_ASSIGN LEFT_ASSIGN RIGHT_ASSIGN AND_ASSIGN XOR_ASSIGN OR_ASSIGN SUB_ASSIGN LEFT_PAREN RIGHT_PAREN LEFT_BRACKET RIGHT_BRACKET LEFT_BRACE RIGHT_BRACE DOT 59 COMMA COLON EQUAL SEMICOLON BANG DASH TILDE PLUS STAR SLASH PERCENT LEFT_ANGLE RIGHT_ANGLE VERTICAL_BAR CARET AMPERSAND QUESTION INVARIANT Lo que procede describe la gramática para el lenguaje de shading de OpenGL en términos de las susodichas señales. variable_identifier: IDENTIFIER primary_expression: variable_identifier INTCONSTANT FLOATCONSTANT BOOLCONSTANT LEFT_PAREN expression RIGHT_PAREN postfix_expression: primary_expression postfix_expression LEFT_BRACKET integer_expression RIGHT_BRACKET function_call postfix_expression DOT FIELD_SELECTION postfix_expression INC_OP postfix_expression DEC_OP integer_expression: expression function_call: function_call_or_method function_call_or_method: function_call_generic postfix_expression DOT function_call_generic function_call_generic: function_call_header_with_parameters RIGHT_PAREN function_call_header_no_parameters RIGHT_PAREN function_call_header_no_parameters: function_call_header VOID function_call_header function_call_header_with_parameters: function_call_header assignment_expression function_call_header_with_parameters COMMA assignment_expression function_call_header: function_identifier LEFT_PAREN // Nota gramática: Los constructores asemejan funciones, pero analizados medicamento reconocidos la mayoría de ellos como claves. Ahora son reconocidos a través de “type_specifier”. function_identifier: type_specifier IDENTIFIER FIELD_SELECTION 60 unary_expression: postfix_expression INC_OP unary_expression DEC_OP unary_expression unary_operator unary_expression // Nota gramática : Ningún estilo tradicional escribe moldes. unary_operator: PLUS DASH BANG TILDE // reservados // Nota gramática : No hay operaciones unarias '*' o '&'. Los punteros no estan soportados. multiplicative_expression: unary_expression multiplicative_expression STAR unary_expression multiplicative_expression SLASH unary_expression multiplicative_expression PERCENT unary_expression // reserved additive_expression: multiplicative_expression additive_expression PLUS multiplicative_expression additive_expression DASH multiplicative_expression shift_expression: additive_expression shift_expression LEFT_OP additive_expression // reserved shift_expression RIGHT_OP additive_expression // reserved relational_expression: shift_expression relational_expression LEFT_ANGLE shift_expression relational_expression RIGHT_ANGLE shift_expression relational_expression LE_OP shift_expression relational_expression GE_OP shift_expression equality_expression: relational_expression equality_expression EQ_OP relational_expression equality_expression NE_OP relational_expression and_expression: equality_expression and_expression AMPERSAND equality_expression // reserved exclusive_or_expression: and_expression exclusive_or_expression CARET and_expression // reserved inclusive_or_expression: exclusive_or_expression inclusive_or_expression VERTICAL_BAR exclusive_or_expression // reserved 61 logical_and_expression: inclusive_or_expression logical_and_expression AND_OP inclusive_or_expression logical_xor_expression: logical_and_expression logical_xor_expression XOR_OP logical_and_expression logical_or_expression: logical_xor_expression logical_or_expression OR_OP logical_xor_expression conditional_expression: logical_or_expression logical_or_expression QUESTION expression COLON assignment_expression assignment_expression: conditional_expression unary_expression assignment_operator assignment_expression assignment_operator: EQUAL MUL_ASSIGN DIV_ASSIGN MOD_ASSIGN // reservado ADD_ASSIGN SUB_ASSIGN LEFT_ASSIGN // reservado RIGHT_ASSIGN // reservado AND_ASSIGN // reservado XOR_ASSIGN // reservado OR_ASSIGN // reservado expression: assignment_expression expression COMMA assignment_expression constant_expression: conditional_expression declaration: function_prototype SEMICOLON init_declarator_list SEMICOLON function_prototype: function_declarator RIGHT_PAREN function_declarator: function_header function_header_with_parameters function_header_with_parameters: function_header parameter_declaration function_header_with_parameters COMMA parameter_declaration function_header: 62 fully_specified_type IDENTIFIER LEFT_PAREN parameter_declarator: type_specifier IDENTIFIER type_specifier IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET parameter_declaration: type_qualifier parameter_qualifier parameter_declarator parameter_qualifier parameter_declarator type_qualifier parameter_qualifier parameter_type_specifier parameter_qualifier parameter_type_specifier parameter_qualifier: /* empty */ IN OUT INOUT parameter_type_specifier: type_specifier init_declarator_list: single_declaration init_declarator_list COMMA IDENTIFIER init_declarator_list COMMA IDENTIFIER LEFT_BRACKET RIGHT_BRACKET init_declarator_list COMMA IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET init_declarator_list COMMA IDENTIFIER LEFT_BRACKET RIGHT_BRACKET EQUAL initializer init_declarator_list COMMA IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET EQUAL initializer init_declarator_list COMMA IDENTIFIER EQUAL initializer single_declaration: fully_specified_type fully_specified_type IDENTIFIER fully_specified_type IDENTIFIER LEFT_BRACKET RIGHT_BRACKET fully_specified_type IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET fully_specified_type IDENTIFIER LEFT_BRACKET RIGHT_BRACKET EQUAL initializer fully_specified_type IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET EQUAL initializer fully_specified_type IDENTIFIER EQUAL initializer INVARIANT IDENTIFIER // Vertex solo // Nota gramatica: No 'enum', o 'typedef'. fully_specified_type: type_specifier type_qualifier type_specifier type_qualifier: CONST ATTRIBUTE // Vertex solo VARYING 63 CENTROID VARYING INVARIANT VARYING INVARIANT CENTROID VARYING UNIFORM type_specifier: type_specifier_nonarray type_specifier_nonarray LEFT_BRACKET constant_expression RIGHT_BRACKET type_specifier_nonarray: VOID FLOAT INT BOOL VEC2 VEC3 VEC4 BVEC2 BVEC3 BVEC4 IVEC2 IVEC3 IVEC4 MAT2 MAT3 MAT4 MAT2X2 MAT2X3 MAT2X4 MAT3X2 MAT3X3 MAT3X4 MAT4X2 MAT4X3 MAT4X4 SAMPLER1D SAMPLER2D SAMPLER3D SAMPLERCUBE SAMPLER1DSHADOW SAMPLER2DSHADOW struct_specifier TYPE_NAME struct_specifier: STRUCT IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE STRUCT LEFT_BRACE struct_declaration_list RIGHT_BRACE struct_declaration_list: struct_declaration struct_declaration_list struct_declaration struct_declaration: type_specifier struct_declarator_list SEMICOLON struct_declarator_list: 64 struct_declarator struct_declarator_list COMMA struct_declarator struct_declarator: IDENTIFIER IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET initializer: assignment_expression declaration_statement: declaration sentencia: sentencia_compuesta sentencia_simple // Nota Gramatical: No etiquete sentencias; 'goto' no está soportado. sentencia_simple: sentencia_declaracion sentencia_expresion sentencia_seleccion sentencia_iteracion sentencia_salto sentencia_compuesta: LAZO_IZQUIERDO LAZO_DERECHO LAZO_IZQUIERDO lista_sentencias LAZO_DERECHO sentencia_no_nuevo_ambito: sentencia_compuesta_no_nuevo_ambito sentencia_simple sentencia_compuesta_no_nuevo_ambito LAZO_IZQUIERDO LAZO_DERECHO LAZO_IZQUIERDO lista_sentencias LAZO_DERECHO lista_sentencias: sentencia lista_sentencias sentencia sentencia_expresion: PUNTO_Y_COMA expresion PUNTO_Y_COMA sentencia_seleccion: IF PARENTESIS_IZQUIERDO sentencia_selecciones_restantes expresion sentencia_selecciones_restantes: sentencia ELSE sentencia sentencia // Nota Gramatical: No 'switch'. Switch no están soportados. 65 PARENTESIS_DERECHO condicion: expresion tipo_completamente_especificado IDENTIFICADOR EQUAL inicializador sentencia_iteracion: WHILE PARENTESIS_IZQUIERDO condicion PARENTESIS_DERECHO sentencia_no_nuevo_ambito DO sentencia WHILE PARENTESIS_IZQUIERDO expresion PARENTESIS_DERECHO PUNTO_Y_COMA FOR PARENTESIS_IZQUIERDO para_sentencia_inicial para_sentencias_restantes PARENTESIS_DERECHO sentencia_no_nuevo_ambito para_sentencia_inicial: expresion_sentencia declaracion_sentencia condicionopt: condicion /* vacio */ para_sentencias_restantes: condicionopt PUNTO_Y_COMA condicionopt PUNTO_Y_COMA expresion sentencia_salto: CONTINUE PUNTO_Y_COMA BREAK PUNTO_Y_COMA RETURN PUNTO_Y_COMA RETURN expresion PUNTO_Y_COMA DISCARD PUNTO_Y_COMA //Solo fragmento de shader. // Nota Gramatical: No ‘goto’; 'goto' no está soportado.. unidad_traduccion: declaracion_externa unidad_traduccion declaracion_externa declaracion_externa: definicion_funcion declaracion definicion_funcion: funcion_prototipo sentencia_compuesta_no_nuevo_ambito 66 10 Problemas 1. ¿Es un error tener concordancia ambigua de los parámetros en una función como la siguiente? void foo(int); void foo(float); foo(3); SOLUCIÓN: No. Si el tipo exacto está disponible, sin conversión, no hay error de ambigüedad. 2. ¿Es un error tener concordancia ambigua de los parámetros en una función como la siguiente? void foo(float, int) void foo(int, float) foo(2, 3); SOLUCIÓN: Sí. Esto debería plantear un error en la llamada ambigua. Siempre hay dos formas de combinar a través de la conversión, y no hay concordancia exacta sin conversión, debería producirse un error. 3. ¿Qué debe suceder si los tipos del segundo y tercer operandos del operador ternario (?:) no coinciden? ¿Éstos deben ser convertidos a otro tipo, o debe retrasarse la conversión para que coincida con el modo en que se utiliza la expresión ternaria (más difícil)? SOLUCIÓN: Deben ser convertidos para coincidir con los demás, a fin de que la expresión sea del tipo determinado por la expresión. 4. Queremos dFdx, dFdy, y fwidth devuelvan 0 si su argumento es una expresión constante con la finalidad de utilizarlos en inicializadores constantes. La especificación actual permite teóricamente que estos derivados no sean cero. SOLUCIÓN ALTERNATIVA: Diga que debe devolver 0, en teoría que nadie nunca ha implementado una devolución que no sea cero, o si lo hiciesen, los shaders debería funcionar bajo la nueva norma. SOLUCIÓN ALTERNATIVA: Deshabilitar estos en las inicializaciones. SOLUCIÓN: Digamos que sólo tienen que devolver 0 cuando se utiliza en inicializadores. 5. ¿Hay que añadir los obvios constructores de la matriz de desaparecidos? SOLUCIÓN ALTERNATIVA: Para restantes Cm, deje indefinido. SOLUCIÓN ALTERNATIVA: Exigir P> = N y Q> = M. Es decir, sólo se puede ir a matrices más pequeñas. SOLUCIÓN: Para matPxQ m; matNxM Cm = matNxM(m) para cada índice en 2D [e] [j] que existe en ambos, m y Cm, coloque Cm [e] [j] en m [e] [j]. Todos los restantes Cm [e] [j] se ponen los de la matriz de identidad . 5A. ¿Se debería añadir constructores de matrices que permitan la construcción de filas de información? 67 SOLUCIÓN: Es aceptable. 6. ¿Debemos exigir operadores de array para ser explícitamente dimensionados antes de ser utilizado con un operador == o !=? SOLUCIÓN: Sí. 6A. ¿Debemos exigir operadores de array para ser explícitamente dimensionados antes de ser utilizado con un una asignación? SOLUCIÓN: Sí. 7. ¿Puede utilizarse un constructor sin tamaño? Está claro lo que se entiende por el número de argumentos enumerados. Hay dos caminos a tomar aquí, que también puede afectar al problema 8. Esto es, ¿ "float []" significará implícitamente una matriz de tamaño, o queremos reservar eso en el sentido de una referencia para estar disponible en un futuro? Si implícitamente el tamaño del array se ajusta perfectamente en el que "float [] (1, 4)" es un array explícitamente dimensionado a 2, al igual que "float a[ ] = float[2](1, 4)" ya está. Incluso podríamos permitir "float a[ ] = float[ ](1, 4);". SOLUCIÓN: Sí, pueden utilizarse constructores sin tamaño, como también se puede declaraciones sin tamaño que son dimensionadas en la inicialización. Tenemos que llegar a una sintaxis de las diferentes referencias en el futuro que es ortogonal con el tipo (corchetes habría sido una sintaxis referida específica a un array). Tenga en cuenta que esto no es otra forma de crear implícitamente tamaño arrays, como un array que de inmediato termina con un tamaño explícito que no se puede cambiar. Dejar clara la nueva diferencia entre la inicializacion y la asignación para arrays sin dimensionar, porque ya hemos dicho el inicializador puede inicializar sintácticamente lo que parece una array sin dimensionar, mientras que la cesión no puede. 8. ¿Puede un parámetro de la función ser un array sin dimensionar? Las funciones pueden clonado internos, y / o arrays pasado por referencia internamente (que sigue satisfaciendo el noaliasing y pasa por una copia semántica externamente). Esta función interna de clonación surgirá de nuevo, si hablamos de pasar por referencia semántica, y hablamos de la necesidad de diferentes cuerpos de la función para transmitir, por ejemplo, en arrays uniformes frente a arrays globales, porque el objeto tiene que ser diferente. SOLUCIÓN: Es aceptable. Requiere de compilación en relación a tiempo comportamiento. Posiblemente estamos en el camino de un solo cuerpo de la función aceptando los arrays de diferentes tipos y dimensiones, y que los corchetes vacios (arrays sin dimensionar) significa aceptar múltiples tamaños, frente el significado de aceptar una referencia a un array. 9. ¿Deberíamos tener una matriz de aliasing para matrices cuadradas, por ejemplo, __ Mat2x2 es lo mismo que mat2? SOLUCIÓN: Sí. 10. ¿Deberíamos tener una matriz alias para vectores, por ejemplo, __ Mat1x2 es lo mismo que vec2? Del mismo modo necesitamos esto, o tenemos una función incorporada para multiplicar un vector fila por un vector columna, y devuelve una matriz. SOLUCIÓN: No, no alias de vectores. No es un alias de verdad, porque los vectores son ambiguos respecto a ser vectores fila o columna. Añadir la función incorporada "matN outerProduct (vecN, vecN)", en su lugar (véase el problema 12). 68 11. ¿El primer número en una matriz no cuadrada es el número de filas o el número de columnas? Número de filas es más intuitivo. Número de columnas es más coherente con el orden existente de la indexación. SOLUCIÓN ALTERNATIVA: El primer número es el número de filas. Queremos movernos en esta dirección de todos modos... que lo principal es independiente del diseño abstracto. Nosotros podemos estar agregando una estructura para pedir el orden fila-mayor, que diga lo que la indexación significa, pero podemos mantener el tipo de declaración de la misma manera, diciendo que el primer número es el número de filas. SOLUCIÓN: Nunca agregue un modo para pedir operaciones basado en filas, y nunca avanzar hacia la lengua que está más basado en filas. El primer número significa el número de columnas. 11A. ¿Hay que añadir un método de selección de una fila fuera de una matriz, por ejemplo, M.row (i)? SOLUCIÓN: Esto es aceptable. 12. ¿Cuál debería ser la verdadera ortografía para el producto de matrices? (Véase el problema 10.) SOLUCIÓN: outerProduct 13. ¿Cuál es el número de posiciones del atributo que se necesita para guardar una matriz? ¿el número de filas, el número de columnas, o el máximo de éstos? SOLUCIÓN ALTERNATIVA: El máximo, para permitir implementaciones de la elección para el movimiento de datos más eficiente. SOLUCIÓN: El número de columnas; debido a la situación actual de los arrays vertex, la cartografía de las columnas a las franjas horarias es vulnerable. 14. ¿Cómo de cerca queremos definir donde está ubicado el centroide? "Próximo" parece un poco vaga. SOLUCIÓN: Por lo menos tiene que estar en la intersección de la primitiva y el pixel, o uno de los fragmentos que se inscribe en la primitiva. Preferiblemente, es el centroide de la intersección. El centroide de las muestras situadas en esta intersección es también una buena elección. La ubicación de las muestras dentro de la intersección que están cerca de estos centroides también son buenas opciones. 15. ¿Podemos poner en una restricción de recursión estática, como se hizo en ES? En el escritorio espec, sólo se deshabilitó la recursión dinámica, pero creo que es mejor deshabilitar también la recursión estática. Si nadie tiene el apoyo de recursión estático hasta el momento, podríamos hacer esto. SOLUCIÓN: Deshabilitar recursión estática, al igual que se hizo en ES. 16. ¿Es necesario añadir transposición? ¿Debería ser un método para transponer en el lugar, o un método que devuelve una transposición funcional, o una función incorporada que devuelve una transposición funcional? SOLUCIÓN: Añadir funciones incorporadas mat2 transpose(in mat2) mat3 transpose(in mat3) mat4 transpose(in mat4) 69 mat2x3 transpose(in mat3x2) mat3x2 transpose(in mat2x3) mat2x4 transpose(in mat4x2) mat4x2 transpose(in mat2x4) mat3x4 transpose(in mat4x3) mat4x3 transpose(in mat3x4) Y reservar un .transpose() para su posible uso futuro. 17. ¿Puede la aplicación consultar el valor inicial se establece un modelo uniforme de enlace en el tiempo? SOLUCIÓN: Sí, utilizando los actuales puntos de acceso para la consulta uniforme, de modo que no sería necesario adoptar. 18. ¿Los centroide variables tienen derivadas? SOLUCIÓN ALTERNATIVA: Las derivadas de centroides variables no están definidas. SOLUCIÓN: Digamos que se definen, pero menos precisas. Avise públicamente para enviar a cabo una actualización de la condición 5 en la lista. SOLUCIÓN ALTERNATIVA: Las derivadas son tan precisas como antes, probablemente ejecutadas por un centroide no variable separado para su uso en derivadas. ¿Podría ser difícil se obtener para expresiones complejas en el argumento. 19. What ES clarifications to we want to bring across? 19A. Ningun otro main se permite además de 'void main()' de punto de entrada estándar. SOLUCIÓN: Sí, esto está claro. 19B. ¿Cómo indefinido son los índices de mala lectura y escritura en arrays? ES dice que podría causar inestabilidad del sistema. Nos dice que el comportamiento es indefinido. SOLUCIÓN: Deja spec. como es, diciendo que no se define. 19C. Una mejor organización de las clases de clasificadores: uniform, varying, attribute, and const se convirtieron en 'clasificadores de almacenamiento' in, out, inout se convirtió en 'clasificadores de parametros' Highp, mediump, lowp son ' clasificadores de precisión' invariant es un 'clasificador de invariancia' Donde, dejaríamos fuera a los dos últimos, que coinciden con las especificaciones para los dos primeros, y es fácil ponerlos en los demás más tarde. SOLUCIÓN: Sí. 20. ¿Debemos exigir shaders que contengan #version 120 para utilizar características específicas de 1,20? Esto reduce potencialmente la portabilidad, ya que la pueden utilizar los códigos de shaders que en realidad no necesitan características 120. Por otra parte, se abre la puerta para los pequeños cambios hacia atrás no compatibles lo cual sería beneficioso, al igual que no tener "__" delante de palabras clave nuevas. SOLUCIÓN: Sí. Exigir #versión 120 para permitir el problema 20A. 20A. Si se requiere la versión 120, entonces podremos soltar "__" en las nuevas palabras clave? SOLUCIÓN: Sí. Las nuevas palabras clave para la versión 120 no necesita la "__". 70 20B. ¿#Versión necesitan coincidir en todas las unidades de compilación en un objeto de programa? Parecería una opción obvia, pero sería agradable que si las bibliotecas (o simplemente el código existente que no necesita ser actualizado) no tiene que ser actualizado sólo porque una unidad de compilación quisiera una nueva característica. SOLUCIÓN: No. Mirando las características que se van añadiendo, parece que no hay necesidad real para exigir la misma versión en cada unidad de compilación. La mayoría son de sintaxis, conversiones, o de otro tipo de shaders locales. Los restantes son: Arrays de objetos: estos tienen todavía un tamaño conocido en tiempo de compilación, de manera transversal unidad de sharing funciona como antes. Las palabras clave sin "__": los shaders 120 no puede utilizarlos como nombres, así es que compartirlas no es posible. Inicializadores uniformes: trabajan, permitimos a algunos de los módulos que no tienen el inicializador. Centroide: si pedimos que las definiciones (vert vs. frag) para hacer coincidan, luego el usuario tiene que usar el 120 para ambos, de otro modo, podrían utilizar 110 frag y 120 vert. 21. ¿Es necesario una versión de centroide gl_FragCoord? SOLUCIÓN: No, por lo menos que quede claro que el actual gl_FragCoord no es un centroide. Añadir un nuevo centroide frag-coord tiene sentido, pero hasta el momento parece ser de uso marginal, de modo que todavía no se tiene que hacer esto. 22. ¿Se puede declarar una funcion de variables ocultas del mismo nombre? El 1,10 espec. específicamente dice que esto no debe suceder. SOLUCIÓN ALTERNATIVA: No. Esto sería un cambio incompatible hacia atrás que puede romper algunos shaders. Por ejemplo, un código como float sin = sin(expr); float r = sin * sin; Es útil, y podría haber sido escrito. SOLUCIÓN: Sí. El uso de la #versión 120 nos permite romper la compatibilidad de una manera sutil. El código todavía puede declarar una variable a nivel local y usarla, a pesar de que el nombre coincide con un nombre de la función. Simplemente no puede entonces proceder a llamar a la función del mismo nombre en el mismo ámbito de aplicación. Sería un error de redeclaración si ambas funciones y variables son declaradas en el mismo ámbito de aplicación. 22A. ¿Se puede declarar una variable oculta en una estructura del mismo nombre en el mismo ámbito, o es un error? Es decir, ¿qué ocurriría en el siguiente supuesto? { struct S { ... }; S S; S ... // S is not the type 'S'. The type is hidden by the variable 'S' } SOLUCIÓN: Esto es un error. C++ lo permite, pero no estoy seguro que a nosotros nos haga esto. Tipos y variables comparten el espacio de nombres en un ámbito, y se trata de una redefinición de un nombre en ese espacio de nombres. La implementación de referencia de 3Dlabs lo deshabilitó. 23. ¿Se puede declarar un tipo de estructura de nombre oculto tanto para las variables y para todas las funciones del mismo nombre? (Redeclarando el mismo nombre de variable en el mismo ámbito de aplicación es, por supuesto, un error de redeclaración.) Esta fue la intención de la especificación 1,10. 71 SOLUCIÓN: Sí. Esto es coherente con la especificación actual, evita la confusión entre el constructor y las nuevas funciones del mismo nombre, y se reserva un espacio de nombres para la firma de los constructores suplentes en el futuro. 24. ¿Se puede declarar un nombre para una función oculta y un tipo de estructura y el constructor de ella (con el mismo nombre), tanto en ámbitos anidados y en el mismo ámbito? Esta fue la intención de la especificación 1,10. SOLUCIÓN: Deshabilitar declaraciones de las funciones locales. En el ámbito global, se trata de un error de redefinición porque tienen el mismo nombre una función y la estructura. SOLUCIÓN ALTERNATIVA: Sí. Ocultando el constructor lo tiene que hacer. No tendría sentido ocultar el constructor y no ocultar el tipo, por lo tanto tienen que ser ocultas. 25. ¿Se puede declarar una función, en un ámbito anidado, mostrando la definición de una función que había sido escondida en una declaración de un tipo de estructura en un ámbito intermedio? Esto no fue dirigida por la especificación 1,10, pero es coherente con sus normas de alcance anidados. SOLUCIÓN: Deshabilitar declaraciones de funciones locales. SOLUCIÓN ALTERNATIVA: Sí. No hay casi ninguna otra interpretación válida para hacerlo, y que sea útil hacerlo. Es sólo un caso de llegar a hacer lo que quiera en un ámbito anidados, combinado con un único espacio global para todas las definiciones de funciones. 26. ¿Se puede declarar una función no oculta y la definición de una función que había sido escondida en una declaración de un tipo de estructura en el mismo ámbito? Esto sin duda no fue contemplado en la especificación 1,10. C++ dice que esto es un error. SOLUCIÓN: Deshabilitar las declaraciones de funciones locales. SOLUCIÓN ALTERNATIVA: No. Haga declarar una función con el mismo nombre que un tipo de estructura del mismo alcance que el error de redeclaración. Esta es una cosa oscura que se está haciendo. Tiene una pequeña oportunidad de romper un shader existente, por ejemplo, void foo(int) { ... } void main() { struct foo { float f; }; // legal void foo(int); // suggested to be illegal; this scope has a foo() } 27. ¿Se puede declarar una función oculta a una variable con el mismo nombre? Esto sería incompatible con la especificación 1,10. SOLUCIÓN: Se trata de un error de redeclaración sólo en alcance global. Para ámbito local, deshabilite la declaración de funciones locales. No hay variables construidas que pueden ser redeclararadas como un nombre de una función por el usuario, por lo que esto se convierte en un problema inexistente. SOLUCIÓN ALTERNATIVA: No. Es realmente asimétrico tener variables no cultas a funciones, pero tienen variables ocultas a funciones. También es compatible hacia atrás, aunque probablemente rara vez se hace en shaders reales. SOLUCIÓN ALTERNATIVA: Sí. Si seguimos permitiendo declaraciones locales, y si queremos cambiar las cosas para las variables ocultas a funciones, para mantener todo simétrico. Sería un error de redeclaración en el mismo ámbito de aplicación. 72 28. ¿Se puede declarar una variable no oculta a una función que había sido escondida por un tipo de estructura que la declaración de la variable oculta? Por ejemplo, void foo(int) { ... } void main() { struct foo { float f; }; // legal, hides 'void foo(int)' { bool foo; // legal, hides 'struct foo' foo(2); // ?? is 'void foo(int)' visible again? } } SOLUCIÓN: No. 22 - 28. Resumen de las soluciones propuestas a los problemas 22 al 28: Deshabilite declaraciones de funciones locales. Una declaración de una variable o un tipo de estructura es un error de redeclaración si ya existe una función, una variable, o un tipo de estructura con el mismo nombre en el mismo ámbito. Una declaración de una función es un error de redeclaración si ya existe una variable o un tipo de estructura con el mismo nombre en el mismo ámbito. Las funciones puede ser declaradas más de una vez en el mismo ámbito. Una declaración de una variable o un tipo de estructura oculta todas las variables, funciones, estructura o tipos (y sus constructores) del mismo nombre sobre todos los ámbitos exteriores. Las funciones ocultas permanecen ocultas. Resumen de lo que es compatible hacia atrás desde esta lista: No puede tener declaraciones de función locales. No puede utilizar una función y una variable con el mismo nombre en el mismo ámbito. El compilador dará errores para estos casos, de modo que ninguno de estos cambios de comportamiento ocurrirá silenciosamente. 29. ¿Hay que añadir los puntos de entrada para encapsular la carga de los atributos una matriz, por lo que el sistema puede realmente ocultar el orden interno en memoria de filas principales frente a columnas principales? SOLUCIÓN: No. Nos gustaría, pero se requeriría soporte de arrays con atributos vertex, qué hasta ahora es sólo extensible a cuatro componentes, de modo que todavía no tienen la capacidad de representar a toda una matriz. Tenga en cuenta la extensión de API que acompaña a esta especificación de lenguaje. 30. ¿Existe un mecanismo de la API para sugerencias a cerca de la fijación del centroide? ¿"Lo más rápido" que permite el centroide es estar situado en el centro de píxeles (fuera de la primitiva)? SOLUCIÓN: No específica que un nuevo mecanismo deba ser añadido. Basta con utilizar el mecanismo de sugerencia de derivadas existente. 31. Necesidad de aclarar que gl_FragCoord es también una de las muestras del multisampling. La misma muestra, tal como se recogió para muestras variables. SOLUCIÓN: Sí. 32. ¿Las funciones incorporadas y los nombres se comportan como si se encuentran en el ámbito global, o en un ámbito más' exterior 'que el ámbito global? 73 POSIBLE SOLUCIÓN: No. Ponga las incorporadas en el ámbito global. Esto significa que no se puede definir una variable o estructura en el ámbito global con el mismo nombre que una función incorporada. Esto significa variables globales de shader contaminar el espacio de nombres de funciones incorporadas (direccionables en el futuro mediante el uso de prefijo "gl", o un espacio de nombres para el caso "gl", tienen que decidir si los usuarios pueden ofrecer las firmas de una novela de un nombre gl). Al igual que en 1,10, redeclarando una función incorporada que no ocultara otras funciones con el mismo nombre. Nosotros no solo permitiríamos ofrecer un nuevo cuerpo de una función incorporada, y todavía esperan encontrar este tipo de cuerpos para redeclarar funciones incorporadas. SOLUCIÓN: Sí. Las incorporadas se encuentran en un ámbito más externo. Esto significa que se puede definir una variable global con el mismo nombre que una incorporada, y que se esconden todas las incorporadas con ese nombre. Redeclarando una función incorporada que ocultara las incorporadas con ese nombre, que rompe la compatibilidad hacia atrás; todas esas firmas utilizadas tendrán que tener un cuerpo condicionado a ello. Esto daría a un error de la mayoría de los casos en los que el codificador anula una firma, pero no otras, y todavía llama una de las otras, porque para la mayoría de las incorporadas, nuestras reglas de conversión pueden no coincidir con los argumentos de uno a otro prototipo. Las excepciones son lessThan(argumentos enteros) vs. lessThan (argumentos flotantes), y similarmente greaterThan, lessThanEqual, greaterThanEqual, equal, y notEqual. 33. ¿Qué nuevas palabras clave queremos reservar? SOLUCIÓN: Podemos reservar nuevas palabras clave, ya que están protegidos por la #version. Estas incluyen lowp, mediump, highp, precision. 34. Necesitamos soportar invarianza básica. SOLUCIÓN: Duplicar palabras clave de invarianza de OpenGL ES, pero sólo permiten su uso en el usuario varyings, una función de salida varyings desde el vertex shader, y las variables especiales de gl_Position y gl_PointSize. 35. ¿Debemos permitir la invarianza en funciónes de retorno de valores, de manera que una función en otra unidad de compilación puede ser usado en un cálculo de invarianza? SOLUCIÓN: Es aceptable. Este es un buen plan. Para la versión 1,2, el compilador sólo puede calcular fuera de la corriente y el control de flujo de datos si se encuentran en su totalidad en una sola unidad de compilación. 36. ¿Shaders de fragmentos varyings necesitan ser declarados como invariantes si estuvieran en el lado del vértice? ¿Pueden ser opcionalmente? SOLUCIÓN: La calificación de invariant tiene que coincidir en todos los lados. La interpolación de computación puede dividirse en partes, y ambas partes deben computar de un modo invariante. Es un error si no coinciden. No informar de esto como un error, significa que alguien pueda hacer innecesariamente cálculos de invarianza silenciosamente en el lado del vértice o no hacerlos cuando se desea, silenciosamente. 37. ¿Qué sucede si diferentes unidades de compilación tienen diferentes tamaños implícitamente para el mismo array de tamaño implicito? SOLUCIÓN: Utilice el máximo, no provocará un error. 38. ¿Hay que eliminar las estructuras incorporadas para coincidir con ES? 74 SOLUCIÓN: Sí. Esto elimina un futuro posible problema de espacio de nombres donde con un operador real de espacio de nombres (por ejemplo, "::") nombres de estructuras anidadas pueden ser cuantificados dentro de la estructura del espacio de nombres, pero hoy tienen que ser cuantificados hacía un mismo alcan 75