VOGONS


Reply 20 of 37, by keenmaster486

User metadata
Rank l33t
Rank
l33t

Update: I reworked my test code in C.
Link to repository: https://github.com/keenmaster486/BEEP6

It is working now! Sets the video mode and can draw some lines. I can move forward with this, although I still wish I could do it in C++ instead.

World's foremost 486 enjoyer.

Reply 21 of 37, by X3J11

User metadata
Rank Newbie
Rank
Newbie

You should add a .gitignore to your repository, ignore obj, exe, err etc...

Someone mentioned earlier; Watcom defaults to its own calling convention where the first four parameters to a function call are passed in registers. Other compilers use the default of passing all args on the stack from right to left.

You can change this with a command line option, look at -#r or -#s. The # is the target cpu model, 0-6 for 8086 to PPro. The register convention is faster, but incompatible with libraries built with other compilers. It can be overridden in code with pragmas, but it's better to rebuild the library with Watcom, and use the same convention throughout. If I get a chance I'll look into why it's not working as C++. I'm a bit rusty, but I've been using Watcom off and on for over 20 years now, so maybe I can help.

Reply 22 of 37, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie
X3J11 wrote:

You can change this with a command line option, look at -#r or -#s.

that only works for 386 and up, -3s, -3r etc. you cant do it with -0 to -2 for 8086/286 code. Since he was targeting -0, you have to do use the -ec# switches.

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 23 of 37, by breadbin

User metadata
Rank Newbie
Rank
Newbie

I'd also recommend a good resource for C programming, in particular game programming by Alex Russell.

http://www3.telus.net/alexander_russell/cours … ntroduction.htm

He programmed one of my favourite games - Spelljammer!

Reply 24 of 37, by X3J11

User metadata
Rank Newbie
Rank
Newbie
BloodyCactus wrote:

that only works for 386 and up, -3s, -3r etc. you cant do it with -0 to -2 for 8086/286 code. Since he was targeting -0, you have to do use the -ec# switches.

I stand corrected. 20+ years, and I learned something new. 😀. Thanks!

Although, in fairness, I really only used Watcom for its 32-bit compiler. I'm sure I did some real mode stuff back in the day, but I'll be darned if I can remember it.

Reply 25 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
gerwin wrote:

Are you sure Mode-X is the way to go? It is a hack. It is a planar mode, meaning you can't just write bytes of pixels in a straightforward manner. Many developers at the time decided to stick to mode 13h because of that, because for their projects the overhead of planar drawing negated the benefit.

I think the reason why so many games are mode 13h is mainly because of 2 reasons:
1) Mode 13h is compatible with MCGA, so lowest common denominator.
2) Mode X is unique to the PC, and quite complicated to use. Not a good target for simple ports from other platforms.

gerwin wrote:

VESA fixed it eventually: Paging combined with a proper linear frame buffer.

In theory yes. In practice VESA support was spotty initially, and not many games made use of it at all.
By the time VESA was properly standardized and was ready for mainstream use, there was Windows 95 with DirectDraw, which solved the same problem.

Aside from that, Mode X has some obvious advantages:
1) You can use the full 256k of memory, allowing you to perform scrolling on larger windows, or double-buffering wth page flipping.
2) You can perform efficient fill routines by writing to all 4 bitplanes at a time.
3) You can store sprites or game tiles in offscreen memory for efficient blitting with the ALU

These advantages are basically the same as for EGA.

Last edited by Scali on 2019-09-09, 08:35. Edited 1 time in total.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 26 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
gerwin wrote:

When I wrote Mode-X is a hack, it is because it was not in the normal list of modes offered by the hardware, and as such it is was not tested like those modes. Some video hardware has quirks with Mode-X, Like I can recognize mode-X on my CL VLB graphics card because it starts to give random blinking pixels. I read some cards do not scroll smoothly. I figure that when Doom got popular, hardware vendors were forced to better prepare for Mode-X and variants. But that is 486-era already.

Wolfenstein 3D also makes use of Mode X, and that was in the 286-era.
I have never seen any VGA card that cannot handle mode X. The scrolling problems are not necessarily related to mode X either. They occur in Commander Keen, which uses EGA mode 0Dh, not mode X. It's to do with timing issues.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 27 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
keenmaster486 wrote:

I can move forward with this, although I still wish I could do it in C++ instead.

I would advise against using C++ on such low-end systems.
C will create more efficient, compact code, and is easier to interface with libraries and assembly code.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 28 of 37, by gerwin

User metadata
Rank l33t
Rank
l33t
Scali wrote:

Wolfenstein 3D also makes use of Mode X, and that was in the 286-era.
I have never seen any VGA card that cannot handle mode X. The scrolling problems are not necessarily related to mode X either. They occur in Commander Keen, which uses EGA mode 0Dh, not mode X. It's to do with timing issues.

Yeah, Good to know. Lets retract the 'it is a hack part' of my initial reply.

Indeed properly functional VESA was late to arrive. Though I am glad it is available now, and working well on Voodoo 3, Nvidia and later S3 cards.

--> ISA Soundcard Overview // Doom MBF 2.04 // SetMul

Reply 29 of 37, by amadeus777999

User metadata
Rank Oldbie
Rank
Oldbie
gerwin wrote:

Are you sure Mode-X is the way to go? It is a hack. It is a planar mode, meaning you can't just write bytes of pixels in a straightforward manner. Many developers at the time decided to stick to mode 13h because of that, because for their projects the overhead of planar drawing negated the benefit.

VESA fixed it eventually: Paging combined with a proper linear frame buffer.

See: Doom in DOS: Original vs Source Ports

Interesting!

Reply 30 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
Deksor wrote:

This doesn't seem it is mode 0x0D, is it ?

Yes it is.
I also discussed it here shortly:
ATI Graphics Solution

The purple background is scrolled in one bitplane. The blue scroller is in another bitplane (it's just one colour, but the palet […]
Show full quote

The purple background is scrolled in one bitplane.
The blue scroller is in another bitplane (it's just one colour, but the palette is changed per scanline, similar to copper tricks on Amiga).
The remaining two bitplanes are used for the scaling logo and vertical scroller.
This would give you huge headaches in VGA mode.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 31 of 37, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

@Scali

I'm very much intrigued by this. I have done a fair bit of modex and unchained vga programming but never really ventured into the 16 colors modes. In vga unchained the bitplanes are simply presented as laying next to each other making columns of 4 pixels wide each plane holding an 8 bit pixel/color value and I never thought of it being different in the 16 colors modes.

Do you happen to know any programming sources on this specific technique of bitplanes overlaying each other?

Reply 32 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
pan069 wrote:

Do you happen to know any programming sources on this specific technique of bitplanes overlaying each other?

Not specifically no.
I did cover it somewhat in my blog series, but not too much in-depth I guess:
https://scalibq.wordpress.com/2011/11/23/just … ld-skool-style/

There is a diagram there (actually for the Atari ST, ignore the part about the words stored in screen memory), which should demonstrate the layout:
If you have a pixel of N bits, you split it up in N bitplanes.
Each bitplane is a 1-bit bitmap.
The N bitmaps are then OR'ed together when the pixels are output. This is why they are effectively 'overlaid'.

Because the pixels are split up into separate 1-bit bitmaps/bitplanes, you can selectively update only the bitplanes you're interested in.
So you can write text in a 1-bit font on a single bitplane, while you leave the others untouched. So the other N-1 bits are not overwritten by your text. The text will act as an 'overlay'.

Extra magic can be performed by cleverly choosing your palette.
For example, if you have 16 colours, you can eg divide it into 2*8 colours, where one bitplane selects between the two sets of 8 colours.
So eg you can have 8 normal colours, and then the same 8 colours with a blue-ish hue applied. This means that you can draw a mask into that bitplane to give certain parts a blue hue, to make it look underwater.
Or use a lighter and darker pattern for shadow effects.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 34 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
pan069 wrote:

Great. Thanks! I'm going to have a bit of a deep dive into this, see if I can wrap my head around this. 😀

The EGA manual may help you to see what you can do with the ALU and the read/write masks:
http://minuszerodegrees.net/oa/OA%20-%20IBM%2 … s%20Adapter.pdf

This page also explains the different write modes for bitplanes, and how the shifter, mask, latches etc work together:
https://wiki.osdev.org/VGA_Hardware

This is somewhat unique to EGA/VGA... all reads/writes on video memory pass through some logic on the video card first, which allows the card to perform rotates, masks and other stuff.
So you cannot actually manipulate the bytes in the EGA's VRAM directly. You are actually reading data into the ALU's latches, and writing the latches back to the bitplanes.
This allows you to manipulate up to 4 bitplanes at a time. Other bitplane-based display systems, such as Atari ST and Amiga, don't have anything like that.

When you actually want to read back the data to the CPU, you can only read from one bitplane at a time of course. But normally that is not something you would ever need to do.

The 1991 donut I made some years ago, is also using EGA mode 0Dh:
https://scalibq.wordpress.com/2013/11/24/just … -like-its-1991/
https://scalibq.wordpress.com/2014/06/08/just … -like-its-1991/
https://scalibq.wordpress.com/2014/08/21/1991 … -final-release/

The poly filler uses the EGA ALU to render many pixels at a time.
It also uses the ALU to render the scroller. You can only address per-byte, which is 8-pixel intervals in mode 0Dh. So I use the ALU to rotate and mask each byte as it is written.
This allows me to rotate and mask the scroller into pixel-perfect position. It scrolls at a speed of 1 pixel per frame, at 60 Hz, so ultra-smooth.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 35 of 37, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

@Scali

So, I did a bunch of reading and I tinkered something together. Inspired by the DoWhackaDo demo I have been trying to figure out how the logo was done (i.e. move behind and in front of the scroller) and I think I might have figured that bit out. It's basically a palette change but I'd love to hear your thoughts on it.

Here is an example. It's super rudimentary, trying to keep it as concise as possible. The only thing in there that I do not fully understand is the how the palette is set. I actually got this code (the load_palette function) from my own archives, I must have written this at least 25 years ago but I added no comments to it, so I have no idea how it actually works. Must have made sense at the time 😀 I've set the palette in mode 13h plenty of times but for some reason I do not yet understand how it works different in mode 0dh. Love your input on that, especially the first bit that sets the attribute index.

Anyhow, this is my code snippet. Use the space bar to switch the white box (substitute for the logo) behind or in front of the blue bars (substitute for the scroller). I refer to the vertical scrollers as "banner".

Also, I have attached an EXE that should work in DosBox.

#include <conio.h>
#include <string.h>

#define VGA_INPUT_STATUS 0x3da
#define VGA_VRETRACE 0x08

#define VGA_SEQUENCER_INDEX 0x3c4
#define VGA_MAP_MASK 0x02

#define VGA_ATTRIBUTE_INDEX 0x3c0

#define VGA_PALETTE_INDEX 0x3c8
#define VGA_PALETTE_DATA 0x3c9

#define VGA_CRT_INDEX 0x3d4
#define VGA_CRT_DATA 0x3d5

#define VGA_ADDRESS_HIGH 0x0c
#define VGA_ADDRESS_LOW 0x0d

#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200

#define PLANE_0 0x01
#define PLANE_1 0x02
#define PLANE_2 0x04
#define PLANE_3 0x08

#define TILE_SIZE 10

#define KEY_ENTER 0x0d
#define KEY_SPACE 0x20

#ifndef FP_SEG
#define FP_SEG(__p) ((unsigned)((unsigned long)(void __far*)(__p) >> 16))
#endif

#ifndef FP_OFF
#define FP_OFF(__p) ((unsigned)(__p))
#endif

#ifndef MK_FP
#define MK_FP(__s,__o) (((unsigned short)(__s)):>((void __near *)(__o)))
#endif

typedef struct {
char r;
char g;
char b;
}
RGB;

RGB palette1[16] = {
{0x00 >> 2, 0x00 >> 2, 0x00 >> 2}, // 0000 -
{0xff >> 2, 0x00 >> 2, 0x00 >> 2}, // 0001 - background

{0x00 >> 2, 0xff >> 2, 0x00 >> 2}, // 0010 - banner
{0x00 >> 2, 0xff >> 2, 0x00 >> 2}, // 0011 - banner

{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0100 - scroller
Show last 199 lines
  {0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0101 - scroller
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0110 - scroller
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0111 - scroller

{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1000 - logo
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1001 - logo

// Logo before scroller
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1010 - logo
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1011 - logo
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1100 - logo (before scroller)
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1101 - logo (before scroller)
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1110 - logo (before scroller)
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1111 - logo (before scroller)
};

RGB palette2[16] = {
{0x00 >> 2, 0x00 >> 2, 0x00 >> 2}, // 0000 -
{0xff >> 2, 0x00 >> 2, 0x00 >> 2}, // 0001 - background

{0x00 >> 2, 0xff >> 2, 0x00 >> 2}, // 0010 - banner
{0x00 >> 2, 0xff >> 2, 0x00 >> 2}, // 0011 - banner

{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0100 - scroller
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0101 - scroller
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0110 - scroller
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 0111 - scroller

{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1000 - logo
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1001 - logo

// Logo behind scroller
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1010 - logo
{0xff >> 2, 0xff >> 2, 0xff >> 2}, // 1011 - logo
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 1100 - logo (behind scroller)
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 1101 - logo (behind scroller)
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 1110 - logo (behind scroller)
{0x00 >> 2, 0x00 >> 2, 0xff >> 2}, // 1111 - logo (behind scroller)
};

unsigned char far *_video_mem = (unsigned char far*)0xA0000000L;

void set_mode(unsigned short mode)
{
_asm {
mov ax,mode
int 0x10
}
}

void load_palette(RGB *palette, int colors)
{
unsigned short pal_seg = FP_SEG(palette);
unsigned short pal_off = FP_OFF(palette);
unsigned short len = colors * 3;

_asm {
xor bl,bl
pal_loop:
mov dx,VGA_INPUT_STATUS
in al,dx

mov dx,VGA_ATTRIBUTE_INDEX
mov al,bl
out dx,al
out dx,al

inc bl
cmp bl,10h
jb pal_loop

mov dx,VGA_ATTRIBUTE_INDEX
mov al,20h
out dx,al

// Select color zero and write the palette data.

mov dx,VGA_PALETTE_INDEX
xor al,al
out dx,al

mov ax,pal_seg
mov ds,ax
mov si,pal_off
mov cx,len

inc dx
rep outsb
}
}

void select_plane(unsigned short plane)
{
outpw(VGA_SEQUENCER_INDEX, (plane << 8) + VGA_MAP_MASK);
}

void draw_rect(int x, int y, int width, int height, unsigned char pattern)
{
int i;
int w = width >> 3;

char *video = _video_mem + (y * (SCREEN_WIDTH / 8)) + (x >> 3);

for (i = y; i < y + height; i++) {
memset(video, pattern, w);

video += SCREEN_WIDTH / 8;
}
}

void draw_background()
{
int r, x, y;
unsigned char color;

select_plane(PLANE_0);

for (r = 0; r < SCREEN_HEIGHT / TILE_SIZE; r++) {
color = r % 2;

for (y = 0; y < TILE_SIZE; y++) {
for (x = 0; x < SCREEN_WIDTH / 8; x += 2) {
if (color) {
int o = (r * TILE_SIZE + y) * (SCREEN_WIDTH / 8) + x;

_video_mem[o + 0] = 0xff;
_video_mem[o + 1] = 0xff;
}

color ^= 1;
}
}
}
}

void draw_banner()
{
select_plane(PLANE_1);

draw_rect( 8, 0, 32, SCREEN_HEIGHT, 0xff);
draw_rect(SCREEN_WIDTH - 40, 0, 32, SCREEN_HEIGHT, 0xff);
}

void draw_scroller()
{
select_plane(PLANE_2);

draw_rect(0, 64, SCREEN_WIDTH, 24, 0xff);
draw_rect(0, 112, SCREEN_WIDTH, 24, 0xff);
}

void draw_logo()
{
select_plane(PLANE_3);

draw_rect(96, 48, 128, 104, 0xff);

}

void main()
{
int pal = 0;

set_mode(0x0d);

load_palette(&palette1, sizeof(palette1) / sizeof(RGB));

draw_background();
draw_banner();
draw_scroller();
draw_logo();

while(1) {
if (kbhit()) {
int key = getch();

if (key == 27) {
break;
}

switch (key) {
case KEY_SPACE:
pal ^= 1;

if (pal == 0) {
load_palette(&palette1, sizeof(palette1) / sizeof(RGB));
}
else {
load_palette(&palette2, sizeof(palette2) / sizeof(RGB));
}

break;
}
}
}

set_mode(0x03);
}

Attachments

  • Filename
    MODE0D.EXE
    File size
    1.86 KiB
    Downloads
    60 downloads
    File license
    Fair use/fair dealing exception

Reply 36 of 37, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

Just managed to answer my own question regarding the palette code:

From Programmers Guide to the EGA/VGA and SuperVGA cards by Richard F. Ferraro:

8.1.5 Accessing the Palette Registers […]
Show full quote

8.1.5 Accessing the Palette Registers

The Palette Registers are contained in a dual-ported memory. The display memory requires access to the Palette Registers in order to refresh the display. The host requires access to the Palette Registers during read or write operations. The process of accessing the dual-ported Palette Registers is illustrated in Figure 8.8.

This dual-ported memory is accessed through a host port register at host I/O address 3C0. This port has two different functions. One is to index the Palette Registers. In this mode, it is called the Attribute Address Register. A second is to act as a portal to the Palette Registers. In this mode, it may be thought of as a data register, similar to the data registers of the other EGA/VGA register groups.

An internal hardware flip-flop is used that multiplexes this port to load either this Attribute Address Register or one of the Attribute Registers. When the flip-flop is in the clear state, data output from the host to port 3C0 hex is directed to this Attribute Address Register. When the flip-flop is in the set state, data written to this port is directed to whichever Attribute Register index is loaded into the ADR field of this register.

The flip-flop is controlled indirectly by the host. When a host assembly language input port instruction, IN, is issued to the Input Status #1 Register, the flip-flop is cleared. This register resides at port address 3BA hex for monochrome implementations or port addresses 3DA hex for color implementations. Once the flip-flop is cleared, the path is set so that outputs to this 3C0 hex port load an index that points to one of the Attribute Registers. This is equivalent to loading any of the other Address Registers. The next assembly language output port instruction written to this 3C0 hex port (OUT) will load the indexed Attribute Register. After the output occurs, the flip-flop automatically changes state. The following output will be directed once again to the Attribute Address Register.

Reply 37 of 37, by Scali

User metadata
Rank l33t
Rank
l33t
pan069 wrote:

So, I did a bunch of reading and I tinkered something together. Inspired by the DoWhackaDo demo I have been trying to figure out how the logo was done (i.e. move behind and in front of the scroller) and I think I might have figured that bit out. It's basically a palette change but I'd love to hear your thoughts on it.

Yup, that's probably how they did it.
As said, bitplanes are combined with a logical or-combination.
So if put your background scroller on bitplane 0, that will be value 0x1
Then you put your logo on bitplane 1, that will be value 0x2

Each pixel will then be (background OR logo).
So you get three possible values:
0x1: background only
0x2: logo only
0x3: background + logo

Which means you have various options to choose colour 0x3 in your palette:
Set it to the same colour as 0x1, and the logo will be behind the background
Set it to the same colour as 0x2, and the logo will be in front of the background
Set it to a colour that is a combination of logo and background, and you create a sort of translucent effect

And some people say bitplanes are useless!

pan069 wrote:

Here is an example. It's super rudimentary, trying to keep it as concise as possible. The only thing in there that I do not fully understand is the how the palette is set. I actually got this code (the load_palette function) from my own archives, I must have written this at least 25 years ago but I added no comments to it, so I have no idea how it actually works. Must have made sense at the time 😀 I've set the palette in mode 13h plenty of times but for some reason I do not yet understand how it works different in mode 0dh. Love your input on that, especially the first bit that sets the attribute index.

I'd like to remark that palettes are a bit of a mess on PC.
That is, CGA has its own palette registers, EGA has its own palette registers and VGA has its own palette registers.

In the case of DoWhackaDo, the video mode may be EGA, but the palette used is VGA. The same goes for my 1991 Donut for example, and a game like F29 Retaliator.

The way VGA is implemented is that the legacy palettes are logically mapped to the VGA palette.
That is, CGA has 16 colours in total, which are mapped to the first 16 colours in the VGA palette.
They match the CGA colours by default. However, if you modify the VGA palette after setting a CGA mode, you basically just use the CGA memory and palette layout, with your own pick of 18-bit VGA colours.

The same goes for EGA, alhough it is a bit more complicated. That is, where CGA's colours are hardwired (except for the background colour), EGA has its own 16-colour palette, where you can choose from a total of 64 (a 2:2:2 RGB format). Again, these colours are mapped onto the VGA palette.
So with the EGA palette, you can reorder colours if you like. But I wouldn't recommend it. The VGA palette is easier to manipulate, and if you use the extra VGA colours anyway, there's no reason to bother with both palettes.

One thing that's unique to the EGA palette, is that you have to disable and re-enable it before an update works (something that emulators generally do not enforce, so a nice case where people coding for emulators will get code that breaks on real hardware). That's another reason not to use the EGA palette: it's too cumbersome. VGA palette can be set on-the-fly, without having to worry about re-enabling it.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/