/*
* HOME(PROJECTS) || RESUME || LINKS || ABOUT
*/

|Newest| . . . |<<Newer| . . . [viewing Oldest]
(view all as one document)

Retrochallenge 2026/10

Start

October 7, 2024

I wanted to start just by seeing how well some of the code for the existing emulator ports to glsl. I decided to create a small glsl test harness, using some code I had from before. I had previously had a library of C functions, called Zcore, that does a lot of useful things in C: memory management, some basic data structures like lists and vectors, some linear algebra routines for graphics, loading TGA images, etc. It's a bunch of slightly organized functions I've used for random experimentation. The last experiment with Zcore was building it into a full interpreted language called Geranium. It's a FORTH-like stack-based language, that is out-of-scope for this project. But, one of the last experiments in Zcore/Geranium was a rewrite of handling glsl vertex and pixel shaders. I also want to eventually add compute shaders to Geranium, so I decided to just go ahead and make Geranium work again as a library.

I went ahead and added compute shaders to the C helper functions in Geranium, and then started a new project using Geranium as a library. It seems to be working. So, what did the compute shader do? I took some code from the 8080 emulator:


#define INR(reg) \
{                                               \
    ++(reg);                                    \
    S_FLAG = (((reg) & 0x80) != 0);             \
    Z_FLAG = ((reg) == 0);                      \
    H_FLAG = (((reg) & 0x0f) == 0);             \
    P_FLAG = PARITY(reg);                       \
}

from i8080.c

The above is some questionable use of macros, especially for glsl. If you haven't done anything with glsl, it is a C-like language with lots of restrictions, like no recursion and no pointers. The above is a macro that increments a register, which is just a variable. I decided to run it on an array of integers:

void main() {


    int i;
    for (i=0;i<4;i++){

        INR(di[i]);

        di[i] |=di[i]  |  (S_FLAG<<24) | (Z_FLAG<<20) | (H_FLAG << 16) | (P_FLAG << 12) | (0xf<<8);

    }

}

from test.glsl

Now, the above is glsl, but is basically looks like C. It's running on an array of 4 ints, calling INR on each. INR is increment register. This is a set of 4 test cases, di[i] has different possible register values to try thing on. Now what is the next line? I take the value of di[i], and I OR it with the different processor flags, Sign, Zero, etc, in convenient to read positions. I also write an F to indicate that's where the flags are. What were the test cases? That is in the C code:

int idata[4] = { 1, 0xff , 0x7f , 2};
from main.c

When we look at the output:

f02 111f00 1010f80 1f03

Let's analyze each result:
f02: F is the flag 'delimiter', ignore it. 02 is the answer. Looking above idata[0] was 1, so 1 increments to 2, with no flags set.

111f00: idata[1] was 0xff. Increments to the answer of 0. Look at the flags: 111. The leading 0, which is not shown, is the Sign flag. The answer is not negative, so 0 is correct. The next flag is the Zero flag, which is 1 (true), because the answer is zero. Next is the Half carry flag, which I don't yet understand so lets ignore it. Next is Parity. Parity is 1 for an even number of bits, and 0 for odd. There are no bits (count 0), which is an even number, so it is a 1.

1010f80: idata[2] was 0x7f. Increments to 0x80. Flags are 1010: lets check. 0x80 is -1 for a byte, so its negative, and the Sign flag is set. The answer is not zero, so it is not set. I'll skip the H flag, and Parity is 0. Parity is 0 for odd numbers of bits and 0x80 (10000000) has a single bit set.

1f03: idata[3] was 0x02. Well, the answer is 3, its not negative, its not zero, I don't know what the H flag should be, and it's 2 bits, which is even, so Parity is set.

Changes to the code to make this work

Very little had to change. This is the glsl version of the same macro:


#define INR(reg) \
{                                               \
    ++(reg);\
    reg&=0xff; \
    S_FLAG = (((reg) & 0x80) != 0)?1:0;             \
    Z_FLAG = ((reg) == 0)?1:0;                      \
    H_FLAG = (((reg) & 0x0f) == 0)?1:0;             \
    P_FLAG = PARITY(reg);                       \
}

from test.glsl What changed?

Well, that's it for now. Next time I want to do a switch-case and see if I can get an instruction or two to run. I also want to get into explaining how data transfers between the CPU and GPU, because so far I have just shown you some random C code, and told you some of it was actually glsl and magically ran on the GPU. Anyway, the actual compute dispatch happens here, and immediately before that was uploading the buffer of ints (all four of them!) to the graphics card. Following the compute dispatch, is a memory barrier, which waits for the GPU to finish, and then a copy back to the CPU.

|Newest| . . . |<<Newer| . . . [viewing Oldest]
(view all as one document)