One
of the desired goals of my 6502 emulator project was that it would
work the same on MS Windows and Linux. I neglected Linux port for
some time and recently faced the difficult task of making the program
work again in Linux environment.
So
I added my graphics device emulator code and SDL2 library without much problems but my emulated character I/O
stopped working in Linux. After long hours spent investigating the
problem and after I tried all known solutions for non-blocking
character input in C/C++ on Linux (I needed kbhit()/getch
'conio'-like functionality) to no avail I finally gave up, cut the
losses and did huge refactoring switching all Linux code to ncurses
library.
Ultimately I think I will move character I/O completely to the graphics device, which should be much more portable and also more true to the hardware architecture of real 6502 based computers. Let's just say that the character I/O that I have in place right now is emulating a serial port type or teletype kind of character I/O. When I'll implement it on my graphics device, it will be more like console I/O type. Anyway, this is how I see it, future will show where the project will take me.
At the same time I have been developing expansion of my virtual graphics
device capabilities. The goal is to have a text mode. Well, not
exactly a separate text mode, but rather ability to render 8x8 bit
characters efficiently on the graphics device by simply pointing the
device to characters table memory bank using one register and then
write the code of the character and its coordinates in other
registers to have the character rendered on the screen. That would enable my console I/O emulation in the future, as mentioned in previous paragraph.
The proof of
concept works. I have downloaded Commodore 64 character ROM,
converted it to my memory image definition format and have compiled a
modified version of EhBasic which has the top of Basic RAM lowered by 4 kB
so I can load the C64 characters table right on top of BASIC RAM and before
the EhBasic code starts.
To
test the proof of concept code, start the emulator (vm65), and in
debug console issue commands:
L
A C64_CHAR.DAT
L
A EHBAS_XX.DAT
EhBasic
will load and prompt for memory size. Just press ENTER and then enter
following program:
5
PRINT:PRINT "BITMAP TEXT DEMO. PRESS [SPACE] TO QUIT...":PRINT
10
C=0:M=0:N=22:B=65506:POKE B+9,0
12
PRINT "NORMAL MODE, CHAR BANK ";N*2048
15
POKE B+13,N:POKE B+17,0:POKE B+18,0
20
FOR Y=0 TO 24
30
FOR X=0 TO 39
40
POKE B+14,X:POKE B+15,Y
50
POKE B+16,C
60
C=C+1:IF C<256 THEN 120
70
IF N=22 THEN N=23:GOTO 100
80
N=22:IF M=0 THEN M=1:GOTO 100
90
M=0
100
POKE B+13,N:POKE B+18,M
110
Y=Y+1:X=-1:C=0
115
IF M=0 THEN PRINT "NORMAL"; ELSE PRINT "REVERSE";
116
PRINT " MODE, CHAR BANK ";N*2048
120
GET K$:IF K$=" " THEN END
130
NEXT X
140
NEXT Y
150
GOTO 5
Run
above program to see both banks of C64 characters print on the SDL2
window first in normal mode, then in reversed color mode.
The
expected result should (after a short while) look similar to this:
Later
I did some performance optimizations like refreshing the surface only
after the whole character is painted instead of painting/refreshing
pixel by pixel and also by copying the entire characters table from
VM's RAM to internal GraphDisp class buffer each time the virtual
graphics device character ROM bank register is updated.
I
added method
void
GraphDisp::CopyCharRom8x8(unsigned char *pchrom);
specifically
for this purpose.
CopyCharRom8x8()
must be called each time when address of character ROM changes.
Code
snippet from MemMapDev class shows how its done:
[...]
}
else if ((unsigned int)addr == mGraphDispAddr + GRAPHDEVREG_CHRTBL) {
//
set new address of the character table, 2 kB bank #0-31
mGrDevRegs.mGraphDispChrTbl
= (unsigned char)(val & 0x003F);
mCharTblAddr
= mGrDevRegs.mGraphDispChrTbl * ((MAX_8BIT_ADDR+1) / 0x20);
unsigned
char char_rom[CHROM_8x8_SIZE];
for
(unsigned int i=0; i<CHROM_8x8_SIZE; i++) {
char_rom[i]
= mpMem->Peek8bitImg((unsigned short)((mCharTblAddr + i) &
0xFFFF));
}
mpGraphDisp->CopyCharRom8x8(char_rom);
[...]
This
way character rendering is handled completely internally inside
GraphDisp class with this public method:
/*
*--------------------------------------------------------------------
*
Method: PrintChar8x8()
*
Purpose: Print 8x8 character at specified row and column.
*
Arguments: code - character code
*
col, row - character coordinates in 8 pixel intervals
*
reversed - color mode (reversed or normal)
*
Returns: n/a
*--------------------------------------------------------------------
*/
void
GraphDisp::PrintChar8x8(int code, int col, int row, bool reversed);
My
github repository is updated with new code and documentation.
For anybody following this project, I recommend getting this latest version as I found and fixed many problems recently and added cool features described above :-).
Enjoy.
Thank you for reading!
Marek K.
9/15/2016