DominScript Back to dominscript.com
Demo source

The script behind the cube. opengl_ffmpeg_script_driven_demo.dom

The complete source of the demo shown on the front page. Roughly 600 lines of DominScript that opens an OpenGL window, decodes a video stream with FFmpeg, and renders a spinning textured cube — with every geometric and rendering decision visible in this file.

The OpenGL plugin underneath is a thin glue layer over real GL entry points (gl.Begin, gl.Vertex3f, gl.Frustum, ...). There is no rendering engine hidden behind a high-level façade; what you read here is what runs.

Lines: ~600 Plugins used: opengl, ffmpeg, image, blob, script_state, os, struct, print Runtime: Linux (X11/Wayland) · Windows
opengl_ffmpeg_script_driven_demo.dom dominscript
/*
 * opengl_ffmpeg_script_driven_demo.dom
 *
 * Opens a 1280x720 OpenGL window and builds a spinning textured cube:
 * four side faces show a video stream decoded by FFmpeg in real time,
 * top and bottom faces show the project logo. Mouse wheel zooms the
 * camera, left-drag rotates the cube.
 *
 * Every drawing decision lives in this script. The OpenGL plugin is
 * a thin glue layer over real GL entry points (gl.Begin, gl.Vertex3f,
 * gl.Frustum, gl.Rotatef, ...) -- there is no hidden engine. If you
 * want to learn how the cube is built, you read this file. There is
 * nowhere else to hide it.
 *
 * State sharing without globals:
 *     DominScript intentionally has no file-level (global) variables.
 *     Callbacks have a fixed `callback i32 NAME(ref blob $X)`
 *     signature, so there is no second parameter for app state.
 *     Instead, the script_state plugin owns one byte buffer; both the
 *     main loop and the callbacks bind a non-owning ref-blob view over
 *     it via blob.RefFromPointer(script_state.GetPointer(),
 *     script_state.GetSize()) and `$S assign RenderState;`. Every
 *     caller sees the same bytes; the assign just attaches typed
 *     field offsets, no copying.
 *
 * Required runtime:
 *     A display server: X11 or Wayland on Linux, a desktop session on
 *     Windows. Built plugins: opengl, ffmpeg, image, blob, script_state.
 */

plugin "builtin:os";
plugin "builtin:struct";
plugin "../plugins/blob/BlobPlugin";
plugin "../plugins/print/PrintPlugin";
plugin "../plugins/script_state/ScriptStatePlugin";
plugin "../plugins/opengl/OpenGlPlugin";
plugin "../plugins/ffmpeg/FfmpegPlugin";
plugin "../plugins/image/ImagePlugin";

#define WINDOW_WIDTH  1280
#define WINDOW_HEIGHT  720
#define VIDEO_WIDTH    640
#define VIDEO_HEIGHT   360
#define LOGO_WIDTH     620
#define LOGO_HEIGHT    220

/* GL constants, copied verbatim from <GL/gl.h>. The plugin's gl.*
   wrappers take these by value, just like glBegin / glEnable / etc.
   in C. Picking them out by name here keeps the script self-explanatory
   instead of sprinkling magic hex numbers through gl.Begin / gl.Enable
   call sites. */
#define GL_COLOR_BUFFER_BIT     16384  /* 0x4000 */
#define GL_DEPTH_BUFFER_BIT     256    /* 0x0100 */
#define GL_TEXTURE_2D           3553   /* 0x0DE1 */
#define GL_QUADS                7      /* 0x0007 */
#define GL_LINES                1      /* 0x0001 */
#define GL_DEPTH_TEST           2929   /* 0x0B71 */
#define GL_BLEND                3042   /* 0x0BE2 */
#define GL_SRC_ALPHA            770    /* 0x0302 */
#define GL_ONE_MINUS_SRC_ALPHA  771    /* 0x0303 */
#define GL_PROJECTION           5889   /* 0x1701 */
#define GL_MODELVIEW            5888   /* 0x1700 */
#define GL_ALPHA_TEST           3008   /* 0x0BC0 */
#define GL_GREATER              516    /* 0x0204 */

/* Event packet layout: the opengl plugin writes raw input events into
   a borrowed blob, and the script overlays this typed view on top. */
#define GL_EVENT_NONE        0
#define GL_EVENT_QUIT        1
#define GL_EVENT_RESIZE      2
#define GL_EVENT_MOUSE_DOWN  3
#define GL_EVENT_MOUSE_UP    4
#define GL_EVENT_MOUSE_MOVE  5
#define GL_EVENT_MOUSE_WHEEL 6
#define GL_MOUSE_LEFT        1

#define FACE_FRONT  0
#define FACE_BACK   1
#define FACE_RIGHT  2
#define FACE_LEFT   3
#define FACE_TOP    4
#define FACE_BOTTOM 5

#define AUTO_SPEED_X 0.23
#define AUTO_SPEED_Y 0.37
#define AUTO_SPEED_Z 0.11

#define CUBE_PANEL_HALF 0.78

/* ----- Shared state struct ------------------------------------------ */
struct RenderState packet
{
    i32     WindowWidth;
    i32     WindowHeight;
    i32     VideoSlot;
    i32     LogoSlot;
    double  CameraDistance;
    double  ViewScale;
    double  AngleX;
    double  AngleY;
    double  AngleZ;
    double  SpeedX;
    double  SpeedY;
    double  SpeedZ;
    double  CubeGap;
    double  EdgeR;
    double  EdgeG;
    double  EdgeB;
    double  EdgeWidth;
}

struct GlEvent packet
{
    u8  magic0;
    u8  magic1;
    u8  magic2;
    u8  magic3;
    u8  version;
    u8  type;
    u8  button;
    u8  pad0;
    i16 x;
    i16 y;
    i16 dx;
    i16 dy;
    i16 wheel;
    u16 width;
    u16 height;
}

void GetCubeFaceGeometry(i32 $Face, double $CubeGap,
                         ref double $Ax, ref double $Ay, ref double $Az,
                         ref double $Bx, ref double $By, ref double $Bz,
                         ref double $Cx, ref double $Cy, ref double $Cz,
                         ref double $Dx, ref double $Dy, ref double $Dz)
{
    double $P = CUBE_PANEL_HALF;
    double $D = 1.05 + ($CubeGap * 0.40);

    if ($Face == FACE_FRONT)
    {
        $Ax = 0.0 - $P; $Ay = 0.0 - $P; $Az =  $D;
        $Bx =       $P; $By = 0.0 - $P; $Bz =  $D;
        $Cx =       $P; $Cy =       $P; $Cz =  $D;
        $Dx = 0.0 - $P; $Dy =       $P; $Dz =  $D;
    }
    else if ($Face == FACE_BACK)
    {
        $Ax =       $P; $Ay = 0.0 - $P; $Az = 0.0 - $D;
        $Bx = 0.0 - $P; $By = 0.0 - $P; $Bz = 0.0 - $D;
        $Cx = 0.0 - $P; $Cy =       $P; $Cz = 0.0 - $D;
        $Dx =       $P; $Dy =       $P; $Dz = 0.0 - $D;
    }
    else if ($Face == FACE_RIGHT)
    {
        $Ax =  $D; $Ay = 0.0 - $P; $Az =       $P;
        $Bx =  $D; $By = 0.0 - $P; $Bz = 0.0 - $P;
        $Cx =  $D; $Cy =       $P; $Cz = 0.0 - $P;
        $Dx =  $D; $Dy =       $P; $Dz =       $P;
    }
    else if ($Face == FACE_LEFT)
    {
        $Ax = 0.0 - $D; $Ay = 0.0 - $P; $Az = 0.0 - $P;
        $Bx = 0.0 - $D; $By = 0.0 - $P; $Bz =       $P;
        $Cx = 0.0 - $D; $Cy =       $P; $Cz =       $P;
        $Dx = 0.0 - $D; $Dy =       $P; $Dz = 0.0 - $P;
    }
    else if ($Face == FACE_TOP)
    {
        $Ax = 0.0 - $P; $Ay =  $D; $Az =       $P;
        $Bx =       $P; $By =  $D; $Bz =       $P;
        $Cx =       $P; $Cy =  $D; $Cz = 0.0 - $P;
        $Dx = 0.0 - $P; $Dy =  $D; $Dz = 0.0 - $P;
    }
    else
    {
        $Ax = 0.0 - $P; $Ay = 0.0 - $D; $Az = 0.0 - $P;
        $Bx =       $P; $By = 0.0 - $D; $Bz = 0.0 - $P;
        $Cx =       $P; $Cy = 0.0 - $D; $Cz =       $P;
        $Dx = 0.0 - $P; $Dy = 0.0 - $D; $Dz =       $P;
    }
}

void FitTextureQuad(double $Ax, double $Ay, double $Az,
                    double $Bx, double $By, double $Bz,
                    double $Cx, double $Cy, double $Cz,
                    double $Dx, double $Dy, double $Dz,
                    i32    $TexW,
                    i32    $TexH,
                    ref double $Q0x, ref double $Q0y, ref double $Q0z,
                    ref double $Q1x, ref double $Q1y, ref double $Q1z,
                    ref double $Q2x, ref double $Q2y, ref double $Q2z,
                    ref double $Q3x, ref double $Q3y, ref double $Q3z)
{
    double $Aspect;
    double $ScaleX = 1.0;
    double $ScaleY = 1.0;
    double $StartX;
    double $StartY;
    double $EndX;
    double $EndY;
    double $L0x; double $L0y; double $L0z;
    double $R0x; double $R0y; double $R0z;
    double $L1x; double $L1y; double $L1z;
    double $R1x; double $R1y; double $R1z;

    if (($TexW <= 0) || ($TexH <= 0))
    {
        $Q0x = $Ax; $Q0y = $Ay; $Q0z = $Az;
        $Q1x = $Bx; $Q1y = $By; $Q1z = $Bz;
        $Q2x = $Cx; $Q2y = $Cy; $Q2z = $Cz;
        $Q3x = $Dx; $Q3y = $Dy; $Q3z = $Dz;
        return;
    }

    $Aspect = (double)$TexW / (double)$TexH;
    if ($Aspect > 1.0)
    {
        $ScaleY = 1.0 / $Aspect;
    }
    else
    {
        $ScaleX = $Aspect;
    }

    $StartX = (1.0 - $ScaleX) * 0.5;
    $EndX   = 1.0 - $StartX;
    $StartY = (1.0 - $ScaleY) * 0.5;
    $EndY   = 1.0 - $StartY;

    $L0x = $Ax + (($Dx - $Ax) * $StartY); $L0y = $Ay + (($Dy - $Ay) * $StartY); $L0z = $Az + (($Dz - $Az) * $StartY);
    $R0x = $Bx + (($Cx - $Bx) * $StartY); $R0y = $By + (($Cy - $By) * $StartY); $R0z = $Bz + (($Cz - $Bz) * $StartY);
    $L1x = $Ax + (($Dx - $Ax) * $EndY);   $L1y = $Ay + (($Dy - $Ay) * $EndY);   $L1z = $Az + (($Dz - $Az) * $EndY);
    $R1x = $Bx + (($Cx - $Bx) * $EndY);   $R1y = $By + (($Cy - $By) * $EndY);   $R1z = $Bz + (($Cz - $Bz) * $EndY);

    $Q0x = $L0x + (($R0x - $L0x) * $StartX); $Q0y = $L0y + (($R0y - $L0y) * $StartX); $Q0z = $L0z + (($R0z - $L0z) * $StartX);
    $Q1x = $L0x + (($R0x - $L0x) * $EndX);   $Q1y = $L0y + (($R0y - $L0y) * $EndX);   $Q1z = $L0z + (($R0z - $L0z) * $EndX);
    $Q2x = $L1x + (($R1x - $L1x) * $EndX);   $Q2y = $L1y + (($R1y - $L1y) * $EndX);   $Q2z = $L1z + (($R1z - $L1z) * $EndX);
    $Q3x = $L1x + (($R1x - $L1x) * $StartX); $Q3y = $L1y + (($R1y - $L1y) * $StartX); $Q3z = $L1z + (($R1z - $L1z) * $StartX);
}

void DrawTexturedQuad(i32 $TextureSlot,
                      double $Ax, double $Ay, double $Az,
                      double $Bx, double $By, double $Bz,
                      double $Cx, double $Cy, double $Cz,
                      double $Dx, double $Dy, double $Dz)
{
    gl.Enable(GL_TEXTURE_2D);
    gl.Enable(GL_BLEND);
    gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    /* Alpha test: transparent pixels (alpha <= 0.003) get DISCARDED
       before the depth-buffer write, so a logo's transparent corners
       don't block the opposite face of the cube from showing through. */
    gl.Enable(GL_ALPHA_TEST);
    gl.AlphaFunc(GL_GREATER, 0.003);
    gl.BindTexture(GL_TEXTURE_2D, $TextureSlot);
    gl.Color3f(1.0, 1.0, 1.0);
    gl.Begin(GL_QUADS);
    gl.TexCoord2f(0.0, 1.0); gl.Vertex3f($Ax, $Ay, $Az);
    gl.TexCoord2f(1.0, 1.0); gl.Vertex3f($Bx, $By, $Bz);
    gl.TexCoord2f(1.0, 0.0); gl.Vertex3f($Cx, $Cy, $Cz);
    gl.TexCoord2f(0.0, 0.0); gl.Vertex3f($Dx, $Dy, $Dz);
    gl.End();
    gl.Disable(GL_ALPHA_TEST);
}

void DrawQuadEdges(double $EdgeR, double $EdgeG, double $EdgeB,
                   double $Ax, double $Ay, double $Az,
                   double $Bx, double $By, double $Bz,
                   double $Cx, double $Cy, double $Cz,
                   double $Dx, double $Dy, double $Dz)
{
    gl.Disable(GL_TEXTURE_2D);
    gl.Color3f($EdgeR, $EdgeG, $EdgeB);
    gl.Begin(GL_LINES);
    gl.Vertex3f($Ax, $Ay, $Az); gl.Vertex3f($Bx, $By, $Bz);
    gl.Vertex3f($Bx, $By, $Bz); gl.Vertex3f($Cx, $Cy, $Cz);
    gl.Vertex3f($Cx, $Cy, $Cz); gl.Vertex3f($Dx, $Dy, $Dz);
    gl.Vertex3f($Dx, $Dy, $Dz); gl.Vertex3f($Ax, $Ay, $Az);
    gl.End();
}

void DrawCubeFace(i32 $Face, i32 $TextureSlot, i32 $TexW, i32 $TexH,
                  double $CubeGap,
                  double $EdgeR, double $EdgeG, double $EdgeB)
{
    double $Ax = 0.0; double $Ay = 0.0; double $Az = 0.0;
    double $Bx = 0.0; double $By = 0.0; double $Bz = 0.0;
    double $Cx = 0.0; double $Cy = 0.0; double $Cz = 0.0;
    double $Dx = 0.0; double $Dy = 0.0; double $Dz = 0.0;
    double $Q0x = 0.0; double $Q0y = 0.0; double $Q0z = 0.0;
    double $Q1x = 0.0; double $Q1y = 0.0; double $Q1z = 0.0;
    double $Q2x = 0.0; double $Q2y = 0.0; double $Q2z = 0.0;
    double $Q3x = 0.0; double $Q3y = 0.0; double $Q3z = 0.0;

    GetCubeFaceGeometry($Face, $CubeGap,
                        &$Ax, &$Ay, &$Az,
                        &$Bx, &$By, &$Bz,
                        &$Cx, &$Cy, &$Cz,
                        &$Dx, &$Dy, &$Dz);

    FitTextureQuad($Ax, $Ay, $Az,
                   $Bx, $By, $Bz,
                   $Cx, $Cy, $Cz,
                   $Dx, $Dy, $Dz,
                   $TexW, $TexH,
                   &$Q0x, &$Q0y, &$Q0z,
                   &$Q1x, &$Q1y, &$Q1z,
                   &$Q2x, &$Q2y, &$Q2z,
                   &$Q3x, &$Q3y, &$Q3z);

    DrawTexturedQuad($TextureSlot,
                     $Q0x, $Q0y, $Q0z,
                     $Q1x, $Q1y, $Q1z,
                     $Q2x, $Q2y, $Q2z,
                     $Q3x, $Q3y, $Q3z);

    DrawQuadEdges($EdgeR, $EdgeG, $EdgeB,
                  $Ax, $Ay, $Az,
                  $Bx, $By, $Bz,
                  $Cx, $Cy, $Cz,
                  $Dx, $Dy, $Dz);
}

void DrawBackgroundGradient(double $TopR,    double $TopG,    double $TopB,
                            double $BottomR, double $BottomG, double $BottomB)
{
    double $FarZ        = 0.0 - 18.0;
    double $SkyTop      =        7.0;
    double $SkyBottom   = 0.0 -  2.0;
    double $SkyHalfW    =       18.0;

    gl.Disable(GL_TEXTURE_2D);
    gl.Begin(GL_QUADS);
    gl.Color3f($TopR, $TopG, $TopB);
    gl.Vertex3f(0.0 - $SkyHalfW, $SkyTop,    $FarZ);
    gl.Vertex3f(      $SkyHalfW, $SkyTop,    $FarZ);
    gl.Color3f($BottomR, $BottomG, $BottomB);
    gl.Vertex3f(      $SkyHalfW, $SkyBottom, $FarZ);
    gl.Vertex3f(0.0 - $SkyHalfW, $SkyBottom, $FarZ);
    gl.End();
}

void DrawGroundGradient(double $NearR, double $NearG, double $NearB,
                        double $FarR,  double $FarG,  double $FarB)
{
    double $GroundY  = 0.0 -  2.3;
    double $GridNear = 0.0 -  3.0;
    double $GridFar  = 0.0 - 18.0;

    gl.Disable(GL_TEXTURE_2D);
    gl.Begin(GL_QUADS);
    gl.Color3f($NearR, $NearG, $NearB);
    gl.Vertex3f(0.0 - 12.0, $GroundY, $GridNear);
    gl.Vertex3f(      12.0, $GroundY, $GridNear);
    gl.Color3f($FarR, $FarG, $FarB);
    gl.Vertex3f(      20.0, $GroundY, $GridFar);
    gl.Vertex3f(0.0 - 20.0, $GroundY, $GridFar);
    gl.End();
}

void SetupPerspective(i32 $WindowWidth, i32 $WindowHeight,
                      double $ViewScale, double $CameraDistance)
{
    double $Aspect;
    double $Top;
    double $Right;
    i32    $H;

    $H = $WindowHeight;
    if ($H <= 0)
    {
        $H = 1;
    }

    $Aspect = (double)$WindowWidth / (double)$H;
    $Top    = $ViewScale;
    $Right  = $Top * $Aspect;

    gl.MatrixMode(GL_PROJECTION);
    gl.LoadIdentity();
    gl.Frustum(0.0 - $Right, $Right, 0.0 - $Top, $Top, 1.0, 20.0);
    gl.MatrixMode(GL_MODELVIEW);
    gl.LoadIdentity();
    gl.Translatef(0.0, 0.0, 0.0 - $CameraDistance);
}

void RenderScriptFrame()
{
    ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                       script_state.GetSize());
    $S assign RenderState;

    if (gl.MakeCurrent() == 0)
    {
        return;
    }

    $S.AngleX = $S.AngleX + $S.SpeedX;
    $S.AngleY = $S.AngleY + $S.SpeedY;
    $S.AngleZ = $S.AngleZ + $S.SpeedZ;

    gl.Viewport(0, 0, $S.WindowWidth, $S.WindowHeight);
    gl.ClearColor(0.015, 0.020, 0.035, 1.0);
    gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.Enable(GL_DEPTH_TEST);
    gl.Enable(GL_BLEND);
    gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    SetupPerspective($S.WindowWidth, $S.WindowHeight,
                     $S.ViewScale, $S.CameraDistance);

    DrawBackgroundGradient(0.01, 0.02, 0.08,
                           0.18, 0.07, 0.18);
    DrawGroundGradient(0.16, 0.16, 0.16,
                       0.08, 0.08, 0.08);

    gl.Rotatef($S.AngleX, 1.0, 0.0, 0.0);
    gl.Rotatef($S.AngleY, 0.0, 1.0, 0.0);
    gl.Rotatef($S.AngleZ, 0.0, 0.0, 1.0);
    gl.LineWidth($S.EdgeWidth);

    DrawCubeFace(FACE_FRONT,  $S.VideoSlot, VIDEO_WIDTH, VIDEO_HEIGHT,
                 $S.CubeGap, $S.EdgeR, $S.EdgeG, $S.EdgeB);
    DrawCubeFace(FACE_BACK,   $S.VideoSlot, VIDEO_WIDTH, VIDEO_HEIGHT,
                 $S.CubeGap, $S.EdgeR, $S.EdgeG, $S.EdgeB);
    DrawCubeFace(FACE_RIGHT,  $S.VideoSlot, VIDEO_WIDTH, VIDEO_HEIGHT,
                 $S.CubeGap, $S.EdgeR, $S.EdgeG, $S.EdgeB);
    DrawCubeFace(FACE_LEFT,   $S.VideoSlot, VIDEO_WIDTH, VIDEO_HEIGHT,
                 $S.CubeGap, $S.EdgeR, $S.EdgeG, $S.EdgeB);
    DrawCubeFace(FACE_TOP,    $S.LogoSlot,  LOGO_WIDTH,  LOGO_HEIGHT,
                 $S.CubeGap, $S.EdgeR, $S.EdgeG, $S.EdgeB);
    DrawCubeFace(FACE_BOTTOM, $S.LogoSlot,  LOGO_WIDTH,  LOGO_HEIGHT,
                 $S.CubeGap, $S.EdgeR, $S.EdgeG, $S.EdgeB);

    gl.SwapBuffers();
    gl.UnbindCurrent();
}

callback i32 OnVideoFrame(ref blob $FramePacket)
{
    ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                       script_state.GetSize());
    $S assign RenderState;
    opengl.UpdateTexture($S.VideoSlot, $FramePacket);
    return 1;
}

double ClampCameraDistance(double $Distance)
{
    if ($Distance < 1.5)
    {
        return 1.5;
    }
    if ($Distance > 20.0)
    {
        return 20.0;
    }
    return $Distance;
}

callback i32 OnOpenGlEvent(ref blob $EventPacket)
{
    ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                       script_state.GetSize());
    $S assign RenderState;
    $EventPacket assign GlEvent;

    if ($EventPacket.type == GL_EVENT_RESIZE)
    {
        $S.WindowWidth  = (i32)$EventPacket.width;
        $S.WindowHeight = (i32)$EventPacket.height;
    }
    else if ($EventPacket.type == GL_EVENT_MOUSE_WHEEL)
    {
        if ($EventPacket.wheel > 0)
        {
            $S.CameraDistance = $S.CameraDistance - (0.25 * (double)$EventPacket.wheel);
        }
        else if ($EventPacket.wheel < 0)
        {
            $S.CameraDistance = $S.CameraDistance + (0.25 * (double)(0 - $EventPacket.wheel));
        }
        $S.CameraDistance = ClampCameraDistance($S.CameraDistance);
    }
    else if ($EventPacket.type == GL_EVENT_MOUSE_DOWN)
    {
        if ($EventPacket.button == GL_MOUSE_LEFT)
        {
            $S.SpeedX = 0.0;
            $S.SpeedY = 0.0;
            $S.SpeedZ = 0.0;
        }
    }
    else if ($EventPacket.type == GL_EVENT_MOUSE_UP)
    {
        if ($EventPacket.button == GL_MOUSE_LEFT)
        {
            $S.SpeedX = AUTO_SPEED_X;
            $S.SpeedY = AUTO_SPEED_Y;
            $S.SpeedZ = AUTO_SPEED_Z;
        }
    }
    else if ($EventPacket.type == GL_EVENT_MOUSE_MOVE)
    {
        if ($EventPacket.button == GL_MOUSE_LEFT)
        {
            $S.AngleY = $S.AngleY + ((double)$EventPacket.dx * 0.50);
            $S.AngleX = $S.AngleX + ((double)$EventPacket.dy * 0.50);
        }
    }

    return 1;
}

i32 main()
{
    i32  $Video     = 0;
    i32  $Opened    = 0;
    blob $LogoPacket[0];
    i32  $Frame     = 0;
    i32  $Delivered = 0;

    $Opened = opengl.Open(WINDOW_WIDTH, WINDOW_HEIGHT,
                          "Domin OpenGL FFmpeg demo");
    if ($Opened == 0)
    {
        return 0;  /* No display available -- exit cleanly. */
    }

    script_state.Allocate(sizeof(RenderState));
    {
        ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                           script_state.GetSize());
        $S assign RenderState;

        $S.WindowWidth    = WINDOW_WIDTH;
        $S.WindowHeight   = WINDOW_HEIGHT;
        $S.CameraDistance = 4.2;
        $S.ViewScale      = 0.45;
        $S.AngleX         =       18.0;
        $S.AngleY         = 0.0 - 24.0;
        $S.AngleZ         = 0.0;
        $S.SpeedX         = AUTO_SPEED_X;
        $S.SpeedY         = AUTO_SPEED_Y;
        $S.SpeedZ         = AUTO_SPEED_Z;
        $S.CubeGap        = 0.45;
        $S.EdgeR          = 0.95;
        $S.EdgeG          = 0.95;
        $S.EdgeB          = 0.80;
        $S.EdgeWidth      = 3.0;

        $S.VideoSlot = opengl.CreateTextureSlot();
        $S.LogoSlot  = opengl.CreateTextureSlot();
    }

    $LogoPacket = image.Load("Assets/Images/domin_logo.png");
    {
        ref blob &$S = blob.RefFromPointer(script_state.GetPointer(),
                                           script_state.GetSize());
        $S assign RenderState;
        opengl.UpdateTexture($S.LogoSlot, $LogoPacket);
    }

    ffmpeg.SetOutputSize(VIDEO_WIDTH, VIDEO_HEIGHT);
    ffmpeg.SetRealtime(1);
    ffmpeg.SetVideoOnly(1);
    $Video = ffmpeg.Open("Assets/Videos/demo.mp4");
    ffmpeg.StartAudio($Video);

    while (opengl.IsOpen() != 0)
    {
        opengl.PumpEvents("OnOpenGlEvent");

        if (opengl.IsOpen() == 0)
        {
            break;
        }

        $Delivered = ffmpeg.DecodeNext($Video, "OnVideoFrame");
        if ($Delivered == 0)
        {
            ffmpeg.Rewind($Video);
        }

        if (opengl.IsOpen() != 0)
        {
            RenderScriptFrame();
        }
        $Frame = $Frame + 1;
    }

    ffmpeg.Close($Video);
    opengl.Close();
    script_state.Free();
    return 0;
}