Thursday 27 April 2023

Creating an old-skool plasma effect

The "plasma" effect. An absolutely classic demoscene effect, and one I've wanted to program for a long time. If you're unaware, it's a cool, gloopy effect that resembles a lava lamp, and it was used in a lot of demos back in the day. I'd always wondered how it was done, and today, I finally managed to figure it out, and it's surprisingly simple. I think the reason I never did it, is because all the tutorials I see online use some wacky, complicated formulas that make my head spin. But it turns out you don't need ridiculous formulas to achieve a similar effect!

The basic principle is simple. You need to set up an X and Y counter when drawing your screen, and for every pixel, work out three values: the sine of the X, the sine of the Y, and some other value based off any combination of the two. Add these values together, divide by 3, and that's the colour index to draw to the screen. Once the screen is fully drawn, increment a "frame counter", which you also add to the three values. This creates the movement effect. Then, loop all of this and do it again!

When it comes to the colours, you'll notice that the default VGA palette looks very weird with this effect. It has no continuity whatsoever, so you'll need to fill the palette with a gradient, starting from dark and becoming brighter throughout the 256 colours.

That's basically it! Pretty simple, right? You don't need to come up with any weird formulas, just mess around and try to find an effect that looks good to you. Since I'm programming in assembly, I use bit shifting instead of division, which is much faster to calculate. Of course, it means I can only divide by 4 instead of 3, but that's not a problem, as it still looks rather nice.

Something of great importance is the speed. Calculating sines is very processor intensive, so it's best to use lookup tables instead. Of course, it can still be quite slow, because you need to "clamp" the value to 360, meaning it has to wrap around once it's above 360. Unfortunately, this requires division, as you're finding the remainder of a number, so it's still a bit slow, but way faster than calculating it manually. To get around this, simply render less pixels! For my example, I'm rendering it at a quarter of the full resolution. By the way, "clamping" is my own figure of speech ;)

Finally, I've shared the source code over at GitHub, if you'd like to check it out!

And here's a picture of it in action, drawing 4 pixels a time, alongside a graphic:



Wednesday 26 April 2023

Allocating memory using Int 21h/AH=48h

This is an issue I've been facing since the very beginning. Like the segment issue I've posted here, the issue relates to .com files, and more specifically, how memory is allocated. To recap, .com files are allocated a chunk of memory that has the fixed size of 64k. This is one memory segment. It uses up the whole 64k, no matter how big or small the file is. Hence, it's impractical (but not impossible!) to allocate memory.

For my graphics library, the BGL, I resorted to using the Program Segment Prefix, which at offset 2-3, contains the address of the segment after the end of the program. Using a cheeky trick, I was able to subtract the amount of bytes I wanted for the graphics buffer, and use the resulting address. It does work, but it isn't the "proper" way of doing it. For that, we need to use .exe files. Because they don't deal with memory the same way as .com files, we can use the BIOS call Int 21h/AH=48h. Or so I thought...

So, here's the mistake I was making. I was simply using that BIOS call and expecting it to just work, which is quite a naive approach. If you do that, the carry flag will be set, and you'll get error code 8, which means "insufficient memory", which just isn't true! What I realized, is that you first need to use Int 21h/AH=4Ah, which resizes a memory block to whichever size you like, granted there's enough available. Then, once you've got some memory freed up, use Int 21h/AH=48h to allocate some memory, and if all goes well, you'll get the segment address in ax, ready to move to es!

Tuesday 25 April 2023

Building .exe files larger than 64kb

Ever since I started programming for DOS, I've had to face the limitation of .com files, which have a maximum file size of 64kb. Today, I figured out exactly why this is, and a way that I can get larger files compiled! The way .com files work, is that all the code and data is dumped into a single data segment at offset 100h. Each segment in DOS has a size of 64kb, hence the limit for .com files. Working within limitations can be fun, but there are times where I've wanted to add more than I could. That changed today, where I had quite the revelation. I knew before that if you use a linker, you can utilize multiple segments, but I had no idea how to use them. I've finally figured it out, and it's shockingly simple:

segment code

..start:

mov ax,stack
mov ss,ax
mov sp,stack_top

mov ax,yems
mov ds,ax
mov dx,hello
mov ah,9
int 21h
mov ax,moar
mov ds,ax
mov dx,hi2
mov ah,9
int 21h
mov ah,4ch
int 21h

segment yems
hello: db "hello world$"

segment dayter
hi:
incbin "E:\dos\low1.raw"
incbin "E:\dos\low2.raw"
incbin "E:\dos\low3.raw"
incbin "E:\dos\low4.raw"
incbin "E:\dos\low5.raw"
incbin "E:\dos\low6.raw"
incbin "E:\dos\low7.raw"
incbin "E:\dos\low8.raw"
incbin "E:\dos\low1b.raw"
incbin "E:\dos\low3b.raw"
incbin "E:\dos\low5b.raw"
incbin "E:\dos\low7b.raw"
incbin "E:\dos\low8b.raw"
incbin "E:\dos\low1c.raw"
segment moar
hi2:
        db "does this still work?$"
incbin "E:\dos\extreme.raw"
segment stack stack
resb 256
stack_top:

All you have to do is change register ds to contain the address of the appropriate segment, and you're all good! The code above is a mess, but it's what I used for testing. All files included in segment dayter add up to 63kb. The file in segment moar is 16.9 (nice) kb. The whole .exe is about 80kb!

Because I like to do all the development in my Windows environment, I needed to use a 32-bit linker. For this, I highly recommend Alink! Of course, I'm using NASM for assembling the code. Here are the commands, for future reference:

nasm -f obj -o file.obj file.asm
alink file.obj

Amiga module player for DOS - the first draft!

After one week, I've finally pulled off what I previously thought impossible - an entire module player, running in real mode DOS! I'...