VOGONS


First post, by radiance32

User metadata
Rank Member
Rank
Member

Hi all,

I've veered into C/C++ development for 16bit 8086 DOS targets,
and I am currently experimenting with graphics.

After reviewing all the tools out there, I have finally, after trying many options,
started my project with OpenWatcom 1.9 on my Windows 10 PC,
using DOSBOX as my execution & testing environment for my compiled executables.

I'm currently writing a 3D wireframe rasterizer and I am currently using a
fast line algorithm in C++ that sets pixels using the graph.h graphics library
that comes with Watcom.
This library is somewhat similar to BGI (Borland Graphics Interface) for old Borland language compilers (although it's definately not compatible).

My target hardware uses a CGA display adapter,
and I can, with the Watcom graph.h library, initialize and set the CGA adapter to 640x200 mono,
and then have my line drawing algorithm set the pixels directly on the GPU using it's _putpixel() function.

However, I need double buffering, first because single buffering causes the model to be built while all the lines are drawn on the screen in say a second or so,
and with double buffering I can just set the bits in a memory array and then copy the array to the CGA adapter to display it.
Also, setting many individual bits in memory is much faster than setting many individual bits in graphics memory.

But, I've tried everything I can think of, and I just can't figure out the layout of the memory.

// Initialize the adapter to to HIRES CGA:
_setvideomode(_HRESBW);
_setcolor(15); // draw in white
struct videoconfig vc;
_getvideoconfig(&vc);
int width = vc.numxpixels;
int height = vc.numypixels;

// Then, I grab a copy of the framebuffer to get the right size for my videobuffer array:
long videobuf_size = _imagesize( 0, 0, width-1, height-1 ); // This value turns out to be 16006 (CGA HIRES mode requires 16KB so that makes sense, what the additional 6 are I don't know..

// Allocate the buffer
char *videobuf = (char*) malloc( videobuf_size );

And my main renderloop basically consists of these buffer related operations:

// Clear the buffer
for(int i=0; i<videobuf_size; i++) {
videobuf = 0;
}

// I then cast my char* buffer to a bool* buffer, so I can address the individual bits of it's monochrome bitmap
bool *bitbuf = (bool*) videobuf;


// Loop through my triangles, transform them and then draw lines
...

// The individual locations of the pixels for the line algorithm are set as such:

// set pixel at x and y on:
bitbuf[( y * (width-1) ) + x] = true;

// Once it's finished, I copy the buffer from memory to graphics memory to display it:

_putimage( 0, 0, videobuf, _GPSET );


IMO this should work, but, as you can see in the attached image, the whole thing is a mess of vertical lines.
I'm thinking that CGA memory does'nt work like I think it does and it would be awesome if someone here
could give me a hand to fix this as I've tried many things and nothing works...

I've attached an image of what it should look like (the blender suzanne monkey being rasterized),
and what it looks like when using my video buffer technique, a garbeled mess...

Thanks!,
Terrence

Attachments

  • x2.png
    Filename
    x2.png
    File size
    24.14 KiB
    Views
    597 views
    File license
    Fair use/fair dealing exception
  • x1.png
    Filename
    x1.png
    File size
    24.38 KiB
    Views
    597 views
    File license
    Fair use/fair dealing exception

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 1 of 18, by MobyGamer

User metadata
Rank Member
Rank
Member
radiance32 wrote on 2024-05-03, 02:57:

But, I've tried everything I can think of, and I just can't figure out the layout of the memory.

CGA screen buffer in monochrome mode starts at B800:0000 and is 640x200 pixels, with 8 pixels to a byte. The mode is interlaced; the first 8KB half of screen memory are the even lines and the second 8KB half are the odd lines.

long videobuf_size = _imagesize( 0, 0, width-1, height-1 ); // This value turns out to be 16006 (CGA HIRES mode requires 16KB so that makes sense, what the additional 6 are I don't know..

Post the source code of _imagesize, and what width and height are, otherwise we can't help you.

# Clear the buffer for(int i=0; i<videobuf_size; i++) { videobuf = 0; } […]
Show full quote

# Clear the buffer
for(int i=0; i<videobuf_size; i++) {
videobuf = 0;
}



memset() is much faster; use that.

# Once it's finished, I copy the buffer from memory to graphics memory to display it: _putimage( 0, 0, videobuf, _GPSET ); I […]
Show full quote

# Once it's finished, I copy the buffer from memory to graphics memory to display it:

_putimage( 0, 0, videobuf, _GPSET );


IMO this should work, but, as you can see in the attached image, the whole thing is a mess of vertical lines.



Post the source code of _putimage and what _GPSET is. Better yet, if you truly have a full buffer in system RAM, just use memcpy().

Moderator: Does VOGONs have a dedicated programming forum?

Reply 2 of 18, by radiance32

User metadata
Rank Member
Rank
Member
MobyGamer wrote on 2024-05-03, 03:24:
CGA screen buffer in monochrome mode starts at B800:0000 and is 640x200 pixels, with 8 pixels to a byte. The mode is interlaced […]
Show full quote
radiance32 wrote on 2024-05-03, 02:57:

But, I've tried everything I can think of, and I just can't figure out the layout of the memory.

CGA screen buffer in monochrome mode starts at B800:0000 and is 640x200 pixels, with 8 pixels to a byte. The mode is interlaced; the first 8KB half of screen memory are the even lines and the second 8KB half are the odd lines.

long videobuf_size = _imagesize( 0, 0, width-1, height-1 ); // This value turns out to be 16006 (CGA HIRES mode requires 16KB so that makes sense, what the additional 6 are I don't know..

Post the source code of _imagesize, and what width and height are, otherwise we can't help you.

# Clear the buffer for(int i=0; i<videobuf_size; i++) { videobuf = 0; } […]
Show full quote

# Clear the buffer
for(int i=0; i<videobuf_size; i++) {
videobuf = 0;
}



memset() is much faster; use that.

# Once it's finished, I copy the buffer from memory to graphics memory to display it: _putimage( 0, 0, videobuf, _GPSET ); I […]
Show full quote

# Once it's finished, I copy the buffer from memory to graphics memory to display it:

_putimage( 0, 0, videobuf, _GPSET );


IMO this should work, but, as you can see in the attached image, the whole thing is a mess of vertical lines.



Post the source code of _putimage and what _GPSET is. Better yet, if you truly have a full buffer in system RAM, just use memcpy().

Moderator: Does VOGONs have a dedicated programming forum?



Hi,

_putimage() is a function of the Watcom graph.h library.

#include <graph.h>
void _FAR _putimage( short x, short y,
char _HUGE *image, short mode );

Link to Documentation: https://open-watcom.github.io/open-watcom-v2- … .html#_putimage
The docs don't mention any formats etc..

There is also a _getimage() which does the opposite.
And the _imagesize() is also a Watcom graph.h function that returns the size of the coordinates given.

As for width = 640 and height = 200

_GPSET tells putimage() to copy the bits (eg no AND/ORs on the pixels etc...)

It would be really handy if you or someone else could provide some example C/C++ code as I am a Visual Learner and am not used to working with hex addresses etc...
I already read that CGA is interlaced, but I was hoping the Watcom graph.h _getimage and _putimage functions translate this for you... (maybe they do not, i'm not sure, documentation is abysmal and google searches yield few results if any)

Thanks!,
Terrence

PS: memset() thanks for the tip... It's been more than a decade since I've used it 😀

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 3 of 18, by MobyGamer

User metadata
Rank Member
Rank
Member

I can't provide watcom support, sorry. Maybe someone else can, either here or at VCFED forums.

To understand CGA, I would suggest writing a small program that sets the graphics mode, then writes a byte of 0xFF to a pointer set to the address B800:0000. You will see 8 pixels show up in the upper left corner, on line 0. Then write these values:
Write 0xFF to 0xB800:0x0050: You will see another 8 pixels show up in the upper left corner, but this time two lines down on line 2, because CGA's first 8KB covers only the even lines.
Write 0x55 to 0xB800:0x2000: You will see 8 *alternating* pixels show up in the upper left corner, but inbetween the others, on line 1, because you are now writing in the second 8KB "bank" of CGA memory which covers the odd lines.

Reply 4 of 18, by radiance32

User metadata
Rank Member
Rank
Member
MobyGamer wrote on 2024-05-03, 03:39:
I can't provide watcom support, sorry. Maybe someone else can, either here or at VCFED forums. […]
Show full quote

I can't provide watcom support, sorry. Maybe someone else can, either here or at VCFED forums.

To understand CGA, I would suggest writing a small program that sets the graphics mode, then writes a byte of 0xFF to a pointer set to the address B800:0000. You will see 8 pixels show up in the upper left corner, on line 0. Then write these values:
Write 0xFF to 0xB800:0x0050: You will see another 8 pixels show up in the upper left corner, but this time two lines down on line 2, because CGA's first 8KB covers only the even lines.
Write 0x55 to 0xB800:0x2000: You will see 8 *alternating* pixels show up in the upper left corner, but inbetween the others, on line 1, because you are now writing in the second 8KB "bank" of CGA memory which covers the odd lines.

You wouldn't happen to have any C/C++ code lying around or that you know of that directly addresses a CGA adapter like this ?

Thanks,
Terrence

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 5 of 18, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

you cant just cast a byte array to bool, and address it the way you do, its still a byte array underneath, not bits which is what cga is expecting (so bitbuf[( y * (width-1) ) + x] = true; wont do what you think its doing). you want to be doing bitwise ops with actual bits, not true + false on bytes. thats why your image is all dots with spaces, none of your bits are consecutive.

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

Reply 6 of 18, by radiance32

User metadata
Rank Member
Rank
Member

Can someone provide me with a simple example function in C/C++ on how to set an individual CGA pixel in an offscreen buffer like this one:

char *videobuf;
videobuf = (char*) malloc (0x4000); // 16384 bytes

eg I need a cheap/fast "setpixel(videobuf, x, y)"
Hopefully without having to read the entire char first, as I need as much speed as I can get...

Cheers,
Terrence

Last edited by radiance32 on 2024-05-03, 04:48. Edited 2 times in total.

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 7 of 18, by radiance32

User metadata
Rank Member
Rank
Member
BloodyCactus wrote on 2024-05-03, 04:06:

you cant just cast a byte array to bool, and address it the way you do, its still a byte array underneath, not bits which is what cga is expecting (so bitbuf[( y * (width-1) ) + x] = true; wont do what you think its doing). you want to be doing bitwise ops with actual bits, not true + false on bytes. thats why your image is all dots with spaces, none of your bits are consecutive.

Yeah, I figured that out not too long ago 😀

Terrence

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 8 of 18, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

Not sure if this is a 100% correct. I am sure some smart person here can point out bugs but it will give you at least some bare-bone Watcom based example without using libraries. [sorry, I do not have much time, just whipping this up while I am waiting for my wife to get ready as we're about to go out].

#include <i86.h>   // int86x
#include <conio.h> // kbhit, getch

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 200

void mode_set(unsigned char mode)
{
union REGS regs;
struct SREGS sregs;

regs.h.al = mode;
regs.h.ah = 0;

int86x(0x10, &regs, &regs, &sregs);
}

void pixel_set(int x, int y)
{
unsigned char far *video;
int offset;

if (y % 2) {
video = (unsigned char far*)0xb8000000L;
}
else {
video = (unsigned char far*)0xb8002000L;
}

// Calculate the offset of the byte in which we need to store the pixel.
offset = ((y / 2) * (SCREEN_WIDTH / 8)) + (x / 8);

// Then read that byte, OR in the pixel at the right position, and write the byte back.
*(video + offset) = *(video + offset) | (0x80 >> (x & 7));
}

void main()
{
// Set graphics mode 640 x 200
mode_set(6);

pixel_set(50, 50);

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

// 27 = ESC key
if (key == 27) {
break;
}
}
}

// Set graphics mode 640 x 200
mode_set(3);
}

Compile with (assuming you're saving the code in main.c):

wcc -zq -2 -bt=dos main.c -i$WATCOM/h
wlink option quiet sys dos name main.exe file main.o libpath $WATCOM/lib286:$WATCOM/lib286/dos

You have to make sure that the $WATCOM environment variable is set to your Watcom directory. For me on Linux it's set to

/usr/bin/watcom

Hope this helps.

Reply 9 of 18, by radiance32

User metadata
Rank Member
Rank
Member

Hi,

FIrst of all a big thanks for writing this for me, it's highly appreciated! 😀

I tried it out, and it works, but, there are some interleaving artifacts,
check out the attached screenshot...

Any idea what that might be and h0w to fix it ?

Cheers,
Terrence

pan069 wrote on 2024-05-03, 08:21:
Not sure if this is a 100% correct. I am sure some smart person here can point out bugs but it will give you at least some bare- […]
Show full quote

Not sure if this is a 100% correct. I am sure some smart person here can point out bugs but it will give you at least some bare-bone Watcom based example without using libraries. [sorry, I do not have much time, just whipping this up while I am waiting for my wife to get ready as we're about to go out].

#include <i86.h>   // int86x
#include <conio.h> // kbhit, getch

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 200

void mode_set(unsigned char mode)
{
union REGS regs;
struct SREGS sregs;

regs.h.al = mode;
regs.h.ah = 0;

int86x(0x10, &regs, &regs, &sregs);
}

void pixel_set(int x, int y)
{
unsigned char far *video;
int offset;

if (y % 2) {
video = (unsigned char far*)0xb8000000L;
}
else {
video = (unsigned char far*)0xb8002000L;
}

// Calculate the offset of the byte in which we need to store the pixel.
offset = ((y / 2) * (SCREEN_WIDTH / 8)) + (x / 8);

// Then read that byte, OR in the pixel at the right position, and write the byte back.
*(video + offset) = *(video + offset) | (0x80 >> (x & 7));
}

void main()
{
// Set graphics mode 640 x 200
mode_set(6);

pixel_set(50, 50);

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

// 27 = ESC key
if (key == 27) {
break;
}
}
}

// Set graphics mode 640 x 200
mode_set(3);
}

Compile with (assuming you're saving the code in main.c):

wcc -zq -2 -bt=dos main.c -i$WATCOM/h
wlink option quiet sys dos name main.exe file main.o libpath $WATCOM/lib286:$WATCOM/lib286/dos

You have to make sure that the $WATCOM environment variable is set to your Watcom directory. For me on Linux it's set to

/usr/bin/watcom

Hope this helps.

Attachments

  • dd2.png
    Filename
    dd2.png
    File size
    20.2 KiB
    Views
    407 views
    File license
    Fair use/fair dealing exception

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 10 of 18, by pan069

User metadata
Rank Oldbie
Rank
Oldbie
radiance32 wrote on 2024-05-03, 22:16:
Hi, […]
Show full quote

Hi,

FIrst of all a big thanks for writing this for me, it's highly appreciated! 😀

I tried it out, and it works, but, there are some interleaving artifacts,
check out the attached screenshot...

Any idea what that might be and h0w to fix it ?

No worries. Yeah, I was expecting my code to have some issues. Can you share your code so we can have a closer look?

Reply 11 of 18, by radiance32

User metadata
Rank Member
Rank
Member

Here's my pixel and line drawing functions (line does not use DrawCGAPoint, it has the code already in it):
Sharing the complete source would not be needed and it's a LOT of code... here's what's important:

void DrawCGAPoint (char *videobuf, int &videowidth, int &videoheight, int x, int y) {
unsigned char far *video;
if (y % 2) video = (unsigned char far*) &videobuf[0];
else video = (unsigned char far*) &videobuf[8192];

int offset = ((y / 2) * (videowidth / 8)) + (x / 8);
*(video + offset) = *(video + offset) | (0x80 >> (x & 7));
};

// THE EXTREMELY FAST LINE ALGORITHM Variation D (Addition Fixed Point)
void DrawCGALine(char *videobuf, int &videowidth, int &videoheight, int x, int y, int x2, int y2) {
bool yLonger = false;
int incrementVal, endVal;
int shortLen = y2 - y;
int longLen = x2 - x;
if (abs(shortLen) > abs(longLen)) {
int swap = shortLen;
shortLen = longLen;
longLen = swap;
yLonger = true;
}
endVal = longLen;
if (longLen < 0) {
incrementVal = -1;
longLen = -longLen;
}
else incrementVal = 1;
int decInc;
if (longLen == 0) decInc = 0;
else decInc = (shortLen << 8) / longLen;
int j = 0;
if (yLonger) {
for (int i = 0; i != endVal; i += incrementVal) {
#if defined (__WINDOWS)
SETPIXEL(x + (j >> 8), y + i, 15);
#elif defined (__WATCOM)
int xx = x + (j >> 8);
int yy = y + i;

unsigned char far *video;
if (yy % 2) video = (unsigned char far*) &videobuf[0];
else video = (unsigned char far*) &videobuf[8192];

int offset = ((yy / 2) * (videowidth / 8)) + (xx / 8);
*(video + offset) = *(video + offset) | (0x80 >> (xx & 7));
#endif
j += decInc;
}
}
else {
for (int i = 0; i != endVal; i += incrementVal) {
#if defined (__WINDOWS)
SETPIXEL(x + i, y + (j >> 8), 15);
#elif defined (__WATCOM)
int xx = x + i;
int yy = y + (j >> 8);

unsigned char far *video;
if (yy % 2) video = (unsigned char far*) &videobuf[0];
else video = (unsigned char far*) &videobuf[8192];
Show last 9 lines
                        
int offset = ((yy / 2) * (videowidth / 8)) + (xx / 8);
*(video + offset) = *(video + offset) | (0x80 >> (xx & 7));
#endif
j += decInc;
}
}
};

The rest below is just how I create, zero out and copy my memory buffer that I am drawing my lines in:

 // Allocate main memory screen buffer
int videobuf_size = 16384;
char *videobuf = (char*) malloc (0x4000); // 16384 bytes

Render loop does:

// Clear Screen Buffer
memset(videobuf, 0, 16384);

// Transform triangles
// Draw triangles as 3 lines with the above drawing function DrawCGALine(...)

// Copy videobuf to CGA adapter video memory
memcpy (vbufCGA, videobuf, 0x4000);

That's all of relevance (I think)

Anyways, I works, but there's some interlacing bug going on, as you can see in the image I attached to my previous post of the XYZ axis 3D object...

Cheers,
Terrence
...

// Copy videobuf to CGA adapter video memory
memcpy (vbufCGA, videobuf, 0x4000);

pan069 wrote on 2024-05-03, 22:51:
radiance32 wrote on 2024-05-03, 22:16:
Hi, […]
Show full quote

Hi,

FIrst of all a big thanks for writing this for me, it's highly appreciated! 😀

I tried it out, and it works, but, there are some interleaving artifacts,
check out the attached screenshot...

Any idea what that might be and h0w to fix it ?

No worries. Yeah, I was expecting my code to have some issues. Can you share your code so we can have a closer look?

Last edited by radiance32 on 2024-05-04, 01:14. Edited 4 times in total.

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 12 of 18, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

Hi.

Sorry, I couldn't get your line code to work so I made a new version of my previous code example. This version uses an off-screen memory buffer, draws a line (just a crappy implementation just for demo purposes), and then copies the buffer to the screen.

I made the off-screen buffer a linear buffer, this makes it much easier to work with in terms of plotting pixels etc. Only when the off-screen buffer is copied to the screen it takes the interleaved nature of the CGA video memory into account and copies an even scanline, then an odd scanline etc. until all scanlines in the off-screen buffer have been copied.

I hope this demonstrates that the pixel plotting works correctly and the copying of the off-screen buffer.

#include <i86.h>     // int86x
#include <conio.h> // kbhit, getch
#include <malloc.h> // _fmalloc, _ffree
#include <string.h> // _fmemcpy, _fmemset

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 200

void mode_set(unsigned char mode)
{
union REGS regs;
struct SREGS sregs;

regs.h.al = mode;
regs.h.ah = 0;

int86x(0x10, &regs, &regs, &sregs);
}

void pixel_set(unsigned char far* buffer, int x, int y)
{
// Calculate the offset of the byte in which we need to store the pixel.
int offset = (y * (SCREEN_WIDTH / 8)) + (x >> 3);

// Add the pixel byte offset to the back buffer address (offset within the buffer).
buffer += offset;

// Read the buffer byte and shift the pixel bit into the right position and OR together
*buffer |= (0x80 >> (x & 7));
}

void back_buffer_clear(unsigned char far* buffer)
{
_fmemset(buffer, 0, sizeof(char) * ((SCREEN_WIDTH / 8) * SCREEN_HEIGHT));
}

void back_buffer_copy(unsigned char far* buffer)
{
int y;

// Have two pointers, one points to the start of the even scan lines
// the other points to the start of the odd scan lines.
unsigned char far* video_even = (unsigned char far*)0xb8000000L;
unsigned char far* video_odd = (unsigned char far*)0xb8002000L;

// Copy the buffer to video memory by alternating each scanline. First
// copy a scan line to even video memory, then the next to odd video memory.
for (y = 0; y < SCREEN_HEIGHT / 2; y++) {
_fmemcpy(video_even, buffer, SCREEN_WIDTH / 8);

video_even += SCREEN_WIDTH / 8;
buffer += SCREEN_WIDTH / 8;

_fmemcpy(video_odd, buffer, SCREEN_WIDTH / 8);

video_odd += SCREEN_WIDTH / 8;
buffer += SCREEN_WIDTH / 8;
}
}

Show last 59 lines
// NOTE: Only draws lines from top-left to bottom-right!
void line(unsigned char far* buffer, int src_x, int src_y, int tgt_x, int tgt_y)
{
int diff_x = (tgt_x - src_x);
int diff_y = (tgt_y - src_y);
int err = 0;
int x = src_x;
int y = src_y;

while (x < tgt_x) {
pixel_set(buffer, x, y);

err = err + diff_y;

if (err >= diff_x) {
y++;
err = err - diff_x;
}

x++;
}
}

void main()
{
// Allocate the back buffer
unsigned char far* buffer = _fmalloc(sizeof(char) * ((SCREEN_WIDTH / 8) * SCREEN_HEIGHT));

// Set graphics mode 640 x 200 (1 bpp)
mode_set(6);

// Clear the back buffer.
back_buffer_clear(buffer);

// Draw line from top-left to bottom-right.
line(buffer, 0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);

// Copy back buffer to the screen
back_buffer_copy(buffer);

// Loop indefinitely until ESC
while(1) {
if (kbhit()) {
int key = getch();

// 27 = ESC key
if (key == 27) {
break;
}
}
}

// Set text mode
mode_set(3);

// Release back buffer
_ffree(buffer);
}

To compile use the same command I showed earlier.

Good luck!

PS: Source of the line drawing algo I used: https://www.it.uu.se/edu/course/homepage/graf … gon/x_lines.htm

Reply 13 of 18, by Ringding

User metadata
Rank Member
Rank
Member

I remember always being confused about whether the least significant bit is the left-most pixel of the 8-pixel run or the right-most one. Apparently it’s the latter…

Reply 14 of 18, by radiance32

User metadata
Rank Member
Rank
Member

Wow thanks for all this work 😀
It's truly appreciated 😀

I'm gonna have a coffee, wake up and then have a try at blendering your code into my 3D rasterizer 😀
I'll report on the results here...

Thanks again!,
Terrence

pan069 wrote on 2024-05-04, 08:11:
Hi. […]
Show full quote

Hi.

Sorry, I couldn't get your line code to work so I made a new version of my previous code example. This version uses an off-screen memory buffer, draws a line (just a crappy implementation just for demo purposes), and then copies the buffer to the screen.

I made the off-screen buffer a linear buffer, this makes it much easier to work with in terms of plotting pixels etc. Only when the off-screen buffer is copied to the screen it takes the interleaved nature of the CGA video memory into account and copies an even scanline, then an odd scanline etc. until all scanlines in the off-screen buffer have been copied.

I hope this demonstrates that the pixel plotting works correctly and the copying of the off-screen buffer.

#include <i86.h>     // int86x
#include <conio.h> // kbhit, getch
#include <malloc.h> // _fmalloc, _ffree
#include <string.h> // _fmemcpy, _fmemset

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 200

void mode_set(unsigned char mode)
{
union REGS regs;
struct SREGS sregs;

regs.h.al = mode;
regs.h.ah = 0;

int86x(0x10, &regs, &regs, &sregs);
}

void pixel_set(unsigned char far* buffer, int x, int y)
{
// Calculate the offset of the byte in which we need to store the pixel.
int offset = (y * (SCREEN_WIDTH / 8)) + (x >> 3);

// Add the pixel byte offset to the back buffer address (offset within the buffer).
buffer += offset;

// Read the buffer byte and shift the pixel bit into the right position and OR together
*buffer |= (0x80 >> (x & 7));
}

void back_buffer_clear(unsigned char far* buffer)
{
_fmemset(buffer, 0, sizeof(char) * ((SCREEN_WIDTH / 8) * SCREEN_HEIGHT));
}

void back_buffer_copy(unsigned char far* buffer)
{
int y;

// Have two pointers, one points to the start of the even scan lines
// the other points to the start of the odd scan lines.
unsigned char far* video_even = (unsigned char far*)0xb8000000L;
unsigned char far* video_odd = (unsigned char far*)0xb8002000L;

// Copy the buffer to video memory by alternating each scanline. First
// copy a scan line to even video memory, then the next to odd video memory.
for (y = 0; y < SCREEN_HEIGHT / 2; y++) {
_fmemcpy(video_even, buffer, SCREEN_WIDTH / 8);

video_even += SCREEN_WIDTH / 8;
buffer += SCREEN_WIDTH / 8;

_fmemcpy(video_odd, buffer, SCREEN_WIDTH / 8);

video_odd += SCREEN_WIDTH / 8;
buffer += SCREEN_WIDTH / 8;
}
}

Show last 59 lines
// NOTE: Only draws lines from top-left to bottom-right!
void line(unsigned char far* buffer, int src_x, int src_y, int tgt_x, int tgt_y)
{
int diff_x = (tgt_x - src_x);
int diff_y = (tgt_y - src_y);
int err = 0;
int x = src_x;
int y = src_y;

while (x < tgt_x) {
pixel_set(buffer, x, y);

err = err + diff_y;

if (err >= diff_x) {
y++;
err = err - diff_x;
}

x++;
}
}

void main()
{
// Allocate the back buffer
unsigned char far* buffer = _fmalloc(sizeof(char) * ((SCREEN_WIDTH / 8) * SCREEN_HEIGHT));

// Set graphics mode 640 x 200 (1 bpp)
mode_set(6);

// Clear the back buffer.
back_buffer_clear(buffer);

// Draw line from top-left to bottom-right.
line(buffer, 0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);

// Copy back buffer to the screen
back_buffer_copy(buffer);

// Loop indefinitely until ESC
while(1) {
if (kbhit()) {
int key = getch();

// 27 = ESC key
if (key == 27) {
break;
}
}
}

// Set text mode
mode_set(3);

// Release back buffer
_ffree(buffer);
}

To compile use the same command I showed earlier.

Good luck!

PS: Source of the line drawing algo I used: https://www.it.uu.se/edu/course/homepage/graf … gon/x_lines.htm

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 15 of 18, by radiance32

User metadata
Rank Member
Rank
Member

Great, It works %100 now 😀

See the attached image of suzanne (the blender monkey primitive) 😀
It's got approx 450 polygons and I'm getting about 3 FPS on a 80186 @ 16MHz (My 200LX palmtop with doublespeed crystal installed)

I've still many things to optimize, such as transforming every float uses in the render loop to fixed point integers,
and I can also shave off one or two matrix transforms by combining them.

When I'm finished and I've got my C/C++ prototype fully optimized,
then I'm gonna tackle further optimization by reprogramming render loop operations into 8086 assembly code,
but that's still far away at the moment...

Thanks again for your help,
I've always programmed 3D graphics stuff for modern x64 and Nvidia CUDA targets in Visual Studio or using GCC and Clang on linux/MacOSX,
this is my first time developing a C/C++ app for 16bit DOS. I'm learning about memory models, far and huge memory addressing etc...

Big thanks! 😀
Terrence

pan069 wrote on 2024-05-04, 08:11:
Hi. […]
Show full quote

Hi.

Sorry, I couldn't get your line code to work so I made a new version of my previous code example. This version uses an off-screen memory buffer, draws a line (just a crappy implementation just for demo purposes), and then copies the buffer to the screen.

I made the off-screen buffer a linear buffer, this makes it much easier to work with in terms of plotting pixels etc. Only when the off-screen buffer is copied to the screen it takes the interleaved nature of the CGA video memory into account and copies an even scanline, then an odd scanline etc. until all scanlines in the off-screen buffer have been copied.

I hope this demonstrates that the pixel plotting works correctly and the copying of the off-screen buffer.

#include <i86.h>     // int86x
#include <conio.h> // kbhit, getch
#include <malloc.h> // _fmalloc, _ffree
#include <string.h> // _fmemcpy, _fmemset

#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 200

void mode_set(unsigned char mode)
{
union REGS regs;
struct SREGS sregs;

regs.h.al = mode;
regs.h.ah = 0;

int86x(0x10, &regs, &regs, &sregs);
}

void pixel_set(unsigned char far* buffer, int x, int y)
{
// Calculate the offset of the byte in which we need to store the pixel.
int offset = (y * (SCREEN_WIDTH / 8)) + (x >> 3);

// Add the pixel byte offset to the back buffer address (offset within the buffer).
buffer += offset;

// Read the buffer byte and shift the pixel bit into the right position and OR together
*buffer |= (0x80 >> (x & 7));
}

void back_buffer_clear(unsigned char far* buffer)
{
_fmemset(buffer, 0, sizeof(char) * ((SCREEN_WIDTH / 8) * SCREEN_HEIGHT));
}

void back_buffer_copy(unsigned char far* buffer)
{
int y;

// Have two pointers, one points to the start of the even scan lines
// the other points to the start of the odd scan lines.
unsigned char far* video_even = (unsigned char far*)0xb8000000L;
unsigned char far* video_odd = (unsigned char far*)0xb8002000L;

// Copy the buffer to video memory by alternating each scanline. First
// copy a scan line to even video memory, then the next to odd video memory.
for (y = 0; y < SCREEN_HEIGHT / 2; y++) {
_fmemcpy(video_even, buffer, SCREEN_WIDTH / 8);

video_even += SCREEN_WIDTH / 8;
buffer += SCREEN_WIDTH / 8;

_fmemcpy(video_odd, buffer, SCREEN_WIDTH / 8);

video_odd += SCREEN_WIDTH / 8;
buffer += SCREEN_WIDTH / 8;
}
}

Show last 59 lines
// NOTE: Only draws lines from top-left to bottom-right!
void line(unsigned char far* buffer, int src_x, int src_y, int tgt_x, int tgt_y)
{
int diff_x = (tgt_x - src_x);
int diff_y = (tgt_y - src_y);
int err = 0;
int x = src_x;
int y = src_y;

while (x < tgt_x) {
pixel_set(buffer, x, y);

err = err + diff_y;

if (err >= diff_x) {
y++;
err = err - diff_x;
}

x++;
}
}

void main()
{
// Allocate the back buffer
unsigned char far* buffer = _fmalloc(sizeof(char) * ((SCREEN_WIDTH / 8) * SCREEN_HEIGHT));

// Set graphics mode 640 x 200 (1 bpp)
mode_set(6);

// Clear the back buffer.
back_buffer_clear(buffer);

// Draw line from top-left to bottom-right.
line(buffer, 0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);

// Copy back buffer to the screen
back_buffer_copy(buffer);

// Loop indefinitely until ESC
while(1) {
if (kbhit()) {
int key = getch();

// 27 = ESC key
if (key == 27) {
break;
}
}
}

// Set text mode
mode_set(3);

// Release back buffer
_ffree(buffer);
}

To compile use the same command I showed earlier.

Good luck!

PS: Source of the line drawing algo I used: https://www.it.uu.se/edu/course/homepage/graf … gon/x_lines.htm

Attachments

  • ff2.png
    Filename
    ff2.png
    File size
    16.84 KiB
    Views
    238 views
    File license
    Fair use/fair dealing exception

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ

Reply 17 of 18, by radiance32

User metadata
Rank Member
Rank
Member
pan069 wrote on 2024-05-04, 23:29:
radiance32 wrote on 2024-05-04, 23:07:

Great, It works %100 now 😀

Fantastic! Glad it works. The screen shot looks great. Good luck with your project!

Thanks again 😀

Another question,
I'm developing this for my 200LX, which has a very wide screen,
so the 640x200 mono CGA mode has square pixels on it's LCD,
but, on a PC monitor, this mode is displayed streched out vertically,
eg one pixel is two pixels vertically.

Since I''m using DOSBOX to test everything while I develop,
is it possible to configure DOSBOX to use an alternative aspect ratio,
so I can run my CGA HIRES app in DOSBOX with square pixels on a wide screen ?

I've searched through the DOSBOX configuration options and can't find anything...

Cheers,
Terrence

Check out my new HP 100/200LX Palmtop YouTube Channel! https://www.youtube.com/channel/UCCVChzZ62a-c4MdJWyRwdCQ