Thursday 14 July 2016

Part 17: Rendering the splash screen

Foreword

In the previous blog we managed to emulate the flashing borders of the loader. In this blog we will emulate the splash screen.

The screenshot of the splash screen might be copyrighted. I am not sure. If contacted by the publisher of the Dan Dare I will remove the screenshot by request.


Theory on the VIC-II graphic Modes

Before we figure out how to emulate rendering the splash screen, lets cover a bit of theory on the graphic modes of the VIC II.

Let us first zoom into what defines the graphic modes in the VIC II. There is two important bits:
  • Bit 5 of register D011: Switch bitmap mode on/off
  • Bit 4 of register D016: Switch multi-color mode on/off
Another important location for the graphic modes is location D018. This location tells the VIC-II where in RAM to retrieve its display information from. The bits on this register are as follows:
  • Bit#0: not used
  • Bit#1-#3: Address Bits 11-13 of the Character Set (*2048)
  • Bit#4-#7: Address Bits 10-13 of the Screen RAM (*1024) 
In bitmap mode, the character address is used to indicate where in memory the bitmap is stored.

You might notice that Most Significant bit (MSB) of these two base addresses is bit number 13 and not bit number 15. So, does this mean that the VIC can only acess the first 16KB of memory? Luckily not! Bits 14 and 15 is supplied by register DD00 (a register of CIA 2).

The following image from http://dustlayer.com/vic-ii/2013/4/22/when-visibility-matters explains very nicely how tese two bits work:

Notice the inverse bit order.

Emulating the Splash Screen

To emulate the splash screen we need to find out what is the graphics mode while the splash screen is displaying.

So, lets kick off the loading process and wait for the following screen to appear:


Add this point click the stop button. We now need to inspect the contents of location D011 and D016 to determine the graphics mode:

  • Location D011 has value 3B. Bit 5 is switched on, meaning bitmap mode is enabled
  • Location D016 has value D8. Bit 4 is switched on, meaning multi color mode is enabled.
So, the splash screen ses multi color bitmap mode. In multi color two bits used to represent a pixel. Each pixel is therefore twice as wide as an ordinary pixel, effectively cutting the horizontal resolution  into halve. However, each pixel you can represent four possible colors.

The four colors used in text-multicolor mode differs a bit from the four colours used in bitmap multi-colour mode. For now, we will only worry about bitmap multi-colour mode.

The following table, again taken from the dustlayer website, explain the four colors of multi-color bitmap mode:


Next, lets determine the bitmap/ screen RAM base addresses. First lets get hold of bit 14 & 15 via location DD00. The value we get via inspection is 4. The lowest two bits are zero. Via the lookup table from the previous section we determine that the VIC II operate in the bank C000-FFFF.

Lets try to put everything in code.The magic will happen with the method drawCharline:

  function drawCharline() {
    var bitmapMode = ((localMem.readMem(0xd011) & 0x20) != 0) ? 1 : 0;
    var multicolorMode = ((localMem.readMem(0xd016) & 0x10) != 0) ? 1 : 0;
    var screenMode = (multicolorMode << 1) | bitmapMode;
    switch (screenMode) {
      //text mode, normal
      case 0:
        drawTextModeNormal(charPosInMem + cycleInLine - 5);
      break;

      //bitmap mode, normal
      case 1:
      break;
      
      //text mode, multi color
      case 2:
      break;

      //bitmap mode, multi color
      case 3:
        drawBitmapModeMultiColor(charPosInMem + cycleInLine - 5);
      break;
    }
  } 

We start by throwing the bitmap-bit and the multi-color bit together to form a number. We then use a switch statement to serve the applicable graphics mode. For now, only Textmode-normal, and bitmap-multicolor is defined.

Text mode normal looks as follows:

  function drawTextModeNormal(charPos) {
    var screenCode = localMem.readMem(1024 + charPos);
    var currentLine = localMem.readCharRom((screenCode << 3) + ((cycleline - 42) & 7));
    var textColor = localMem.readMem(0xd800 + charPos) & 0xf;
    var backgroundColor = localMem.readMem(0xd021) & 0xf;
    var currentCol = 0;
    for (currentCol = 0; currentCol < 8; currentCol++) {
      var pixelSet = (currentLine & 0x80) == 0x80;
      if (pixelSet) {
        imgData.data[posInCanvas + 0] = colors[textColor][0];
        imgData.data[posInCanvas + 1] = colors[textColor][1];
        imgData.data[posInCanvas + 2] = colors[textColor][2];
        imgData.data[posInCanvas + 3] = 255;
      } else {
        imgData.data[posInCanvas + 0] = colors[backgroundColor][0];
        imgData.data[posInCanvas + 1] = colors[backgroundColor][1];
        imgData.data[posInCanvas + 2] = colors[backgroundColor][2];
        imgData.data[posInCanvas + 3] = 255;
      }
      currentLine = currentLine << 1;
      posInCanvas = posInCanvas + 4;
   }

  }


Pretty straightforward!

Next look at the bitmap-multicolor mode:

  function drawBitmapModeMultiColor(charPos) {
    var currentLine = localMem.readMem(0xe000+(charPos << 3) + ((cycleline - 42) & 7));
    var textColor = localMem.readMem(0xd800 + charPos);
    var backgroundColor = localMem.readMem(0xd021);
    var color1 = (localMem.readMem(49152 + charPos) & 0xf0) >> 4;
    var color2 = localMem.readMem(49152 + charPos) & 0xf;
    var color3 = localMem.readMem(0xd800 + charPos) & 0xf;
    var colorArray = [backgroundColor, color1, color2, color3];
    var pixPair = 0;
    for (pixPair = 0; pixPair < 4; pixPair++) {
      var colorValue = (currentLine >> 6) & 3;
      imgData.data[posInCanvas + 0] = colors[colorArray[colorValue]][0];
      imgData.data[posInCanvas + 1] = colors[colorArray[colorValue]][1];
      imgData.data[posInCanvas + 2] = colors[colorArray[colorValue]][2];
      imgData.data[posInCanvas + 3] = 255;
      imgData.data[posInCanvas + 4] = colors[colorArray[colorValue]][0];
      imgData.data[posInCanvas + 5] = colors[colorArray[colorValue]][1];
      imgData.data[posInCanvas + 6] = colors[colorArray[colorValue]][2];
      imgData.data[posInCanvas + 7] = 255;

      currentLine = currentLine << 2;
      posInCanvas = posInCanvas + 8;
   }

  }

A couple of things happening:
  • A 4 colour tablet is created for the pixels to be drawn
  • 2 pixels is shifted at a time. The resulting pixel color is drawn twice
  • You will also notice that we are adding 49152 to the address when we are reading from memory. This is a bit of hardcoding just to get the splash screen displayed
This time, when we load the game, the splash screen gets displayed:

So, lets wait and see how far our emulator can get with loading the game. After about five minutes the game loading gets stuck with the following screen:

What went wrong? In the next section we will do some investigations...

Investigating why game loading got stuck

Time for some debugging again. We will start by waiting for the moment when the splash screen has just disappeared then we will stop our emulator.

At this point we need to add a breakpoint at a suitable place to avoid endless stepping. A typical place would be where the bit read gets shifted into a temporary memory location. Here is the piece of assembly where it is happening:

0363   AD 0D DC   LDA $DC0D
0366   8E 0F DC   STX $DC0F
0369   4A         LSR A
036A   4A         LSR A
036B   26 C0      ROL $C0
036D   A5 C0      LDA $C0
036F   88         DEY
0370   F0 00      BEQ $0372

We will place a breakpoint at location 36B.

The Y register plays a crucial role in this piece of code. Since, at this point in time we are reading bytes, the Y register decrements from 8 each time till it reaches 0. So, we RUN a couple of times till Y has the value of one.When Y is one we slowly step through...

The interesting line is as follows:

03ba STA ($f9),Y

From this we see that F9/FA stores the current address where the next byte read should be written to.

If you read further down the assembly code, you will also discover that the end address is stored at FB/FC for the current block:

03BA   91 F9      STA ($F9),Y
03BC   A0 05      LDY #$05
03BE   84 01      STY $01
03C0   45 AF      EOR $AF
03C2   85 AF      STA $AF
03C4   E6 F9      INC $F9
03C6   D0 05      BNE $03CD
03C8   EE 20 D0   INC $D020
03CB   E6 FA      INC $FA
03CD   A5 F9      LDA $F9
03CF   C5 FB      CMP $FB
03D1   D0 0B      BNE $03DE
03D3   A5 FA      LDA $FA
03D5   C5 FC      CMP $FC
03D7   D0 05      BNE $03DE


So lets remove the breakpoint and let it run for intervals of about two seconds or so.

At one point you will notice that the game loader starts to load information in the peripheral region (D000-E000). This sounds dangerous. If you, however, look at the assembly instruction at location 3B8, we will realise that the peripheral region is disabled when the write happens:

03B8   84 01      STY $01

At this point a zero gets written to location 1. When you write a zero to this location, RAM will be visible at all three areas (e.g. BASIC ROM, KERNEL ROM and IO region).

Our emulator, however, doesn't check for this. To cater for this scenario, we need to make the following adjustment to our memory class:

  function IOEnabled() {
    var temp = mainMem[1] & 3;
    var temp2 = mainMem[1] & 4;
    return (temp2 != 0) & (temp != 0);
  }


  this.readMem = function (address) {
    if ((address >= 0xa000) & (address <=0xbfff))
      return basicRom[address & 0x1fff];
    else if ((address >= 0xe000) & (address <=0xffff) & kernelEnabled())
      return kernalRom[address & 0x1fff];
    else if ((address >= 0xdc00) & (address <= 0xdcff) & IOEnabled()) {
      return ciaRead(address);
    } else if (address == 1) {
      var temp = mainMem[address] & 239;
      if (!playPressed)
        temp = temp | 16;
      return temp;
    }
    return mainMem[address];
  }

  this.writeMem = function (address, byteval) {
    if ((address >= 0xdc00) & (address <= 0xdcff) & IOEnabled()) {
      ciaWrite(address, byteval);
      return;
    } else if (address == 1) {
      var temp = byteval & 32;
      temp = temp >> 5;
      tape.setMotorOn(temp);
    }      
    mainMem[address] = byteval;
  }

Lets restart our emulator with these changes. This time we past the stuck screen:

You can sort of identify the intro screen between the garbled characters. We will fix the intro screen in the next blog.

In Summary

In this blog we managed to emulate the spalsh screen. In the next blog I will fix up the Intro screen.

Till next time!

No comments:

Post a Comment