It is designed for straight-forward translation to GLSL or Javascript. In the future, it is planned to implement translation to asm.js / web assembler to further improve performance on the web.
RiceScript is in the early stage of development, therefore many basic features are not supported, like pointers or dynamic memory allocation. However, thanks to its simplicity, it integrates well with GLSL, for example: you can use GLSL-like math in RiceScript or even re-use parts of RiceScript code in GLSL.
Quick comparison to C
As you can see in the example below, RiceScript look and feels like C, with few minor exceptions, i.e. unnamed "()" function and operator ^ that is calculating power instead of xor:
() // declaration of RiceScript-specific unnamed "main" function, // called every frame { cls(0); // clear screen to black int n = 6; double s = 2^n; // operator ^ in RiceScript is power, not xor double t = klock(); // get current time in seconds // xres, yres are global variables, "double" type // xres = width in pixels of the current window // yres = height in pixels of the current window for(double x=0; x<xres; ++x) { double y = yres/2; y += turbulence(x/s, t, n)*s; setpix(x, y); // draw single pixel, white color by default } } double turbulence(double x, double y, int n) { double r = 0; double a = 1, s = 1; for(int i=0; i<n; ++i, s*=2, a*=.5) { r += noise(x*s,y*s)*a; // calls built-in perlin noise function } return r; }
Primitive types
There are two primitive types in RiceScript:
- double - IEEE-754 64bit floating point number
- int - 32bit signed integer, internally can be represented as double (in Javascript translation)
- #use glsl_math
- float - IEEE-754 32bit floating point, internally can be represented as double (in Javascript translation)
- vec2 - floating point 2-tuple { x, y }
- vec3 - floating point 3-tuple { x, y, z }
- vec4 - floating point 4-tuple { x, y, z, w }
- mat2 - floating point 2x2 matrix
- mat3 - floating point 3x3 matrix
- mat4 - floating point 4x4 matrix
Unnamed functions and default return type
As shown in the previous example we can declare unnamed functions, which usually denotes main function/starting point of a program. The default return type for all functions is "double" and can be omitted in function declaration. For unnamed functions, return type is always "double" and cannot be explicitly declared. For other functions we can optionally declare return type: for now only primitive types and vector/matrices are supported. References in a return type are not supported.
In PolyCube, the main function is declared simple as "()", like in EvalDraw, and is called every frame. Other EvalDraw modes: (x), (x,y), (x,y,&r,&g,&b), etc.. are not supported. Unlike in EvalDraw language, the main function itself and scope after main function has to be explicity declared:
() { // some code here that will be called every frame }
Auto variables and scope of declared variables
The default numerical type for variables is "double". In declaration of static variables or in function arguments double keyword can be omitted.
Moreover, numerical variables don't have to be declared. Undeclared numerical variables (non-static) are automatically declared as auto variables in the main scope of the function. Other variables: static or with explicity declared type will be called declared variables.
It is important to note that unlike auto variables, declared variables have restricted scope visibility, just like in C langauge; they are only visible in the scope of declaration and after the declaration. This is the main difference to "eval" language used in EvalDraw, where all variables are declared and visible in the main scope of the function.
Let's compare explicitly typed and non-typed version (both will compile in RiceScript):
// Explicitly-declared variable types () { cls(0); int n = 6; double s = 2^n; double t = klock(); for(double x=0; x<xres; ++x) { double y = yres/2; y += turbulence(x/s, t, n)*s; setpix(x, y); } } double turbulence(double x, double y, int n) { double r = 0; double a = 1, s = 1; for(int i=0; i<n; ++i, s*=2, a*=.5) { r += noise(x*s,y*s)*a; } return r; }
// Auto variables with "double" type () { cls(0); int n = 6; s = 2^n; t = klock(); for(x=0; x<xres; ++x) { y = yres/2; y += turbulence(x/s, t, n)*s; setpix(x, y); } } turbulence(x, y, int n) { r = 0; a = 1; s = 1; for(int i=0; i<n; ++i, s*=2, a*=.5) { r += noise(x*s,y*s)*a; } return r; }
Now, let's look closely at non-typed version and note down variables visibility:
() { cls(0); int n = 6; // "int" type has to be explicitly declared s = 2^n; // "s" is auto "double" variable, "^" is power operator t = klock(); // "t" is auto "double" variable for(x=0; x<xres; ++x) // "x" is auto "double" variable { y = yres/2; // "y" is auto "double" variable y += turbulence(x/s, t, n)*s; setpix(x, y); } // "x" and "y" are visible outside of "for" loop, // even that they are only used inside! } // default return type is "double" turbulence(x, y, int n) // arguments "x", "y" have default type "double" { r = 0; a = 1; s = 1; // "r", "a", "s" are auto "double" variables for(int i=0; i<n; ++i, s*=2, a*=.5) { r += noise(x*s,y*s)*a; // calls built-in perlin noise function } // "i" is declared, thus not visible outside "for" loop return r; }
GLSL translation and #use macro
In GLSL shaders we can declare #use macro as follows:
#use function_name
Currently #use macro is experimental feature and many restrictions apply. A good practice is to make RiceScript functions that we want to use in GLSL simple and more GLSL-like.
Let's go back to our example. Thanks to #use macro, we can declare "turbulence" function once in RiceScript and call it from GLSL:
() { t = klock(); // get current time in seconds gluniform1f("t", t); // pass "t" as uniform glquad(1); // draw full-screen quad with alpha = 1.0 cls(); // clear 2d canvas, no color = make it transparent s = 2^6; // hardcode number of turbulence octaves to 6 for(x=0; x<xres; ++x) { y = yres/2; y += turbulence(x/s, t)*s; // call shared "turbulence" function setpix(x, y); // draw single pixel, white color by default } } turbulence(x, y) // return type will be automatically "double" in RiceScript and "float" in GLSL, // even if we explicitly declare it as "double" { r = 0; a = 1; s = 1; // "r", "a" and "s" type will be "double" in RiceScript and "float" in GLSL // To make translation compatible with OpenGL ES 2.0 shaders, // we have to use only simple "for" loops, // with explicitly declared type and constant number of iterations for(int i=0; i<6; ++i) { r += noise(x*s,y*s)*a; // We have to take extra expressions out of "for" body as well, // GLSL in WebGL 1.0 require this s *= 2; a *= .5; } return r; } //==================================================== // After @f we can start unnamed GLSL fragment shader @f // First, we have to declare all dependencies, here to built-in noise function #use noise // Declare that we will use our "turbulence" function #use turbulence uniform float t; // current time in seconds void main() { const float s = 1.0/128.0; // scale, make it 1/pow(2, 6 octaves + 1) float r = turbulence(gl_FragCoord.x*s, gl_FragCoord.y*s - t); // call "turbulence" function declared in RiceScript gl_FragColor = vec4(r*.25 + .25); // set fragment color }
Go back to main page.