Guide to develop 4k Intros for MorphOS
  • Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    I will publish some of my code to develop 4k intro for our favourite operative system, Windows 8... eheemm, no, seriously, for our favourite system, MorphOS.

    For this guide, we will need MorphOS SDK, and lzma_loader to crunch the executable output.

    MorphOS SDK
    Lzma Loader

    Develop a 4k intro can be divided on 4 tasks:

    Statup code.
    Graphics code.
    Audio code.
    Intro itself.


    And we will add an extra task, a stack virtual machine.

    Startup code

    Some time ago I published some code to do this task, I have received help from this forum to optimize this part, specially from Piru.

    This code is based on that startup code published, I have made small changes but basically the tasks are:

    Initialize OpenGL Context.
    Initialize AHI device.
    Calculate sinus and cosinus tables.


    So here is the startup code:

    Code:

    /*
    * 4Kb Intro startup by Pedro Gil (Balrog Soft)
    * www.amigaskool.net
    *
    * TinyGL and AHI device startup code,
    * and music synth for 4k intros.
    * The song was ripped from howagen intro by tonic.
    *
    * Special thanks to Piru for his small opts,
    * and Morphzone.org people!
    *
    */

    #include <stdio.h>
    #include <limits.h>

    #include <devices/ahi.h>

    #include <proto/exec.h>
    #include <proto/dos.h>
    #include <proto/tinygl.h>
    #include <proto/ahi.h>


    #define ubyte unsigned char
    #define uint unsigned int

    #define byte signed char

    #ifndef M_PI
    #define M_PI 3.14159265
    #define M_PI2 6.283185
    #endif

    // Sinus and cosinus arrays have 720 elements

    #define TABLE_DEGREES 720
    #define DEGREE_STEP M_PI2 / TABLE_DEGREES

    // Define our ahi device for 8 bits
    // signed mono buffer with a frequency
    // of 8000 samples

    #define FREQUENCY 8000
    #define TYPE AHIST_M8S
    #define BUFFERSIZE 24000

    static void display(void);
    static void keys(unsigned char c, int x, int y);
    static void synth4k(void);


    struct ExecBase *SysBase;
    struct Library *TinyGLBase;


    GLContext *__tglContext;

    static struct MsgPort *AHImp = NULL;
    static struct AHIRequest *AHIios1 = NULL;
    static struct AHIRequest *AHIios2 = NULL;
    static struct AHIRequest *link = NULL;

    static struct Message *wbstartupmsg = NULL;
    static struct Process *self = NULL;

    static void *tmp;
    static BOOL init = TRUE;

    static byte AHIDevice;

    // Here are defined two audio buffers,
    // it is similar to a screen double buffer
    static byte buffer1[BUFFERSIZE], buffer2[BUFFERSIZE];
    static byte *p1=buffer1, *p2=buffer2;
    static uint signals;
    static int t, i, j, n;

    // Our sinus and cosinus tables
    static float sinus[TABLE_DEGREES], cosinus[TABLE_DEGREES],
    a = 0, b = 1, c, d = DEGREE_STEP;

    #define __TEXTSECTION__ __attribute__((section(".text")))


    #ifdef NOSTARTUPFILES
    int entry(void)
    #else
    int main(void)
    #endif
    {
    SysBase = *(struct ExecBase **) 4;

    wbstartupmsg = 0;
    self = (struct Process *) FindTask(0);
    if (self->pr_CLI == 0)
    {
    WaitPort(&self->pr_MsgPort);
    wbstartupmsg = GetMsg(&self->pr_MsgPort);
    }

    TinyGLBase = OpenLibrary("tinygl.library", 50);

    if (TinyGLBase)
    {

    // Initialize AHI device

    AHImp=CreateMsgPort();
    #ifndef REMOVE
    if(AHImp != NULL)
    {
    #endif
    AHIios1 = (struct AHIRequest *) CreateIORequest(AHImp, sizeof(struct AHIRequest));
    AHIios1->ahir_Version = 4;
    AHIDevice = OpenDevice(AHINAME, 0, (struct IORequest *)AHIios1, 0);
    #ifndef REMOVE
    }
    #endif

    // Make a copy of the request (for double buffering)

    AHIios2 = AllocVec(sizeof(struct AHIRequest), MEMF_ANY);
    CopyMem(AHIios1, AHIios2, sizeof(struct AHIRequest));

    // Generate sinus and cosinus tables
    i = TABLE_DEGREES;
    while (i)
    {
    c = a + b * d;
    b = b - c * d;
    sinus [TABLE_DEGREES-i] = c;
    cosinus[TABLE_DEGREES-i] = b;
    a = c;
    i--;
    }
    c = 0;

    __tglContext = GLInit();
    if (__tglContext)
    {
    // Initialize TinyGL

    glutInit(NULL,NULL);

    glutInitDisplayMode(GLUT_RGBA);

    #ifndef REMOVE
    glutInitWindowSize(640, 480);
    #endif

    glutFullScreen();

    glutCreateWindow(NULL);

    #ifndef REMOVE
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    #endif

    // Setting blending settings

    glEnable(GL_BLEND); // Enable Blending

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    glEnableClientState(GL_VERTEX_ARRAY);

    // Setting projection, P2D define a
    // 2D projection for 2d graphics.
    glMatrixMode(GL_PROJECTION);

    #ifdef P2D
    glOrtho(320.0f, -320.0f,
    240.0f, -240.0f,
    -1.0f, 1.0f);
    #else
    gluPerspective(80, 1.333333f, 1.0, 500.0);
    glTranslatef(0.0f, 0.0f, -240.0f);
    #endif

    glMatrixMode(GL_MODELVIEW);

    // Some unused stuff

    //glScalef(2.0f,2.0f,0.0f);
    //glEnable(GL_LINE_SMOOTH);

    //glHint(TGL_CORRECT_NORMALS_HINT, GL_FASTEST);
    //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

    // Define our main key and idle functions

    glutKeyboardFunc(keys);

    glutIdleFunc(display);

    // And enter to the main loop.

    glutMainLoop();

    // Close TinyGL Context
    GLClose(__tglContext);
    }

    // Abort any pending AHI iorequests
    AbortIO((struct IORequest *) AHIios1);
    WaitIO((struct IORequest *) AHIios1);

    if(link) // Only if the second request was started
    {
    AbortIO((struct IORequest *) AHIios2);
    WaitIO((struct IORequest *) AHIios2);
    }

    if(!AHIDevice)
    CloseDevice((struct IORequest *)AHIios1);

    DeleteIORequest((struct IORequest *)AHIios1);
    FreeVec(AHIios2);

    // Close TinyGL lib
    CloseLibrary(TinyGLBase);
    }

    if (wbstartupmsg)
    {
    Forbid();
    ReplyMsg(wbstartupmsg);
    }

    return 0;
    }


    static void keys(unsigned char c, int x, int y)
    {
    // Send a fake CTRL+C signal to exit
    if (c == 0x1b)
    {
    SetSignal(SIGBREAKF_CTRL_C, SIGBREAKF_CTRL_C);
    }
    }

    static void display(void)
    {
    // Ahi device handle code

    // Send io request to the device
    if ((signals & (1L << AHImp->mp_SigBit) || init))
    {
    if (init && link)
    init = FALSE;
    if (!link)
    synth4k();
    // AHIios1->ahir_Std.io_Message.mn_Node.ln_Pri = 0;
    AHIios1->ahir_Std.io_Command = CMD_WRITE;
    AHIios1->ahir_Std.io_Data = p1;
    AHIios1->ahir_Std.io_Length = BUFFERSIZE;
    // AHIios1->ahir_Std.io_Offset = 0;
    AHIios1->ahir_Frequency = FREQUENCY;
    AHIios1->ahir_Type = TYPE;
    AHIios1->ahir_Volume = 0x10000; // Full volume
    AHIios1->ahir_Position = 0x8000; // Centered
    AHIios1->ahir_Link = link;
    SendIO((struct IORequest *) AHIios1);
    }

    // Check if there are any signals from ahi device
    if(link)
    {
    // Send a fake signal to not stop the loop
    SetSignal(SIGBREAKF_CTRL_D, SIGBREAKF_CTRL_D);
    signals = Wait(SIGBREAKF_CTRL_D | (1L << AHImp->mp_SigBit));
    }

    link = AHIios1;

    // Swap ahi buffers
    if ((signals & (1L << AHImp->mp_SigBit)) || init)
    {
    if (!init)
    WaitIO((struct IORequest *) AHIios2);

    // Swap buffer and request pointers, and restart
    tmp = p1;
    p1 = p2;
    p2 = tmp;

    synth4k();

    tmp = AHIios1;
    AHIios1 = AHIios2;
    AHIios2 = tmp;
    }

    // Here goes the drawing stuff!

    glClear(GL_COLOR_BUFFER_BIT); // Clear Screen Buffer


    glutSwapBuffers();

    }

    // This is the future 4k synth
    static void synth4k(void)
    {

    }

    #ifdef NOSTARTUPFILES
    /* __abox__ symbol is required or else the binary is loaded as PowerUP app */
    const int __abox__ __TEXTSECTION__ = 1;
    #endif


    entry function does all the initizalization stuff and start the main loop.
    keys function only check if escape key is pressed and send a signal to exit.
    display function does all graphic stuff and also swaps ahi buffers if need.
    synth4k is reserved to fill the next audio buffer with our song.

    To build this code, you will need a makefile, don't you?

    Code:

    CC = gcc
    CPP = g++
    LD = ld
    STRIP = strip
    CFLAGS = -O1 -s -fomit-frame-pointer -noixemul -ISDK:tinygl-sdk/include

    NOSTARTUPFILES = 1
    REMOVE = 1
    2D_PROJECTION = 1

    ifdef NOSTARTUPFILES
    CFLAGS+= -DNOSTARTUPFILES -nostartfiles
    endif

    ifdef REMOVE
    CFLAGS+= -DREMOVE
    endif

    ifdef 2D_PROJECTION
    CFLAGS+= -DP2D
    endif

    INTRO_OBJS = intro.o

    all: intro

    intro: $(INTRO_OBJS)
    $(CC) $(CFLAGS) -o $@ $(INTRO_OBJS)
    $(STRIP) -s -R .comment -R .gnu.version -R .gnu.version_d -R .gnu.version_r $@
    chmod u+x $@

    intro_map:
    $(CC) $(CFLAGS) intro.c -Wl,-Map=foobar.map,--traditional-format

    intro_asm:
    $(CC) $(CFLAGS) -save-temps -o $@ $(INTRO_OBJS)

    clean:
    rm -f *.o intro


    Ok, but this executable is sooo big! It's near 4k limit, what to do? lzma loader will save your butt, again Piru made an excellent job, and I obtain an executable of 1660 bytes, maybe too much for a startup code, but it's not so bad.

    You can put lzma loader files inside your build directory and use this script to get your intro crunched on ram disk.

    Code:

    copy lzmaLoader_shared ram:intro
    lzma e intro -so >> ram:intro


    You have it, a startup code for 4k intros, but where is the fun? a black screen and no sound? On the next episode, beloved childrens, we will add a 4k synth with oscillators, envelope, tracks and patterns support, and all of this for only 1kb! enough to make a sweeeet chip tune!

    For lazy people like me, you can download all the files here:

    http://www.amigaskool.net/download/4k_intro_1.lha

    [ Edited by BalrogSoft 18.05.2015 - 21:49 ]
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »13.05.15 - 23:14
    Profile Visit Website
  • Yokemate of Keyboards
    Yokemate of Keyboards
    takemehomegrandma
    Posts: 2720 from 2003/2/24
    Both thumbs up!

    :-)
    MorphOS is Amiga done right! :-)
    MorphOS NG will be AROS done right! :-)
  • »13.05.15 - 23:28
    Profile
  • Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    Once upon a time, a 4k intro that didn't have music, it was a very sad intro, because it couldn't dance...

    To solve this problem and bring some fun and dance to our intro, I made a small and simple 4k synth.

    How it works?

    All the song is defined on two arrays, seq, and patterns.

    Patterns contains all patterns with notes to build the song, it's similar to patterns on nanoloop or protracker, numeric values are used as notes, start from 1 to 254, 255 means that the sound is stopped on this position note, and 0 means that last note remain played.

    Patterns array for this piece of a familiar song to all C64 users, looks like this (SILENCE notes are not used):

    First some defs need:

    Code:

    #define TONEFREQ 220.0f
    #define TONEBASE 1.059463094359f

    #define CHANNELS 2
    #define PATTERNS 5
    #define PATTERNLEN 16
    #define SONGLEN 4

    #define NOTELEN 2048
    #define PATLEN 32768

    #define SILENT 255


    Code:

    static char patterns[PATTERNS][PATTERNLEN]=
    {
    {13, 18, 20, 18, 25, 18,20,18,13, 18, 20, 18, 25, 18,20,18,},
    {22, 0, 0, 0, 0, 0, 20,0, 23,22,0, 20, 0,0,22,18},
    {0,0, 0,0,0,0, 18, 18, 18,17,0,15, 0, 0, 17, 18,},
    {0, 0, 0,0, 0, 0, 0, 0, 0,13,15,13,20,18,13,20},
    {18, 0, 0,0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0},
    };


    And this is our song, very simple:

    Code:

    static char seq[CHANNELS][SONGLEN] =
    {
    {0, 0, 0, 0},
    {1, 2, 3, 4}
    };


    The song uses two channels with four patterns. The first channel repeats pattern 0 continously (first row on patterns) and the second channel plays patterns 1, 2, 3 and 4 (the rest of patterns defined on the array)

    The synth apply an envelope filter with 4 phases, attack, decay, sustain and release, this filter is defined for every channel here:

    Code:

    static float env_chan[CHANNELS][8] =
    {
    {
    0, 0, // a
    256, 1, // d
    2048, 0.2, // s
    3072, 0.6, // r
    },
    {
    0, 0, // a
    500, 1, // d
    7000, 0.8, // s
    8000, 0.7 // r
    }
    };


    Every phase have two values, the first value, is the pointer of the current note when this phase starts, and second is the volume applied to this phase, from 0 to 1.

    And here is where magic happens, the code are commented, so if you have any question, go ahead!

    Code:

    static short pos[2];
    static float freq;
    static char note[2];
    static short note_pos;

    static byte ch;
    static float env,hz;
    static float *env_val;
    static char pat_ptr;

    // Two oscillators are availabe, sin and saw
    // first parameter are hertzs and second parameter is time

    static float osc_sin(float f, int tm)
    {
    int d = tm*720*f/8000;
    return (sinus[(d%720)]+1.0)/2.0;
    }

    static float osc_saw(float f, int tm)
    {
    int r=(2*FREQUENCY / f);

    return (tm % r) * (1.0/r);
    }

    static void synth4k(void)
    {
    // Fill the audio buffer
    for (n = 0; n < BUFFERSIZE; n++)
    {
    // Set a pointer to the current buffer
    // and set the current buffer to zero
    byte *pb = &p1[n];
    *pb = 0;

    // Process our song to generate a wave for every channel
    for (ch = 0; ch < CHANNELS; ch++)
    {
    // Set some pointers to save some bytes,
    // note contains the current note played on this channel
    // po is the current position of the wave note for filling our buffer
    // pat_ptr contains the current pattern note to be played on this channel
    char *not = &note[ch];
    short *po = &pos[ch];
    note_pos = (t>>11)%PATTERNLEN;
    pat_ptr = patterns[seq[ch][(t>>15)%SONGLEN]][note_pos];

    // If note is silence, don't process the song
    if (pat_ptr < 255)
    {
    // Change the current note to be played,
    // if it's zero the last note is preserved
    if (pat_ptr > 0 && t%NOTELEN == 0)
    {
    *not = ((pat_ptr&63)-1);
    *po = 0;
    }

    // Apply the envelope filter
    env_val = env_chan[ch];
    env = env_val[7];
    for (i=0; i<8; i+=2)
    {
    if (env_val[i] > *po)
    {
    float pr = ((*po-env_val[i-2])/(env_val[i] - env_val[i-2]));
    env = pr * (env_val[i+1] - env_val[i-1]) + env_val[i-1];
    }
    }
    // Set base frequency
    freq = TONEBASE;
    // Get the frequency for the note to be played,
    // it is a simple power function, but it is not available.
    for (j = 1; j < *not; j ++)
    freq *= TONEBASE;
    // Get the herzs need to play the specific note
    hz = TONEFREQ*freq;

    // Here we define the instruments for the channels,
    // Play with oscillators and apply more to the time,
    // or hz parameters, add different oscillators, use
    // your imagination to obtain different sounds!
    if (ch == 0)
    *pb+=env*(osc_saw(hz,t))*48;
    else
    *pb+=env*(osc_sin(hz,t+osc_sin(5,t)*10))*24;
    }
    // Increment the buffer position
    *po+=1;
    }
    // Increment the time position
    t++;
    }
    }


    The instruments are defined on synth4k, this is for channel 0:

    *pb+=env*(osc_saw(hz,t))*48;

    and for channel 1:

    *pb+=env*(osc_sin(hz,t+osc_sin(5,t)*10))*24;

    As you can see, instruments are composed of 3 parts, env*osc*volume, osc is the part where you can use your imagination to build your instruments, applying some oscillators on time, hertzs, adding different oscillators, ...

    To modify the octave, you should change TONEFREQ, it have an initial value of 220, 110 is an octave down, and 440 is an octave up.


    And here are all files available to build the startup code and synthetizer.

    http://www.amigaskool.net/download/4k_intro_2.lha

    [ Edited by BalrogSoft 18.05.2015 - 18:10 ]
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »14.05.15 - 00:04
    Profile Visit Website
  • Paladin of the Pegasos
    Paladin of the Pegasos
    Yasu
    Posts: 1724 from 2012/3/22
    From: Stockholm, Sweden
    I have wanted to learn how to program, even just a little. Thansk for this! I will give it a try :-D
    AMIGA FORUM - Hela Sveriges Amigatidning!
    AMIGA FORUM - Sweden's Amiga Magazine!

    My MorphOS blog
  • »14.05.15 - 12:42
    Profile Visit Website
  • Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    Develop a 4k intro maybe is not the best way to start programming... the code is optimized to give a small executable, and it isn't the best source to learn programming, anyway, give it a try! You could use OpenGL functions to draw something on screen with the current code.

    For the next episode, we will use stack virtual machine with some rendering functions implemented.
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »14.05.15 - 13:42
    Profile Visit Website
  • Priest of the Order of the Butterfly
    Priest of the Order of the Butterfly
    ausPPC
    Posts: 543 from 2007/8/6
    From: Pending...
    I just tried this on a registered MorphOS 3.5 macmini. It ran but didn't respond to Esc or Ctrl-C and seemed to consume about 95% cpu. I was still able to switch screens back to Ambient.
    PPC assembly ain't so bad... ;)
  • »18.05.15 - 00:31
    Profile Visit Website
  • Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    It's a know issue, I have reported the problem and was solved on MorphOS 3.6, for some reason TinyGL doesn't work properly and it doesn't respond to CTRL+C signals. There is more info here:

    Overw8-bits, 6k intro for MorphOS

    [ Edited by BalrogSoft 18.05.2015 - 08:59 ]
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »18.05.15 - 08:59
    Profile Visit Website
  • Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    Using the code above, you can develop a 4k intro using c language, but there are some cases where writing some intro parts using a bytecode language is useful and it takes less space.

    I have implemented a stack virtual machine, but really is a mixed solution, because it uses registers as variables. There are basic instructions push, pop instructions, arithmetic, branching and jumps, calling c functions, and some custom instructions to perform graphical effects.

    This vm can have technically, 127 registers, memory recommended upto 64kb*, 17 basic instructions, and 5 graphical instructions.

    *Memory array should not be bigger than 64kb, because jump instructions are short values.

    Basic instructions

    HALT - 1 byte - Finish execution
    PUSH - 2 bytes - Push one byte in stack
    RPUSH - 2 bytes - Push register in stack
    RPOP - 2 bytes - Pop into register
    ADD, SUB, MUL, DIV - 1 byte - Pop two bytes from stack and push the operation result
    CALL - 2 bytes - Call C function
    JMP - 3 bytes - Jump to address (Short)
    RJMP - 3 bytes - Relative jump (Short)
    CMP - 1 byte - Pop two bytes and compare, set the less significant bits:
    flags register: xxxx xGEL
    G greather than - E equal - L less than

    BREQ, BRLE, BRGR - 2 bytes - branch if last CMP result is great, equal or less, jump size is 1 signed byte.
    LDSP, STSP - 1 byte - Push and pop sp current address

    Graphical instructions

    The drawing system uses some internal variables to store a drawing position x and y, although the vm only support byte values, internally the drawing system have short values.
    There are two basic instructions, MOVE and CLPA.
    MOVE translate the next drawing point adding a signed byte to internal drawing position x and y. So you can access to screen positions beyond 128 pixels, but I have used glScale to resize my content.
    CLPA means CLose PAth, and perform the drawing operation created with MOVE. Start drawing point is 0, 0 which is the center of screen.
    SSHA means Set SHApe, and define which render mode uses, values accepted are GL_POINTS, GL_LINES, and GL_LINE_LOOP


    SCOL - 1 byte - Get from stack two bytes for color 4 bits per channel
    LWID - 1 byte - Set drawing line width from stack
    SSHA - 1 byte - Set drawing shape from stack
    MOVE - 1 byte - Get two bytes from stack and add these values to the drawing coordinate
    CLPA - 1 byte - Perform the graphical operation

    The memory array contain a simple effect, and as you can see is similar to write an assembler program:

    Code:

    static byte mem[VM_MEMORY] =
    {
    PUSH, 8,
    LWID,

    PUSH, GL_LINE_LOOP,
    SSHA,

    PUSH, 0xff,
    PUSH, 0x1f,
    SCOL,

    CALL, 0,
    CALL, 0,
    MOVE,

    CALL, 0,
    CALL, 0,
    MOVE,

    CLPA,

    RPUSH, 0,
    PUSH, 1,
    ADD,
    RPOP, 0,

    RPUSH, 0,
    PUSH, 127,
    CMP,

    BREQ, 3,
    JMP, 0,11,

    HALT,
    };


    You can use C functions using CALL instruction from VM, technically you can define 127 C functions.
    VM can pass parameters to these functions with stack, and the C functions can return any value pushing them on to the stack.

    This is the code:

    Code:


    #define VM_REGISTERS 16
    #define VM_MEMORY 4096

    #define HALT 0x00
    #define PUSH 0x01
    #define RPUSH 0x02
    #define RPOP 0x03
    #define ADD 0x04
    #define SUB 0x05
    #define MUL 0x06
    #define DIV 0x07
    #define CALL 0x08
    #define JMP 0x09
    #define RJMP 0x0A
    #define CMP 0x0B
    #define BRLE 0x0C
    #define BREQ 0x0D
    #define BRGR 0x0E
    #define LDSP 0x0F
    #define STSP 0x10

    #define SCOL 0x60
    #define LWID 0x61
    #define SSHA 0x62
    #define MOVE 0x63
    #define CLPA 0x65

    static void vm_exec(void);

    static short pc;
    static short sp;
    static ubyte flags;

    static short vertex[1024], shape=0, x = 0, y = 0, m=0;

    static byte reg[VM_REGISTERS];

    static byte mem[VM_MEMORY] =
    {
    PUSH, 8,
    LWID,

    PUSH, GL_LINE_LOOP,
    SSHA,

    PUSH, 0xff,
    PUSH, 0x1f,
    SCOL,

    CALL, 0,
    CALL, 0,
    MOVE,

    CALL, 0,
    CALL, 0,
    MOVE,

    CLPA,

    RPUSH, 0,
    PUSH, 1,
    ADD,
    RPOP, 0,

    RPUSH, 0,
    PUSH, 127,
    CMP,

    BREQ, 3,
    JMP, 0,11,

    HALT,
    };


    static void push(byte value)
    {
    mem[sp--] = value;
    }

    static byte pop(void)
    {
    return mem[++sp];
    }

    static short dpop(void)
    {
    return (pop()<<8)+(pop()&0xff);
    }

    static byte pop_pc(void)
    {
    return mem[pc++];
    }

    static byte dpop_pc(void)
    {
    return (pop_pc()<<8)+(pop_pc()&0xff);
    }

    static void func1(void)
    {
    }

    static void func2(void)
    {
    }

    static void func3(void)
    {
    }

    static void (*func_ptr[3])(void) = {func1, func2, func3};

    static void vm_exec(void)
    {
    ubyte opCode;
    byte a,b;
    short c;

    pc = 0;
    sp = VM_MEMORY-1;
    flags = 0;
    do {
    opCode = pop_pc();
    switch (opCode)
    {
    case PUSH:
    push(pop_pc());
    break;
    case RPUSH:
    push(reg[pop_pc()]);
    break;
    case RPOP:
    reg[pop_pc()]=pop();
    break;
    case ADD:
    push(pop()+pop());
    break;
    case SUB:
    push(pop()-pop());
    break;
    case MUL:
    push(pop()*pop());
    break;
    case DIV:
    push(pop()/pop());
    break;
    case CALL:
    (*func_ptr[pop_pc()])();
    break;
    case JMP:
    pc = dpop_pc();
    break;
    case RJMP:
    pc += pop_pc();
    break;
    case CMP:
    a = pop();
    b = pop();
    flags = 0;
    flags |= ((a<b) << 0);
    flags |= ((a==b)<< 1);
    flags |= ((a>b) << 2);
    break;
    case BRLE:
    a = pop_pc();
    if (flags & (1 << 0))
    pc += a;
    break;
    case BREQ:
    a = pop_pc();
    if (flags & (1 << 1))
    pc += a;
    break;
    case BRGR:
    a = pop_pc();
    if (flags & (1 << 2))
    pc += a;
    break;
    case LDSP:
    sp = dpop();
    break;
    case STSP:
    push(sp);
    push(sp>>8);
    break;

    case SCOL:
    c = dpop();
    glColor4ub((c>>4)&0xf0,c&0xf0,(c<<4)&0xf0,(c>>8)&0xf0);
    break;
    case LWID:
    glLineWidth(pop());
    break;
    case SSHA:
    shape = pop();
    break;
    case MOVE:
    x += pop();
    y += pop();
    vertex[m++] = x;
    vertex[m++] = y;
    break;
    case CLPA:
    glVertexPointer(2, GL_SHORT, 0, vertex);
    glDrawArrays(shape, 0, m);
    m = x = y = 0;
    break;
    }
    } while (opCode != 0x00); // HALT
    }


    And for lazy people like me, here are the files to see this example:

    http://www.amigaskool.net/download/4k_intro_3.lha

    Here is the executable of this example, for those who wanna see result first... :)

    http://www.amigaskool.net/download/4k_intro_exe.lha


    [ Edited by BalrogSoft 19.05.2015 - 11:37 ]
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »18.05.15 - 20:53
    Profile Visit Website
  • Acolyte of the Butterfly
    Acolyte of the Butterfly
    kolla
    Posts: 105 from 2003/4/22
    Does it require a 4k monitor?
    -- kolla
  • »19.05.15 - 23:22
    Profile
  • Order of the Butterfly
    Order of the Butterfly
    BalrogSoft
    Posts: 171 from 2006/10/6
    From: Spain
    No, It doesn't requiere a 4k monitor, 4k means 4 kilobytes. This guide is about demoscene and how to make a 4kb intro for MorphOS, it isn't related to 4k resolution, sorry about that, now that MorphOS support 4k resolution, it could be misunderstanding.
    Balrog Software - AmigaSkool.net
    Mac Mini - MorphOS 3.8 - G4 1.5Ghz - Ati 9200 64 Mb
    Efika - MorphOS 3.6 - Ati 9200 64Mb - 80 Gb HD
    Amiga 1200D - OS 3.9 - Blizzard 603e/240mh
  • »20.05.15 - 20:13
    Profile Visit Website
  • Acolyte of the Butterfly
    Acolyte of the Butterfly
    kolla
    Posts: 105 from 2003/4/22
    Quote:

    BalrogSoft wrote:
    No, It doesn't requiere a 4k monitor, 4k means 4 kilobytes. This guide is about demoscene and how to make a 4kb intro for MorphOS, it isn't related to 4k resolution, sorry about that, now that MorphOS support 4k resolution, it could be misunderstanding.


    Yes, which was I asked, I clicked on the thread for wrong reason ;)

    I guess a 4k 4k demo would be neat, hehe.
    -- kolla
  • »22.05.15 - 01:16
    Profile