Guide to develop 4k Intros for MorphOS (Part 1)
For this guide, we will need MorphOS SDK, and lzma_loader to crunch the executable output.
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 morphzone 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 function is reserved to fill the next audio buffer with our song.
To build this code, you will need a makefile, don't you?
Makefile:
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.
CLI:
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: