Looks like my ET4000AX card doesn't have the VESA BIOS extensions 🙁
By default on my 286 system I get my warning screen about no supported VBE found and my code won't load (as expected on a non-VESA card).
If I load TL1VESA.COM (an ET3000/ET4000 VESA TSR) it loads and the MSDOS logo and initial progress bar and font display perfectly (hurrah!) but querying the vbe capabilities field seems to indicate that the DAC is fixed at 6bit. That could be an artifact of the TSR however, as my card has the Sierra highcolour/truecolour DAC chip.
The Trident card with the built-in VESA support reports the following:
1src/main.c.204 Now initialising VESA graphics mode 2src/gfx.c.63 gfx_Init() Initalising gfx mode 3src/vesa.c.90 VESA BIOS queried successfully! 4src/vesa.c.265 vesa_PrintVBEInfo() VESA BIOS information follows 5VBE Signature: VESAP 6VBE Vendor: Copyright 1988-1991 TRIDENT MICROSYSTEMS INC. 7VBE Version: 258 8SW Version: 0 9Vendor: 0 10Product: 0 11Version: 0 12Capabilities: 0 13DAC Type: Fixed, 6bit 14Total RAM: 512KB 15src/vesa.c.280 vesa_PrintVBEInfo() End of VESA BIOS information 16---------- 17src/vesa.c.291 vesa_PrintVBEModes() VESA mode list follows 18Mode 0: 170h 19Mode 1: 171h 20Mode 2: 100h 21Mode 3: 101h 22Mode 4: 103h 23Mode 5: 104h 24Mode 6: 102h 25Mode 7: 6ah 26Mode 8: 108h 27Mode 9: 109h 28Mode 10: 10ah 29Mode 11: 10bh 30Mode 12: 10ch 31src/vesa.c.300 vesa_PrintVBEModes() Found 13 VESA modes
There's also a difference in behaviour in attempting to change the DAC mode via INT10 (both report 6bpp, fixed, but I thought it would be an interesting exercise to see what happens if I blindly attempt to programme it for 8bpp).
First, here's the Tseng card:
1src/gfx.c.156 gfx_Init() VESA BIOS indicates VGA DAC is fixed at 6bpp 2src/gfx.c.157 gfx_Init() ... but let's try changing it anyway! 3src/vesa.c.147 vesa_SetDAC() Setting VESA DAC mode 8bpp 4src/vesa.c.164 vesa_SetDAC() Successfully set VESA DAC mode 8bpp 5src/vesa.c.177 vesa_GetDAC() Checking VESA DAC mode for 8bpp 6src/vesa.c.194 vesa_GetDAC() VESA DAC mode is 6bpp, this is WRONG!
It lets me call the INT10 function to set the DAC depth (and the return value in AX indicates success), but on re-reading the value back it remains at 6bpp.
The Trident card responds differently:
1src/gfx.c.156 gfx_Init() VESA BIOS indicates VGA DAC is fixed at 6bpp 2src/gfx.c.157 gfx_Init() ... but let's try changing it anyway! 3src/vesa.c.147 vesa_SetDAC() Setting VESA DAC mode 8bpp 4src/vesa.c.158 vesa_SetDAC() Error, Unable to set VESA DAC mode 8bpp [return code 0x4f08] 5src/gfx.c.162 gfx_Init() Unable to switch VGA DAC, defaulting to 6bpp
The code to set the DAC depth fails, as indicated by the value 0x4F08 returned in AX.
(Setting the VGA DAC to 8bpp) remains an interesting thought exercise until I can get a machine together where I can actually test my code path. I've got a PVI-486SP3 with the choice of a Voodoo3, Trio64 or Millenium II, so I'll have to fire that up to check that it works as expected on a switchable 6/8bpp VGA DAC.
The big positive is that my code works on real hardware (and my 286, at that!), so I'm fairly pleased so far.
- The background UI bitmap is copy-pasted from the PC-98 version, hence the PC9801 logo in the bottom right.
- No input code yet, so as soon as the menu interface is built and the browser pane populated I'm just doing a return();
- All of the small icons, checkboxes, pre-formatted text etc is loaded entirely in to base 640KB. Larger assets like the default UI background, scroll window backgrounds etc just have their headers loaded and then re-read from the disk, line-by-line as the page is refresh and redrawn. I've yet to see what the performance for this is like on my 286.
The attachment progress4.png is no longer available
Metadata loading works - any games that the application scrapes can also have a metadata file (launch.dat) in their directory, this is a simple ini-style file and can have the following entries:
1[default] 2name=Advanced Power Dolls 2 3developer=Kogado Studio Inc. 4midi_mpu=0 5midi_serial=1 6year=1996 7genre=Strategy 8images=cover.bmp,screen1.bmp,screen2.bmp,box.bmp 9series=Power Dolls 10start=game.exe 11alt_start=config.exe
There's some minor graphical glitches in the output - I think I still have some drawing primitives (box/line/filled, etc) that are not cast to the correct integer types, but certainly all of the bitmap and bitmap font stuff is working spot on.
Next up is replacing the PC-98 specific keyboard handling with IBM PC alternatives. I don't imagine there will be much to change though.
1src/vesa.c.147 vesa_SetDAC() Setting VESA DAC mode 8bpp 2src/vesa.c.164 vesa_SetDAC() Successfully set VESA DAC mode 8bpp
Does the call to set the palette width actually return 8 in BH? The standard says the call is still allowed to succeed if the requested width is higher than possible, just that the hardware will use the closest possible value (which will be returned in register BH).
I could probably add setting the palette width to DOSBox, it's already a variable but is always assigned a value of 6 with no code hooked up to adjust it.
AX=0x004F, indicating success, but clearly the hardware (or the VESA TSR in use by the Tseng Labs card) is changing it to 6bpp. The Trident card acts different, as the call fails; it does not return 0x004F.
I would dearly love to know where my long-lost CL-GD5428 got to in the last 6 years, as that would be another ISA card to test with. I can try some PCI cards (Trio, Virge, Millenium etc) on my 486, but that will take a day or two to clear out space and set it up.
If I load TL1VESA.COM (an ET3000/ET4000 VESA TSR) it loads and the MSDOS logo and initial progress bar and font display perfectly (hurrah!) but querying the vbe capabilities field seems to indicate that the DAC is fixed at 6bit. That could be an artifact of the TSR however, as my card has the Sierra highcolour/truecolour DAC chip.
HiColor doesn't imply that the DACs are 8-bit wide.
See eg. the Sierra HiColor RAMDAC series: SC11482 and SC11483 have 6-bit DACs, only the SC11484 has 8-bit.
TrueColor RAMDACs all have 8-bit DACs, of course, but I wouldn't be sure if the full width is always available in the palette modes.
Zaglądali do kufrów, zaglądali do waliz, nie zajrzeli do dupy - tam miałem klimatyzm.
Sorry, it's a MUSIC MU9C4910, which is supposed to be 15/16/24bit:
The attachment IMG20210202214745.jpg is no longer available
Anyway, it's not a major issue. Palette setting, whether 6bpp or 8bpp is working 'good enough'; it might be interesting to visit the possibility of 15/16bit modes in the future but it's not a priority right now (and I hate to think about throwing about twice the amount of pixel data in 'hi res' on a 286).
Input now works, so I can scroll through the list of scraped games. Image loading of 8bpp bitmap files as listed in each games metadata file are also loaded,
Here's a real example of a screenshot as displayed in the interface for the title '3D Demon' (it's the first game in my folder, alphabetically, so its just easier to test metadata and image loading from the first item in the list - yes, yes, I know it's from BASS, but it's the first example I had at hand):
The attachment progress5.png is no longer available
However, compared to the performance on the PC-98, it's slow. Redraws of the browser pane (basically, any time you move the cursor) are excruciating... you can't just tap-tap-tap the up and down cursor keys; it's more like tap (1s), tap (1s) tap......
I'll have to do drop some timers in so that I can see where the bottleneck is.
Oh, and those issues I thought were gfx primitives operations going wrong aren't (the four thin horizontal lines in the image) - I think they are artifacts of the copy to the VGA memory windows since they seem to be at the end of each 64k section of the display. I suspect my logic isn't quite correct.
OMG this is the answer to my prayers!! A DOS launcher that runs on DOS, loads metadata and shows screenshots!
A few suggestions:
- Allow to sort/filer by developer, year, genre, etc..;
- Allow for a game box cover screenshot as well (mobygames is a great source for these), thumbnails are tiny, not exceeding 10kb;
- Support at least 1000 games on the database;
- Option to run game and run setup;
- Option to prompt for parameters before game launch;
carlostexwrote on 2021-02-02, 23:17:- Allow to sort/filer by developer, year, genre, etc..;
- Allow for a game box cover screenshot as well (mobygames is a great s […] Show full quote
- Allow to sort/filer by developer, year, genre, etc..;
- Allow for a game box cover screenshot as well (mobygames is a great source for these), thumbnails are tiny, not exceeding 10kb;
- Support at least 1000 games on the database;
- Option to run game and run setup;
- Option to prompt for parameters before game launch;
In order:
1. Genre and "game series" filtering is already in and working. Publisher and year would be trivial enhancements to add.
2. Already done.
3. Memory is the only limit. I need a way of counting available free memory while it is running, but each game 'object' in ram is a few hundred bytes, so I'd say at least 500 would be doable. My PC98 setup had about 300 titles in mostly the same codebase, but it was in protected mode and not hamstrung by 640k.
4. Already done. Choice of two start commands, defined in each games metadata file.
5. Hmmm... I did think about this, since the pc98 and x68k are very similar in terms of games with arguments to start. What most of those users seem to have settled on is writing a "start.bat" with the exe and any flags needed. Text entry in the interface is doable, but another world of complexity.
3. Memory is the only limit. I need a way of counting available free memory while it is running, but each game 'object' in ram is a few hundred bytes, so I'd say at least 500 would be doable. My PC98 setup had about 300 titles in mostly the same codebase, but it was in protected mode and not hamstrung by 640k.
Each 'object' takes a few hundred bytes? Let's say each one is 333 bytes, you can fit 3 games in 1KB. Of course this depends how much RAM the rest of the program takes, and of course the actual size of each 'object', but yeah i think you should be able to do more than 500. Whatever above 500 is possible would be nice.
David Jason Carr's Launchbox was programmed in VisualBasic, which of course itself is a hog of a programming language and his program does around 330 games before crapping out. Your program is more complex, and so is the metadata too, but C is much more effective than VisualBasic, so you should win there. Some inline assembler would probably help making the program more efficient as far as RAM usage and speed too. I have no idea how competent those C compilers for DOS are.
1typedef struct gamedata { 2 int gameid; // Unique ID for this game - assigned at scan time 3 char drive; // Drive letter 4 char path[65]; // Full drive and path name; e.g. A:\Games\FinalFight 5 char name[MAX_STRING_SIZE]; // Just the directory name; e.g. FinalFight 6 int has_dat; // Flag to indicate __launch.dat was found in the game directory 7 struct gamedata *next; // Pointer to next gamedata entry 8} gamedata_t;
There's then a linked-list of all the games scraped from disk and created as one of those gamedata objects. The currently selected game may, or may not have metadata also available, which is then represented as:
1typedef struct launchdat { 2 char realname[MAX_STRING_SIZE]; // A 'friendly' name to display the game as, instead of just the directory name 3 char genre[MAX_STRING_SIZE]; // A string to represent the genre, in case we want to filter by genre 4 int year; // Year the game was released 5 int midi; // Supports MIDI out 6 int midi_serial; // Supports MIDI serial out 7 char series[MAX_STRING_SIZE]; // Series name; e.g. Gradius, Streetfighter, etc. 8 char publisher[MAX_STRING_SIZE]; // The name of the publisher 9 char developer[MAX_STRING_SIZE]; // The name of the developer 10 char start[MAX_FILENAME_SIZE]; // Name of the main start file 11 char alt_start[MAX_FILENAME_SIZE]; // Name of an alternative start file (e.g a config utility) 12 char images[IMAGE_BUFFER_SIZE]; // String containing all the image filenames 13} launchdat_t;
But, only one of those is loaded at a time. Same goes for any artwork/boxart; only one is loaded at a time. So as long as things don't go too crazy in terms of size of the exe file (it's ~360KB at present, including 256KB of data pre-allocating the video buffer) and don't load too many more artwork assets into memory (I have perhaps 60KB loaded at the moment - icons, buttons etc), there's probably ~200KB or thereabouts of the base 640KB remaining.
Don't think I'm hitting an actual limit here - but when I exclude some of my game folders (D:\Advnture and D:\Misc) the application runs fine (with over 400 entries in the database). If I include them (even if excluding everything else) the interface locks up.
I suspect a possible directory name or similar issue somewhere in the parsing/display of the scraped-and-sorted entries, so as yet, I haven't hit an upper limit on the number of entries.
I'll have to do some digging in to the (lack of) performance as I've been adding some timers to various critical sections:
1src/timers.c.24 GFX Init : 55 ticks 2src/main.c.231 Valid graphics mode found 3src/timers.c.24 UI Init : 0 ticks 4src/timers.c.24 Font Loading : 220 ticks 5src/timers.c.24 UI Asset Loading : 1922 ticks 6src/timers.c.24 Game Scraping : 2527 ticks 7src/timers.c.24 Game Sorting : 9392 ticks 8src/timers.c.24 Draw full UI buffer (-info) : 2746 ticks 9src/timers.c.24 Draw full UI buffer (+info) : 2746 ticks 10src/timers.c.24 Flip GFX buffer : 55 ticks 11src/timers.c.24 Redraw New Game : 659 ticks 12src/timers.c.24 Scroll Browser Down : 659 ticks 13src/timers.c.24 Redraw New Game : 659 ticks 14src/timers.c.24 Redraw New Game : 659 ticks
This is on Dosbox @ 0-frameskip and 3000 cycles and at my current maximum of 409 titles, so take the figures with a pinch of salt. Certainly the RAM to VGA flip is not the culprit (steady 55 clock ticks each time), but the scroll and redraw routine is hurting a lot.
Ok, got a major speedup with the functions which redraw the newly selected game - from ~700-800 ticks to ~100.
One entire up/down cursor movement generated the following metrics:
For a game with artwork
1src/timers.c.24 - Clear artwork window : 494 ticks 2src/timers.c.24 - Lookup game : 0 ticks 3src/timers.c.24 - Load metadata : 55 ticks 4src/timers.c.24 - Process artwork list : 0 ticks 5src/timers.c.24 - Update UI Info pane : 55 ticks 6src/timers.c.24 - Display artwork : 220 ticks 7src/timers.c.24 Redraw New Game : 824 ticks
For a game without artwork
1src/timers.c.24 - Clear artwork list : 0 ticks 2src/timers.c.24 - Clear artwork window : 549 ticks 3src/timers.c.24 - Lookup game : 0 ticks 4src/timers.c.24 - Update UI Info pane : 110 ticks 5src/timers.c.24 - Display artwork : 0 ticks 6src/timers.c.24 Redraw New Game : 659 ticks
Ouch, the clearing of the artwork window is expensive. Those routines do a few different things, setting and clearing some variables, but the main one being a call to my gfx_BoxFill() primitive, which paints a rectangle in a given colour.
I hadn't optimised it since writing it, and it was implemented as:
There is a bit more complexity there, checking for negative dimensions, etc, but that's the gist of it. I've now replaced it with a call to memset() as follows:
That drops the time to move the cursor down a line, clear the artwork window and load the new data to the following:
1src/timers.c.24 Scroll Browser Down : 0 ticks 2src/timers.c.24 - Clear artwork list : 0 ticks 3src/timers.c.24 - Clear artwork window : 55 ticks 4src/timers.c.24 - Lookup game : 0 ticks 5src/timers.c.24 - Update UI Info pane : 55 ticks 6src/timers.c.24 - Display artwork : 0 ticks 7src/timers.c.24 Redraw New Game : 110 ticks
110 ticks in total, and the clearing of the artwork window is ten times faster. Just a tenth of a second (based on a 1000 clock tick granularity) to move between rows.
Ouch, the clearing of the artwork window is expensive. Those routines do a few different things, setting and clearing some variables, but the main one being a call to my gfx_BoxFill() primitive, which paints a rectangle in a given colour.
Maybe I missed this in the thread, but how do you deal with the segmented frame buffer? I believe you had a buffer in internal memory that represents the entire screen (256Kb), the Watcom __huge buffer. But how do you copy to the screen? Isn't each of the video buffer segments located at 0xA000 and you need to switch segment? Also a segment might end in the middle of a scanline, right?
Ouch, the clearing of the artwork window is expensive. Those routines do a few different things, setting and clearing some variables, but the main one being a call to my gfx_BoxFill() primitive, which paints a rectangle in a given colour.
Maybe I missed this in the thread, but how do you deal with the segmented frame buffer? I believe you had a buffer in internal memory that represents the entire screen (256Kb), the Watcom __huge buffer. But how do you copy to the screen? Isn't each of the video buffer segments located at 0xA000 and you need to switch segment? Also a segment might end in the middle of a scanline, right?
Here's the gfx_Flip() function in full. It operates on the 256KB __huge array that I do all of my compositing, 'blits', sets, fills and clears into:
1void gfx_Flip(){ 2 // Copy a buffer of GFX_ROWS * GFX_COLS bytes to 3 // the active VRAM framebuffer for display. 4 5 unsigned short int window; 6 long int left; 7 8 // Set the vram pointer to the start of the buffer 9 vram = vram_buffer; 10 11 left = (long int) VRAM_END; 12 13 // for each window in the number of windows for this video mode 14 for(window = 0; window < windows_in_use; window++ ){ 15 16 // set new window to be active 17 vesa_SetWindow(window); 18 19 // copy the block of pixels for this memory window 20 if (left > window_bytes){ 21 if (GFX_VERBOSE){ 22 printf("%s.%d\t gfx_Flip() Copying %ld bytes to window %d\n", __FILE__, __LINE__, window_bytes, window); 23 } 24 _fmemcpy(VGA, vram, window_bytes - 1); 25 26 left -= (window_bytes - 1); 27 } else { 28 if (GFX_VERBOSE){ 29 printf("%s.%d\t gfx_Flip() Copying remaining %ld bytes to window %d\n", __FILE__, __LINE__, left, window); 30 } 31 _fmemcpy(VGA, vram, left); 32 } 33 34 // Increment the pointer to the vram buffer by the size of one video window 35 vram += (long int) (window_bytes); 36 }; 37 38 // Reset vram buffer pointer position 39 vram = vram_buffer; 40}
I'm not going to pretend that it's perfect... and I know that I still have a bug somewhere which is causing this when the buffer is copied to the 4 video memory segments:
The attachment progress6.png is no longer available
But, by and large, it's working, and the copy mechanism isn't proving to be a bottleneck (for my purposes, at least), at least compared to some of my other crummy code 😀