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.
/*
* 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;
}