Sunday, July 14, 2013

First screenshot

Yes, you are not dreaming! It's true, it's fresh, it's happening RIGHT NOW! Ladies and gentlemen, I proudly present you the very first screenshot of Avalot d' Argent in the holy embrace of SvummVM!
Okay, the colors really need some adjustments, but apart from that you can see that the background image is successfully loaded and shown accurately. (Of course here will come the GUI in the top and the bottom, but that's not my first priority at the moment.)

Lucerna
Let me tell you a little story about my fight with the bits! Everything started after my latest post: I began to mess around with graphics, and I had some mailing with dreammaster, who helped me a lot and gave me an awesome "Graphics in ScummVM for dummies" guide. By following that, I successfully set up the basics of the screen handling in my engine, what you can find here and here. Graph's initial purpose was to replace the Graph unit from Pascal with a C++ class, so it can work in synergy with Lucerna (the class/unit which is responsible for the screen, keyboard and mouse handling) but as time passes I am slowly getting convinced that it will eventually become a replacement for Lucerna. I don't see it clear yet, time will tell...
At the moment Graph is not much more than a wrapper around a Graphics::Surface object.
I got the resolution of the game (what you can find in the header file of Graph) from this function call in the original code: 
 gd:=3; gm:=0; initgraph(gd,gm,'');
Where 3 stands for EGA graphic driver and 0 means EGALo mode, which is equal to 640x200 screen resolution, 16 colors and 4 screen pages. (I'll bother with these "pages" later.)
I also had some difficulties setting up the color palette to mimic the EGA display, but dreammaster told me a lot about it, and by "borrowing" a little bit of code from Strangerke's Mortvielle engine, I get over with it quite fast too. As you may see it in the screenshot at the beginning of the post, there is still a couple of things to do with it, since the colors in the original game are much more livid, but I'll leave it's fix for later.

The next big deal came right after that. I had a long conversation with fuzzie (who is "substituting" Strangerke, my mentor, while he is on holiday) about the topic and she told me a great deal about EGA displays and how did they handle data.
The function which is responsible for loading the data of each screen is Lucerna::load(). The tricky part was here to notice the loop in the original code (which is fuzzie's merit), and recognize that we have to face the "four plane style". After that, recreating it in C++ was quite easy, and fuzzie helped me a lot with that as well.
Here's the result of it:

f.seek(177);

/*for (bit = 0; bit <= 3; bit++) {
 port[0x3c4] = 2;
 port[0x3ce] = 4;
 port[0x3c5] = 1 << bit;
 port[0x3cf] = bit;
 blockread(f, a0, 12080);
 move(a0, a1, 12080);
}*/

Graphics::Surface background;
background.create(_vm->_graph._screenWidth, _vm->_graph._screenHeight, Graphics::PixelFormat::createFormatCLUT8());

byte backgroundHeight = 8 * 12080 / _vm->_graph._screenWidth; // With 640 width it's 151
// The 8 = number of bits in a byte, and 12080 comes from the original code (see above)

for (byte plane = 0; plane < 4; plane++)
 for (uint16 y = 0; y < backgroundHeight; y++)
  for (uint16 x = 0; x < _vm->_graph._screenWidth; x += 8) {
   byte pixel = f.readByte();
   for (byte i = 0; i < 8; i++) {
    byte pixelBit = (pixel >> i) & 1;
    *(byte *)background.getBasePtr(x + 7 - i, y) += (pixelBit << plane);
   } 
  }

_vm->_graph.copySurface(background);

background.free();

As you can see I left the automatically converted loop in a comment. Under that, there's my C++ interpretation of it. You can find the idea behind that loop's core on this page. We guessed it's using four planes because the original loop goes from 0 to 3. So my code does the very same. It first reads the blue component of every pixel: since every bit represent a pixel, we jump in the x loop 8 times every time, and read a byte only after every 8th processed pixel. After that, we loop through the given byte, get the blue component of every pixel, and put them in their place. We do that four times, so every plane (blue, green, red, intensity) is read by the end of the big, plane loop, and finally we got the background image!
The interesting part with this is the following: we read an image with the size of 640x151. That's ok, but after playing the game a couple of times using DOSBox, I clearly saw that it shows the background image (and obviously the whole game too) in a much bigger resolution. I found that the window's size the emulator uses is 640x400. It's very strange since the original Pascal code (as I mentioned above) uses half of that height. To mimic DOSBox's output, I put a little supplement into Graph::copySurface() which copies every line of the background 2 times. After that, my graphical output became almost the same with the one DOSBox produced, but I am still not sure if it's the right way of operation and I am still searching for traces of this "stretching" in the original code.

(P.s.: Thomas: if you read this post and have any clue or suggestion about this resolution-problem, I'd be very glad if you could share it with me in a comment or by mail! :) Is the original output of the game really in 640x400, and if it is, where the "trick" is located in the original code which makes it possible?)

4 comments:

  1. One small comment. In your graph code, for copySurface(), it might be better to pass 'Graphics::Surface source' as a pointer. That will save an unnecessary copy constructor.

    ReplyDelete
    Replies
    1. Thanks for your remark! Changing it right now! :)

      Delete
  2. I'm impressed you've got this far this soon. Well done!

    Avalot and Avaricius both run the EGA in its little-used and little-known 16-colour 640×200 mode. Almost nobody else used that, and I can't entirely remember why we used it ourselves; it might have been so that there were more "pages" to flip between. (Avaricius used that for some very ugly hacks.) Because nobody else much used this mode, you're probably better off doubling everything vertically so it looks reasonable in 640×480. Does that help?

    The only other oddity is that if you have VGA, Avalot will use that to redefine colour 13, magenta, as a colour closer to a white flesh tone; it will still run in 16-colour 640×200 though.

    Sorry about not mentioning the EGA colour bank thing; I should have realised you'd run into it.

    ReplyDelete
    Replies
    1. No problem and thank you very much! :)
      I decided that I'll use 640x200 during the development, and if it'll looks better, I'll change to 640x400 at the end if all will agree about that. It's truly almost just one more line of code in Graph::copySurface().
      I also met with the so-called pages, but I think I'll be able to knock them out with Graph.
      Thanks for telling me about the magenta color as well, I'll keep an eye on that! ;)

      Delete