I've been trying to write a CHIP-8 emulator as a personal project, and I've got stuck on implementing it's graphics.
As of now I'm trying to implement the framebuffer as an array of 8 bit elements, where the value of each element should be either 1 or 0, and is set through XORing the previous value of said element with the new one.
I also attempted to write an SDL2 routine to render it to screen. I've clearly failed somewhere tho, as all it displays is half the scren white and the other black, at which point the program stops responding.
Here is the DXYN instruction as I implemented it:
case 0xd:
{
gfxflag=1;
V[0xf]=0;
cny=(RAM[PC+1]>>4);
cnx=(((RAM[PC]>>4)<<4)^RAM[PC]);
//n=((RAM[pc+1] ^ (RAM[pc+1]>>4)))
//THIS WILL XOR N BYETS OF RAM (N elements of char) INTO 8*N CONSECUTIVE ELEMENTS OF FB LEFT TO RIGHT
for(gfxcn=0; gfxcn < (((RAM[PC+1]>>4)<<4)^RAM[PC+1]); gfxcn++)
{
FRAMEBUFFER[((cnx*cny)+(8*gfxcn))] = FRAMEBUFFER[((cnx*cny)+(8*gfxcn))] ^ (RAM[I]>>7);
FRAMEBUFFER[(((cnx*cny)+1)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+1)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>6 ^ ((RAM[I+gfxcn]>>7)<<1));
FRAMEBUFFER[(((cnx*cny)+2)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+2)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>5 ^ ((RAM[I+gfxcn]>>6)<<1));
FRAMEBUFFER[(((cnx*cny)+3)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+3)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>4 ^ ((RAM[I+gfxcn]>>5)<<1));
FRAMEBUFFER[(((cnx*cny)+4)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+4)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>3 ^ ((RAM[I+gfxcn]>>4)<<1));
FRAMEBUFFER[(((cnx*cny)+5)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+5)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>2 ^ ((RAM[I+gfxcn]>>3)<<1));
FRAMEBUFFER[(((cnx*cny)+6)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+6)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>1 ^ ((RAM[I+gfxcn]>>2)<<1));
FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))] ^ (RAM[I+gfxcn] ^ ((RAM[I+gfxcn]>>1)<<1));
if (((FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))]) != ((FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))])) ^ ((RAM[I+gfxcn] ^ ((RAM[I+gfxcn]>>1)<<1)))))
{
V[0xf]=1;
}
}
PC+=2;
break;
}
Here is the SDL2 code as well
gfxflag=0;
for(cnx=0; cnx<64; cnx++)
{
for(cny=0; cny<32; cny++)
{
switch(FRAMEBUFFER[cnx*cny])
{
case 0:
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawPoint(renderer, cnx, cny);
case 1:
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDrawPoint(renderer, cnx, cny);
}
SDL_RenderPresent(renderer);
}
}
It may or may not be relevant, but I'm also adjuncting the entire program:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <SDL2/SDL.h>
unsigned short PC=0x200, I=0, STACK[16]={0};
unsigned char V[16], SP=0, DT=0, ST=0, FRAMEBUFFER[64*32]={0};
unsigned char RAM[4096]=
{0xf0, 0x90, 0x90, 0x90, 0xf0, /* 0 */// 0-4 key 0|map each font sprite is 8x5 column x row
0x20, 0x60, 0x20, 0x20, 0x70, /* 1 */// 5-9 key 1|map each number is a row, 8 columns wide
0xf0, 0x10, 0xf0, 0x80, 0xf0, /* 2 */// 10-14 key 2|map in ram each array element is a row, 8 col/one byte
0xf0, 0x10, 0xf0, 0x10, 0xf0, /* 3 */// 15-19 key 3|map total memory occupied by font: 80/0x50 bytes
0x90, 0x90, 0xf0, 0x10, 0x10, /* 4 */// 20-24 key 4|map
0xf0, 0x80, 0xf0, 0x10, 0xf0, /* 5 */// 25-29 key 5|map
0xf0, 0x80, 0xf0, 0x90, 0xf0, /* 6 */// 30-34 key 6|map
0xf0, 0x10, 0x20, 0x40, 0x40, /* 7 */// 35-39 key 7|map
0xf0, 0x90, 0xf0, 0x90, 0xf0, /* 8 */// 40-44 key 8|map
0xf0, 0x90, 0xf0, 0x10, 0xf0, /* 9 */// 45-49 key 9|map
0xf0, 0x90, 0xf0, 0x90, 0x90, /* A */// 50-54 key 10|map
0xe0, 0x90, 0xe0, 0x90, 0xe0, /* B */// 55-59 key 11|map
0xf0, 0x80, 0x80, 0x80, 0xf0, /* C */// 60-64 key 12|map
0xe0, 0x90, 0x90, 0x90, 0xe0, /* D */// 65-69 key 13|map
0xf0, 0x80, 0xf0, 0x80, 0xf0, /* E */// 70-74 key 14|map
0xf0, 0x80, 0xf0, 0x80, 0x80, /* F */// 75-79 key 15|map
0};
unsigned char ponggame[246]=
{0x6A, 0x02 ,0x6B ,0x0C ,0x6C,
0x3F ,0x6D ,0x0C ,0xA2 ,0xEA,
0xDA ,0xB6 ,0xDC ,0xD6 ,0x6E,
0x00 ,0x22 ,0xD4 ,0x66 ,0x03,
0x68 ,0x02 ,0x60 ,0x60 ,0xF0,
0x15 ,0xF0 ,0x07 ,0x30 ,0x00,
0x12 ,0x1A ,0xC7 ,0x17 ,0x77,
0x08 ,0x69 ,0xFF ,0xA2 ,0xF0,
0xD6 ,0x71 ,0xA2 ,0xEA ,0xDA,
0xB6 ,0xDC ,0xD6 ,0x60 ,0x01,
0xE0 ,0xA1 ,0x7B ,0xFE ,0x60,
0x04 ,0xE0 ,0xA1 ,0x7B ,0x02,
0x60 ,0x1F ,0x8B ,0x02 ,0xDA,
0xB6 ,0x60 ,0x0C ,0xE0 ,0xA1,
0x7D ,0xFE ,0x60 ,0x0D ,0xE0,
0xA1 ,0x7D ,0x02 ,0x60 ,0x1F,
0x8D ,0x02 ,0xDC ,0xD6 ,0xA2,
0xF0 ,0xD6 ,0x71 ,0x86 ,0x84,
0x87 ,0x94 ,0x60 ,0x3F ,0x86,
0x02 ,0x61 ,0x1F ,0x87 ,0x12,
0x46 ,0x02 ,0x12 ,0x78 ,0x46,
0x3F ,0x12 ,0x82 ,0x47 ,0x1F,
0x69 ,0xFF ,0x47 ,0x00 ,0x69,
0x01 ,0xD6 ,0x71 ,0x12 ,0x2A,
0x68 ,0x02 ,0x63 ,0x01 ,0x80,
0x70 ,0x80 ,0xB5 ,0x12 ,0x8A,
0x68 ,0xFE ,0x63 ,0x0A ,0x80,
0x70 ,0x80 ,0xD5 ,0x3F ,0x01,
0x12 ,0xA2 ,0x61 ,0x02 ,0x80,
0x15 ,0x3F ,0x01 ,0x12 ,0xBA,
0x80 ,0x15 ,0x3F ,0x01 ,0x12,
0xC8 ,0x80 ,0x15 ,0x3F ,0x01,
0x12 ,0xC2 ,0x60 ,0x20 ,0xF0,
0x18 ,0x22 ,0xD4 ,0x8E ,0x34,
0x22 ,0xD4 ,0x66 ,0x3E ,0x33,
0x01 ,0x66 ,0x03 ,0x68 ,0xFE,
0x33 ,0x01 ,0x68 ,0x02 ,0x12,
0x16 ,0x79 ,0xFF ,0x49 ,0xFE,
0x69 ,0xFF ,0x12 ,0xC8 ,0x79,
0x01 ,0x49 ,0x02 ,0x69 ,0x01,
0x60 ,0x04 ,0xF0 ,0x18 ,0x76,
0x01 ,0x46 ,0x40 ,0x76 ,0xFE,
0x12 ,0x6C ,0xA2 ,0xF2 ,0xFE,
0x33 ,0xF2 ,0x65 ,0xF1 ,0x29,
0x64 ,0x14 ,0x65 ,0x00 ,0xD4,
0x55 ,0x74 ,0x15 ,0xF2 ,0x29,
0xD4 ,0x55 ,0x00 ,0xEE ,0x80,
0x80 ,0x80 ,0x80 ,0x80 ,0x80,
0x80 ,0x00 ,0x00 ,0x00 ,0x00,
0x00};
char genflag=0, debugflag=0, imemcounter=0, keybuf, gfxflag;//used to determine 0nnn//used to toggle debug mode// inst 'f5' and 'f6' gfxcn// used in E insts, retains key//tels interpreter to update screen
short gfxcn, ldcn, debugcn; //used in d instruction to aid graphing to framebuffer//used for debug console
short clearcn=0, res8=0, cny=0, cnx=0, cn=0, cn2=0;//used to clear display//used to calculate 8xy5-6 carry according to higher 8 bits//used in d inst x4
short spriteloc[16]={ //stores locations of sprite
00, 05, 10, 15,
20, 25, 30, 35,
40, 45, 50, 55,
60, 65, 70, 75};
int main(int argc, char **argv)
{
init:
for(ldcn=0; ldcn<246; ldcn++)//ROMLOAD
{
//printf("%x ", RAM[0x200 + ldcn]);
RAM[0x200+ldcn]=ponggame[ldcn];
//printf("%x %x ", ldcn, RAM[0x200 + ldcn]);
}
srand(time(NULL));
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *window=NULL;
SDL_Renderer *renderer=NULL;
SDL_CreateWindowAndRenderer(64, 32, 0, &window, &renderer); // surface -> texture -> window
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
//SDL_RenderClear(renderer); WHY IS THIS EVEN HERE
emuthread:
returnfromgfx:
while(1)
{
if(debugflag==1)
{
//if(debugcn>=5)
//{
// return 0;
//}
//debugcn++;
printf("%x ", PC);
printf("%x %x ", RAM[PC], RAM[PC+1]); //print next instruction to be executed
}
if(gfxflag==1)
{
goto gfx;
}
switch((RAM[PC]>>4)) // CONDITIONAL BASED ON HIGHER 4 BITS OF INSTRUCTION
{
case 0x0:
{
if(RAM[PC+1]==0xEE) //CONDITIONAL BASED ON SECOND BYTE OF INSTRUCTION
{
PC=STACK[SP];
SP--;
genflag++;
}
if(RAM[PC+1]==0xE0)
{
for(clearcn=0; clearcn<2048; clearcn++)
{
FRAMEBUFFER[clearcn]=0;
}
genflag++;
PC+=2;
}
if(genflag==0)
{
PC = ((((RAM[PC]>>4)<<4) ^ RAM[PC])<<8) ^ RAM[PC+1];
}
if(genflag>0)
{
genflag=0;
}
break;
}
case 0x1:
{
PC = ((((RAM[PC]>>4)<<4) ^ RAM[PC])<<8) ^ RAM[PC+1];
break;
}
case 0x2:
{
SP++;
STACK[SP]=PC;
PC = ((((RAM[PC]>>4)<<4) ^ RAM[PC])<<8) ^ RAM[PC+1];
break;
}
case 0x3:
{
if(V[(((RAM[PC]>>4)<<4)^RAM[PC])]==RAM[PC+1])
{
PC+=2;
}
PC+=2;
break;
}
case 0x4:
{
if(V[(((RAM[PC]>>4)<<4)^RAM[PC])]!=RAM[PC+1])
{
PC+=2;
}
PC+=2;
break;
}
case 0x5:
{
if(V[(((RAM[PC]>>4)<<4)^RAM[PC])]==(RAM[PC+1]>>4))
{
PC+=2;
}
PC+=2;
break;
}
case 0x6:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])]=RAM[PC+1];
PC+=2;
break;
}
case 0x7:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = (RAM[PC+1] + V[(((RAM[PC]>>4)<<4)^RAM[PC])]);
PC+=2;
break;
}
case 0x8:
{
switch((((RAM[PC+1]>>4)<<4)^RAM[PC+1])) //conditional based on lower 4 bits of lower byte
{
case 0x0:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = (RAM[PC+1]>>4);
PC+=2;
break;
}
case 0x1:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = V[(((RAM[PC]>>4)<<4)^RAM[PC])] | (RAM[PC+1]>>4);
PC+=2;
break;
}
case 0x2:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = V[(((RAM[PC]>>4)<<4)^RAM[PC])] & (RAM[PC+1]>>4);
PC+=2;
break;
}
case 0x3:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = V[(((RAM[PC]>>4)<<4)^RAM[PC])] ^ (RAM[PC+1]>>4);
PC+=2;
break;
}
case 0x4:
{
V[0xf]=0;
res8= V[(((RAM[PC]>>4)<<4)^RAM[PC])] + (RAM[PC+1]>>4);
if((res8>>8)>0)
{
V[0xf]=1;
}
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = (((res8>>8)<<8)^res8);
PC+=2;
break;
}
case 0x5:
{
V[0xf]=0;
if(V[(((RAM[PC]>>4)<<4)^RAM[PC])] > (RAM[PC+1]>>4))
{
V[0xf]=0;
}
PC+=2;
break;
}
case 0x6:
{
V[0xf]=0;
if((((V[(((RAM[PC]>>4)<<4)^RAM[PC])]>>1)<<1)^V[(((RAM[PC]>>4)<<4)^RAM[PC])])==1)
{
V[0XF]=1;
}
PC+=2;
break;
}
case 0x7:
{
V[0xf]=0;
if((RAM[PC+1]>>4) > V[(((RAM[PC]>>4)<<4)^RAM[PC])])
{
V[0xf]=1;
}
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = (RAM[PC+1]>>4) - V[(((RAM[PC]>>4)<<4)^RAM[PC])];
PC+=2;
break;
}
case 0xe:
{
V[0xf]=0;
if((V[(((RAM[PC]>>4)<<4)^RAM[PC])]>>7)==1)
{
V[0xf]=1;
}
PC+=2;
break;
}
break;
}
}
case 0x9:
{
if(V[(((RAM[PC]>>4)<<4)^RAM[PC])]!=(RAM[PC+1]>>4))
{
PC+=2;
}
PC+=2;
break;
}
case 0xa:
{
I = ((((RAM[PC]>>4)<<4) ^ RAM[PC])<<8) ^ RAM[PC+1];
PC+=2;
break;
}
case 0xb:
{
PC = (((((RAM[PC]>>4)<<4) ^ RAM[PC])<<8) ^ RAM[PC+1]) + V[0x0];
PC+=2;
break;
}
case 0xc:
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = (RAM[PC+1] & (rand() % 256));
PC+=2;
break;
}
case 0xd:
{
gfxflag=1;
V[0xf]=0;
cny=(RAM[PC+1]>>4);
cnx=(((RAM[PC]>>4)<<4)^RAM[PC]);
//n=((RAM[pc+1] ^ (RAM[pc+1]>>4)))
//THIS WILL XOR N BYETS OF RAM (N elements of char) INTO 8*N CONSECUTIVE ELEMENTS OF FB LEFT TO RIGHT
for(gfxcn=0; gfxcn < (((RAM[PC+1]>>4)<<4)^RAM[PC+1]); gfxcn++)
{
FRAMEBUFFER[((cnx*cny)+(8*gfxcn))] = FRAMEBUFFER[((cnx*cny)+(8*gfxcn))] ^ (RAM[I]>>7);
FRAMEBUFFER[(((cnx*cny)+1)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+1)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>6 ^ ((RAM[I+gfxcn]>>7)<<1));
FRAMEBUFFER[(((cnx*cny)+2)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+2)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>5 ^ ((RAM[I+gfxcn]>>6)<<1));
FRAMEBUFFER[(((cnx*cny)+3)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+3)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>4 ^ ((RAM[I+gfxcn]>>5)<<1));
FRAMEBUFFER[(((cnx*cny)+4)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+4)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>3 ^ ((RAM[I+gfxcn]>>4)<<1));
FRAMEBUFFER[(((cnx*cny)+5)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+5)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>2 ^ ((RAM[I+gfxcn]>>3)<<1));
FRAMEBUFFER[(((cnx*cny)+6)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+6)+(8*gfxcn))] ^ (RAM[I+gfxcn]>>1 ^ ((RAM[I+gfxcn]>>2)<<1));
FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))] = FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))] ^ (RAM[I+gfxcn] ^ ((RAM[I+gfxcn]>>1)<<1));
if (((FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))]) != ((FRAMEBUFFER[(((cnx*cny)+7)+(8*gfxcn))])) ^ ((RAM[I+gfxcn] ^ ((RAM[I+gfxcn]>>1)<<1)))))
{
V[0xf]=1;
}
}
PC+=2;
break;
}
case 0xe:
{
if(keybuf==V[(((RAM[PC]>>4)<<4)^RAM[PC])])
{
PC+=2;
}
if(keybuf!=V[(((RAM[PC]>>4)<<4)^RAM[PC])])
{
PC+=2;
}
PC+=2;
break;
}
case 0xf:
{
switch(RAM[PC+1]>>4)
{
case 0x0:
{
if((((RAM[PC+1]>>4)<<4)^RAM[PC+1]>>4)==7)
{
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = DT;
}
if((((RAM[PC+1]>>4)<<4)^RAM[PC+1]>>4)==0xa)
{
goto input;
returnfrominput:
V[(((RAM[PC]>>4)<<4)^RAM[PC])] = keybuf;
}
PC+=2;
break;
}
case 0x1:
{
if((((RAM[PC+1]>>4)<<4)^RAM[PC+1]>>4)==5)
{
DT = V[(((RAM[PC]>>4)<<4)^RAM[PC])];
}
if((((RAM[PC+1]>>4)<<4)^RAM[PC+1]>>4)==8)
{
ST = V[(((RAM[PC]>>4)<<4)^RAM[PC])];
}
if((((RAM[PC+1]>>4)<<4)^RAM[PC+1]>>4)==0xe)
{
I = I + V[(((RAM[PC]>>4)<<4)^RAM[PC])];
}
PC+=2;
break;
}
case 0x2:
{
I = spriteloc[V[(((RAM[PC]>>4)<<4)^RAM[PC])]];
PC+=2;
break;
}
case 0x3:
{
//mod 100 -> 0xx
//then mod 10 -> 00x
RAM[I]= ((V[(((RAM[PC]>>4)<<4)^RAM[PC])] ^ ((V[(((RAM[PC]>>4)<<4)^RAM[PC])] % 100) % 10)) - ((V[(((RAM[PC]>>4)<<4)^RAM[PC])] ^ ((V[(((RAM[PC]>>4)<<4)^RAM[PC])] % 100) % 10)) % 100)); //100
RAM[I+1]= (((V[(((RAM[PC]>>4)<<4)^RAM[PC])] ^ ((V[(((RAM[PC]>>4)<<4)^RAM[PC])] % 100) % 10)) % 100) / 10); //10
RAM[I+2]= ((V[(((RAM[PC]>>4)<<4)^RAM[PC])] % 100) % 10); //1
PC+=2;
break;
}
case 0x5:
{
for(imemcounter=0; imemcounter<16; imemcounter++)
{
RAM[I+imemcounter]=V[imemcounter];
}
PC+=2;
break;
}
case 0x6:
{
for(imemcounter=0; imemcounter<16; imemcounter++)
{
V[imemcounter]=RAM[I+imemcounter];
}
PC+=2;
break;
}
break;
}
break;
}
}
if(DT>0)
{
DT--;
}
if(ST<0)
{
ST--;
}
}
input:
switch(getch())
{
case '1':
keybuf= 1;
case '2':
keybuf= 2;
case '3':
keybuf= 3;
case '4':
keybuf= 12;
case 'q':
keybuf= 4;
case 'w':
keybuf= 5;
case 'e':
keybuf= 6;
case 'r':
keybuf= 13;
case 'a':
keybuf= 7;
case 's':
keybuf= 8;
case 'd':
keybuf= 9;
case 'f':
keybuf= 14;
case 'z':
keybuf= 10;
case 'x':
keybuf= 0;
case 'c':
keybuf= 11;
case 'v':
keybuf= 15;
default:
goto input;
}
goto returnfrominput;
gfx:
gfxflag=0;
for(cnx=0; cnx<64; cnx++)
{
for(cny=0; cny<32; cny++)
{
switch(FRAMEBUFFER[cnx*cny])
{
case 0:
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawPoint(renderer, cnx, cny);
case 1:
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDrawPoint(renderer, cnx, cny);
}
SDL_RenderPresent(renderer);
}
}
goto returnfromgfx;
return 0;
}
After making the corrections ctx suggested, the color problem seems to be mostly fixed. However, the program renders hogwash to the screen, or so it seems. Moreover, I tested the SDL2 code by making it display random pixels, and it seems to work save for the second halfth, which is still black, but I suppose it doesn't actually pertain to the problem after all.
If the routine is capable of rendering on it's own, the error must be somewhere along the emulation chunk, but I have looked at it for days on end and I still haven't found it.