Skip to content

Conversation

@amoibos
Copy link
Contributor

@amoibos amoibos commented May 24, 2025

Tested with Meka, Emulicious and SC-3000H, looking good for me.

@sverx
Copy link
Owner

sverx commented May 24, 2025

Interesting!
Would you mind explaining me how this 'bitmap mode' works in mode 2?
Also, don't you need to arrange the tilemap in a specific way for this to work?

@amoibos
Copy link
Contributor Author

amoibos commented May 24, 2025

This implementation requires that the tiles are sequentical ordered, row by row. The function consist of two parts, reading from vdp the byte which contains the pixel pattern(of tile line) and reading the byte for the color information(2 nibbles fg, bg ). A sample is attached, using the bresenham for drawing a line.
Performance was not goal, yet. There is no caching, maybe waits and setting address could be optimized. If the area is pre-colored, you only need to update the pattern which save some cycles thats why two functions and one macro exist.

assets.zip

@sverx
Copy link
Owner

sverx commented May 25, 2025

I see, SG_setPixel sets only a single bit of information, that is if the pixel at x,y is using either the foreground or the background color, and SG_setPixelColor sets which color is the foreground, or the background... but I honestly would expect a setPixel function to work with three parameters: the x and y screen coordinates and the color for the pixel... wouldn't it be possible to do that instead?
Also a function to prepare the screen for bitmap mode is needed.

@amoibos
Copy link
Contributor Author

amoibos commented May 25, 2025

Ok, the naming isn't great, I can rename the putPixel macro as setPixel with fixed foreground flag but how should I name the two functions?
What do you mean with preparing, set the tile order or clearing color and pattern information?

@sverx
Copy link
Owner

sverx commented May 26, 2025

No, sorry, I wasn't complaining about the naming at all.
Instead, what I meant is that I would expect a function that draws a pixel on screen to want just three parameters: x, y, color so I would for instance expect something like:
SG_setPixel (unsigned char x_coord, unsigned char y_coord, unsigned char color)
even when the operation of putting a pixel is indeed split into two separate operations, one that affects the byte addressed by the x and y coordinates and the other that affects the color of the 8 pixels 'around' the one we're drawing.

Also, I have no idea what's best here, if to have a single function that prepares the tile order in the PNT and sets the background color such as
SG_initBMPmode (unsigned char background_color)
and then when the color in the SG_setPixel call is equal to the background color it means you want to reset the bit there and when it's not equal then the bit needs to be set and you also have to ensure that the foreground color information is updated with the requested color (of course this might cause color clashing but that's expected...)

Of course this means sharing the same background color for the whole screen - unless a separate function is provided to update the background color for a specific rectangular area of the screen.

In short, I don't know what's the best approach, but I'd like something that it's simple to use.

@sverx
Copy link
Owner

sverx commented May 28, 2025

I'm still thinking about this.

Maybe a solution could be, after having initialized the bitmap to an empty (all zeroes) starting point with a background color of choice, have a single function to draw a pixel that works like this:

  • it accepts three parameters for the pixel: x, y, color
  • it reads from VRAM the color info relative to the x,y position
  • if the background color info matches with the pixel color, we want to 'remove' a pixel, so we will proceed to reset the corresponding bit in the tile data but we won't update the color info
  • if the background color info doesn't match with the pixel color, we want to 'put' a pixel, so we will update the color info with the new foreground color and of course we will proceed to set the corresponding bit in the tile data

How does it sound? I think this would work very well for 99% of the cases, and we would just need a function to update the color info in case the user wants to change the background color for a specific location...

@sverx
Copy link
Owner

sverx commented Jun 4, 2025

I'm still elaborating in my head about this, and I think we could even have an interesting twist on the above approach if:

  • when we initialize the bitmap we also specify a foreground color, in addition to the background color
  • the function to draw a pixel works basically as explained above with the difference that if the foreground color for the pixel we want to draw is already the desired one, we don't even need to update the color info.

This would result in a faster operation when drawing using the single color we specified while initializing, but also when successive calls want to set the same foreground color that's already set, because we won't need to pointlessly update another byte in VRAM.

You opinion on this? 😃

@amoibos
Copy link
Contributor Author

amoibos commented Jun 7, 2025

Sorry for my delayed answer, too much holidays in Germany now and lately:-)
Indeed, there are many points to consider depending on use cases and performance which I didn't have in mind yet.
For me the goal was to create a solution for a mixed mode, graphics on upper screen, text on the bottom.
Graphics created pixel by pixel on tile data, letters by a change of the tilemap.
If I understood you correctly, then how do we get more than two colors on screen?
We could also interprete fg color 0 as do not update color information.

@sverx
Copy link
Owner

sverx commented Jun 8, 2025

If I understood you correctly, then how do we get more than two colors on screen?

When the third parameter in the setPixel function doesn't match with either the current background or the current foreground color information for the pixel, we have to set a new foreground color information for it, using the requested color value.
This will cause the famous side effect of changing the color of all the other foreground pixels that are in the same 8×1 pixels block, but that's expected.

We could also interprete fg color 0 as do not update color information.

Isn't that the transparent color? I think this should be a valid color both for the background and for the foreground, unless it's pointless (can sprites be set behind the background image in Mode 2?) 🤔

@sverx
Copy link
Owner

sverx commented Jun 8, 2025

Actually your previous message just give me an additional idea: what if the third parameter could be either an actual color (0-15) or be a pseudo-color, that is some way to tell the function that we don't want to mess with the color information at all?

What I mean is: if you want to draw a pixel of color 4 you do something like
SG_setPixel (100, 80, 4);
and that 4 will be checked against the current foreground and background information for that pixel, triggering either a reset of a bit and no color information update or it would set a bit and would also possibly update the color information... but there might be also these two:
SG_setPixel (100, 80, SG_BITMAP_BACKGROUND_COLOR);
SG_setPixel (100, 80, SG_BITMAP_FOREGROUND_COLOR);
which would respectively reset and set a bit with no interaction at all with the color information for that pixel.

Does this sound like something that could be useful? I mean, once you initialized the bitmap mode you could in theory just use that as a 1bpp bitmap using just these two above and that would be faster...

@amoibos
Copy link
Contributor Author

amoibos commented Jun 8, 2025

FYI, I just updated the PR where both functions are merged into one, setting pixel and coloring.
0xFF is used for do not update the color information.
Honestly I'm not a fan from implicitly changes based on the color state of the pixel.
Drawing lines and circles will invert the pixel when there are using the same pixel coordinates.
We could use a macro for third argument of setpixel for setting background or foreground information, but I'm not sure how useful this is in reality.
This wouldn't introduce too much complexity for the programmer but in the end it is better do in the kiss way.
I haven't benchmarked it how faster it is by just updating the pattern.

@sverx
Copy link
Owner

sverx commented Jun 8, 2025

Drawing lines and circles will invert the pixel when there are using the same pixel coordinates.

I'm not sure I get what you mean here 🤔

We could use a macro for third argument of setpixel for setting background or foreground information, but I'm not sure how useful this is in reality.

We could leave this feature out for now, possibly improving later if needed.

in the end it is better do in the kiss way

Yes, that's why I insisted in having a single function to plot a pixel of any color, regardless of what is the existing color information for that area.

@sverx
Copy link
Owner

sverx commented Jun 9, 2025

I went through your latest commits and it looks like it's not working as expected.

For instance, if you initialize the bitmap with green as foreground color and black as a background color, then drawing a black pixel will change the foreground color of that area while it should instead use the background color.

The NO_COLOR_UPDATE mode seems to be instead pretty similar to the idea of putting a pixel using the foreground color information already present, but then there's no way to use the background color information already present, so it feels incomplete.

I suspect it's my fault not explain well enough how this should work - my point was to avoid useless changing of color information either explicitly (using two constants such as EXISTING_BACKGROUND_COLOR and EXISTING_FOREGROUND_COLOR or similar names) or implicitly, when the specified color for the pixel is already either the background or the foreground color for that 8×1 pixels block.

Let me know if you thought of a simpler approach, in the user point of view.

Add unsetting pattern pixel
Improve drawing performance by reducing WAIT's
@amoibos
Copy link
Contributor Author

amoibos commented Jun 9, 2025

With the latest commit it should be fixed.
The function expect the following arguments:

void SG_setPixel (unsigned char x, unsigned char y, unsigned char color)

The third argument color expect values from 0-15 &16 for foreground, to suppress color update NO_COLOR_UPDATE is(16) used.
By using the macro SG_BgColor the value gets an offset by 128, this will manipulate the background bits.
Technically the third argument mixes mode and color value, not nice but convenient.

@sverx
Copy link
Owner

sverx commented Jun 9, 2025

Sorry, this is not going towards the way it should work, especially the setPixel function should never affect the BMP background color.

Let me try to be clearer with some pseudo-code.

--- setPixel (x, y, pixel_color):
- if pixel_color equals EXISTING_FOREGROUND_COLOR goto [F]
- if pixel_color equals EXISTING_BACKGROUND_COLOR goto [B]
- retrieve color info byte from color memory x,y
- if pixel_color is same as foreground color info goto [F]
- if pixel_color is same as background color info goto [B]
- set foreground color in color info byte to pixel_color
- update color info byte at color memory x,y
[F] set bit in tile memory at x,y
- return
[B] reset bit in tile memory at x,y
- return

@sverx
Copy link
Owner

sverx commented Jun 17, 2025

Let's make this simpler, let's leave the EXISTING_FOREGROUND_COLOR / EXISTING_BACKGROUND_COLOR parts out for now.

Something like this would work just fine:

--- setPixel (x, y, pixel_color):
- retrieve color info byte from color memory x,y
- if pixel_color is same as background color info goto [B]
- if pixel_color is same as foreground color info goto [F]
- set foreground color in color info byte to pixel_color
- update color info byte at color memory x,y
[F] set bit in tile memory at x,y
- return
[B] reset bit in tile memory at x,y
- return

@sverx
Copy link
Owner

sverx commented Aug 9, 2025

Back to this!

I tried to rework your SG_setPixel, and this is what I got (note: untested code!)

So this works as one would expect: the pixel on screen at (x,y) will get the color specified in the function call (color is 0 to 15 as expected) but the function will skip updating the color memory if it's possible, which is when the desired color happen to be already the current FG color or the current BG color of that block.

How does that sound to you? I'm looking forward to your opinion 😃

We can talk other improvements later.

void SG_setPixel (unsigned char x, unsigned char y, unsigned char color) {
  unsigned int address=SG_get_Tile_address(x, y);

  // check if color data update is needed
  SG_set_address_VRAM_read (CGTADDRESS + address);
  WAIT_VRAM; 
  unsigned char color_data = VDPDataPort;
  if ((color_data & 0x0F) == color) {
    color=0;   // we want a pixel in the current background color
  } else if ((color_data >> 4) == color) {
    color=1;   // we want a pixel in the current foreground color
  } else {
    color_data = (color_data & 0x0F) | (color << 4);
    SG_set_address_VRAM (CGTADDRESS + address); 
    VDPDataPort = color_data;
    color=1;    // we want a pixel in the new foreground color
  }

  unsigned char pattern_data=(0x80 >> (x & 0x07));
  // set pattern data
  SG_set_address_VRAM_read (PGTADDRESS + address);
  WAIT_VRAM; 
  if (color) {
    pattern_data |= VDPDataPort;
  } else {
    pattern_data = VDPDataPort & (~pattern_data);
  }
  SG_set_address_VRAM (PGTADDRESS + address);
  VDPDataPort = pattern_data;
}

@sverx
Copy link
Owner

sverx commented Sep 2, 2025

I wanted to see if the code above was actually really working, so I made a quick test using code from your example.

It works, I just had to rename that SG_putPixel, and make sure your code was calling that with the expected parameters (x, y, color).

screenshot

Here's the source and the compiled ROM if you want to see how it is: test.zip

Emulicious' profiler says it takes from 899 to 1214 cycles at each call, which doesn't seem so bad.
Still, once this works, I'd love to spend some time and rewrite it in Z80 asm for performance 😄

Let me know what you think about this solution. Cheers!

@sverx
Copy link
Owner

sverx commented Oct 7, 2025

I just pushed an update to SGlib adding support for a bitmap mode, and I have rewritten the C function to put a pixel in assembly so that it's way faster now.

To initialize the bitmap mode you use
SG_initBitmapMode (unsigned char foreground_color, unsigned char background_color)
which will also paint the whole screen in background color.
The foreground color value could be even not important, but there's a performance gain here if you're going to draw pixels using this color, as the attributes are already set. (No, there's not [yet?] a way to draw a pixel using the current foreground or background color).

After the initialization you can draw pixels in any color using
SG_putPixel(x,y,color)

I hope you find this useful 😃

If you want to provide code for an example that shows how to draw something, that is really appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants