Thread overview
"This is madness!" Red Book OpenGL Example 1-3, done using templates
Aug 01, 2007
downs
Aug 01, 2007
downs
Aug 03, 2007
Don Clugston
August 01, 2007
This is a translation of the Red Book's OpenGL Example 1-3 into D, without using any standard library functions or pre-existing OpenGL bindings. It makes extensive use of templates and contains a minimum of repeated information. I realize the whole heap is probably close to unreadable, but it helps to start from the bottom - main is almost clean, and to ignore the templated code for the beginning. Use in whole or part as you like, under any license - none of this is copyrightable anyway.

template Tuple(T...) { alias T Tuple; }

/// Import the NAMES as constants of type TYPE (must be :uint) into the namespace,
/// starting with OFFSET and moving in NEXT jumps
template _ConstEnums(TYPE, alias NEXT, uint OFFSET, NAMES...) {
  static if (!NAMES.length) const char[] _ConstEnums="";
  else {
    static assert(is(typeof(NAMES[0]): char[]),
      "Invalid _ConstEnums parameter "~NAMES[0].stringof~" which is "~typeof(NAMES[0]).stringof~".");
    const char[] _ConstEnums="const "~TYPE.stringof~" "~NAMES[0]~"="~OFFSET.stringof~"; "
      ~_ConstEnums!(TYPE, NEXT, NEXT(OFFSET), NAMES[1..$]);
  }
}

typedef uint Enum; /// additive increase
typedef uint Bitfield; /// multiplicative increase

uint Inc(uint v) { return v+1; } /// for Enum
uint Dbl(uint v) { return v*2; } /// for Bitfield
/// convenient wrapper
template ConstEnums(TYPE, OFFS_AND_NAMES...) {
  static assert(OFFS_AND_NAMES.length>1);
  static assert(is(TYPE: uint));
  static if(is(TYPE: Enum))
    const char[] ConstEnums=_ConstEnums!(TYPE, Inc, OFFS_AND_NAMES);
  else {
    static assert(is(TYPE: Bitfield), "Don't know how to generate const-enum from TYPE "~TYPE.stringof);
    const char[] ConstEnums=_ConstEnums!(TYPE, Dbl, OFFS_AND_NAMES);
  }
}

/// Makes sure all U are of type T
template AssertTypes(T, U...) {
  static if (U.length) {
    const char[] AssertTypes=
      "static assert(is("~typeof(U[0]).stringof~": "~T.stringof~"), \"
        Type assertion failed - cannot convert "~typeof(U[0]).stringof~" to "~T.stringof~"\"); "
    ~AssertTypes!(T, U[1..$]);
  } else const char[] AssertTypes="";
}

/// all STRINGS[2..$] are prepended STRINGS[0] and appended STRINGS[1]
template PrePost(STRINGS...) {
  mixin(AssertTypes!(char[], STRINGS));
  static assert(STRINGS.length!<3, "Insufficient argument count for PrePost");
  static if(STRINGS.length>3) alias Tuple!(STRINGS[0]~STRINGS[2]~STRINGS[1], PrePost!(STRINGS[0..2], STRINGS[3..$])) PrePost;
  else alias Tuple!(STRINGS[0]~STRINGS[2]~STRINGS[1]) PrePost;
}

template Prepend(STRINGS...) { alias PrePost!(STRINGS[0], "", STRINGS[1..$]) Prepend; }
template Append(STRINGS...) { alias PrePost!("", STRINGS[0], STRINGS[1..$]) Append; }

extern(C) {
  typedef Enum StringName;
  mixin(ConstEnums!(StringName, 0x1F00, Prepend!("GL_", "VENDOR", "RENDERER", "VERSION", "EXTENSIONS")));
  typedef Enum ShadeModel;
  mixin(ConstEnums!(ShadeModel, 0x1D00, Prepend!("GL_", "FLAT", "SMOOTH")));
  typedef Bitfield AttribMask;
  mixin(ConstEnums!(AttribMask, 1, PrePost!("GL_", "_BIT",
    "CURRENT", "POINT", "LINE", "POLYGON",
    "POLYGON_STIPPLE", "PIXEL_MODE", "LIGHTING", "FOG",
    Append!("_BUFFER", "DEPTH", "ACCUM", "STENCIL"), "VIEWPORT",
    "TRANSFORM", "ENABLE", "COLOR_BUFFER", "HINT",
    "EVAL", "LIST", "TEXTURE", "SCISSORS")));
  const AttribMask GL_ALL_ATTRIB_BITS=0xFFFF_FFFF;
  typedef Enum MatrixMode;
  mixin(ConstEnums!(MatrixMode, 0x1700, Prepend!("GL_", "MODELVIEW", "PROJECTION", "TEXTURE")));

  char *glGetString(StringName);
  Enum glGetError();
}

class glException : Exception { this(char[] s) { super(s); } }

T glCheck(T)(lazy T lv) {
  void checkError() {
    auto error=glGetError();
    if (error) throw new glException(format("GL error: ", error));
  }
  static if(is(T==void)) {
    lv(); checkError;
  } else {
    auto res=lv();
    checkError;
    return res;
  }
}

extern(C) {
  void glClearColor(float red=0f, float green=0f, float blue=0f, float alpha=0f);
  void glShadeModel(ShadeModel mode);
}
void init() {
  glClearColor;
  glShadeModel(GL_FLAT);
}

extern(C) {
  void glPushMatrix();
  void glPopMatrix();
}
void glMatrix(void delegate() dg) {
  glPushMatrix(); dg(); glPopMatrix();
}

/// Look up the full type name for the OpenGL type suffix typeID
char[] glLookupType(char[] typeID) {
  foreach (type; ["byte", "double", "float", "int", "short", "ubyte", "uint", "ushort"]) {
    if(type[0..typeID.length]==typeID) return type;
  }
  assert(false, "Invalid type: "~typeID);
}

/// "type, type, type" .. length is count
char[] paramList(int count, string type) {
  if (count>1) return type~", "~paramList(count-1, type); return type;
}

char[] glColorMixin(int params, char[] typeID, bool vector) {
  return "void glColor"~(params==3?"3":"4")~typeID~(vector?"v":"")~" ("~
    (vector?glLookupType(typeID)~" *); ":paramList(params, glLookupType(typeID))~"); ");
}

/// Generate the mixin string for all the glColor variations
char[] glColors() {
  char[] res;
  foreach (count; [3, 4]) foreach (name; ["b", "d", "f", "i", "s", "ub", "ui", "us"])
    res~=glColorMixin(count, name, false)~glColorMixin(count, name, true);
  return res;
}
extern(C) mixin(glColors); /// all the glColor functions in a single concise mixin.

/// It's unsigned if it starts with 'u'. Pure simplicity.
template unsigned(T) {
  static if(T.stringof[0]=='u') const bool unsigned=true;
  else const bool unsigned=false;
}

/// A very generic glColor. Supports static arrays.
import std.traits;
void glColor(T...)(T t) {
  static if (T.length==1) { alias T[0] Thingie; const bool vector=true; alias typeof(t[0][0]) ElemType; }
  else { alias T Thingie; const bool vector=false; alias typeof(t[0]) ElemType; }
  const char[] count=Thingie.length.stringof;
  static assert(count=="3"||count=="4");
  const char[] type=(unsigned!(ElemType)?"u":"")~ElemType.stringof[0];
  static if(vector) mixin("glColor"~count~type~"v(t[0].ptr); ");
  else mixin("glColor"~count~type~"(t); ");
}

float spin=0f;
alias Tuple!(1f) Xf;
alias Tuple!(0f, 1f) Yf;
alias Tuple!(0f, 0f, 1f) Zf;
template pair(T...) { alias Tuple!(T, T) pair; }
extern(C) {
  void glClear(AttribMask mask);
  void glRotatef(float angle, float x=1, float y=0, float z=0);
  void glRectf(pair!(pair!(float)));
  void glutSwapBuffers();
  void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glCheck(glMatrix({
      glRotatef(spin, Zf);
      glColor(1f, 1f, 1f);
      glRectf(pair!(-25f), pair!(25f));
    }));
    glutSwapBuffers;
  }
}

extern(C) {
  void glutPostRedisplay();
  void spinDisplay() {
    spin+=2f;
    if (spin>360) spin-=360;
    glutPostRedisplay;
  }
}

extern(C) {
  void glViewport(pair!(pair!(int)));
  void glMatrixMode(MatrixMode);
  void glLoadIdentity();
  void glOrtho(pair!(double) leftright, pair!(double) bottomtop, pair!(double) nearfar);
  template plusminus(T...) {
    static if(T.length) alias Tuple!(T[0], -T[0], plusminus!(T[1..$])) plusminus;
    else alias Tuple!() plusminus;
  }
  void reshape(pair!(int) size) {
    glViewport(0, 0, size);
    glCheck({glMatrixMode=GL_PROJECTION; glLoadIdentity; });
    glCheck(glOrtho(plusminus!(50f, 50f, 1f)));
    glCheck({glMatrixMode=GL_MODELVIEW; glLoadIdentity; });
  }
}

extern(C) {
  typedef Enum MouseButton;
  mixin(ConstEnums!(MouseButton, 0, PrePost!("GLUT_", "_BUTTON", "LEFT", "MIDDLE", "RIGHT")));
  typedef Enum MouseState;
  mixin(ConstEnums!(MouseState, 0, Prepend!("GLUT_", "DOWN", "UP")));
  void glutIdleFunc(void function());
  void mouse(MouseButton button, MouseState state, pair!(int) where) {
    switch(button) {
      case GLUT_LEFT_BUTTON: if(state==GLUT_DOWN) glutIdleFunc=&spinDisplay; break;
      case GLUT_MIDDLE_BUTTON: if(state==GLUT_DOWN) glutIdleFunc=null; break;
      default: break;
    }
  }
}

import std.string;
extern(C) {
  void glutInit(int *argc, char **argv);
  void glutInitDisplayMode(uint mode);
  void glutInitWindowSize(pair!(int) size);
  void glutInitWindowPosition(pair!(int) pos);
  void glutDisplayFunc(void function());
  void glutReshapeFunc(void function(pair!(int) size));
  void glutMouseFunc(void function(MouseButton button, MouseState state, pair!(int) pos));

  int glutCreateWindow(char *name);
  typedef Bitfield DisplayMode;
  mixin(ConstEnums!(DisplayMode, 1, Prepend!(
    "GLUT_", "INDEX", "DOUBLE", "ACCUM", "ALPHA", "DEPTH", "STENCIL", "SKIPTHISBOGUS", "MULTISAMPLE", "STEREO", "LUMINANCE"
  )));
  const DisplayMode GLUT_RGB=0, GLUT_RGBA=0, GLUT_SINGLE=0;
  void glutMainLoop();
}

int main(string[] args) {
  /// C-ify the args
  int len=args.length;
  char *[] ptrs; foreach (entry; args) ptrs~=toStringz(entry);
  glutInit(&len, ptrs.ptr);
  glutInitDisplayMode=GLUT_DOUBLE | GLUT_RGB;
  glutInitWindowSize=pair!(250);
  glutInitWindowPosition=pair!(100);
  glutCreateWindow("foo".ptr);
  init;
  glutDisplayFunc=&display;
  glutReshapeFunc=&reshape;
  glutMouseFunc=&mouse;
  glutMainLoop;
  return 0;
}
August 01, 2007
downs wrote:
> /// A very generic glColor. Supports static arrays.
> import std.traits;
> void glColor(T...)(T t) {
>   static if (T.length==1) { alias T[0] Thingie; const bool vector=true; alias typeof(t[0][0]) ElemType; }
>   else { alias T Thingie; const bool vector=false; alias typeof(t[0]) ElemType; }
>   const char[] count=Thingie.length.stringof;
>   static assert(count=="3"||count=="4");
>   const char[] type=(unsigned!(ElemType)?"u":"")~ElemType.stringof[0];
Stupid me. I never tested this part for unsigneds.
Of course, it should be (unsigned!(ElemType)?"u"~ElemType.stringof[1]:ElemType.stringof[0]);
Sorry.
 --downs
August 03, 2007
downs wrote:
> downs wrote:
>> /// A very generic glColor. Supports static arrays.
>> import std.traits;
>> void glColor(T...)(T t) {
>>   static if (T.length==1) { alias T[0] Thingie; const bool vector=true; alias typeof(t[0][0]) ElemType; }
>>   else { alias T Thingie; const bool vector=false; alias typeof(t[0]) ElemType; }
>>   const char[] count=Thingie.length.stringof;
>>   static assert(count=="3"||count=="4");
>>   const char[] type=(unsigned!(ElemType)?"u":"")~ElemType.stringof[0];
> Stupid me. I never tested this part for unsigneds.
> Of course, it should be (unsigned!(ElemType)?"u"~ElemType.stringof[1]:ElemType.stringof[0]);
> Sorry.
>  --downs

Still doesn't compile with DMD 1.020.
So basically you're generating the GL headers using metaprogramming?
Crazy stuff. <g>