Objective
Do you want to know how to see the images on MSX? Do you need to load a drawing as an initial display of your game? Or maybe a screen of some level?
You can do this by following this post. We will see how we can transform an image generated with another platform using a drawing program and display it on the MSX.
This tutorial will be used to do so through MSX-DOS and using MSX screen modes on screen 5 or 7 (as they are called in the basic, and are usually known, but in MSX-Technical book they call them Graphic 4 and Graphic 6).
How does video work on MSX?
The part responsible for generating images on the MSX is the VDP (Video Display Processor). In the first generation, the TMS9918 with 16K of memory is used; in the MSX2, it switches to the Yamaha V9938 with 128K, offering more colors and vertical scroll; and finally, the MSX2+ and Turbo-R have an improved version, the V9958, which adds horizontal scroll. After the official versions, the V9990 appeared (which some say should have been included in the MSX Turbo-R), offering many more colors, 512K of RAM and multilayer scroll.
Depending on the screen mode being operated, the VDP interprets the data stored in its memory (often referred to as VRAM) as either pixel colors or pattern colors.
In this chapter, we will focus on loading screen 5 or 7, which use VRAM at the pixel level. In these screen modes, 16 different colors can be displayed simultaneously from a palette of 512 possible colors. The data stored in memory represents the color index of each pixel. The following image presents a diagram based on a graphic from the MSX2-Technical Handbook:
The left part corresponds to the video memory (VRAM), and the right part shows its correspondence with the image generated on the screen. Thus, the first byte of the screen contains information about pixels (0,0) and (1,0). To index 16 colors, we only need 4 bits. The high part of the byte represents the leftmost pixel, and the low part represents the rightmost pixel, as indicated by the arrows. In total, we can display 256 * 212 pixels. To paint them, we need 256 * 212 / 2 = 27136 bytes (the diagram shows up to 27135 since it starts counting from 0), as each byte contains two pixels.
The color indices are defined using register #16 and are stored in memory at address 0x7680 to 0x76AF for screen 5, and between positions 0xFA80 and 0xFAAF for screen 7.
Fortunately, Fusion-C has a command to define the palette, which is very useful and simplifies this process. The command is:
void SetPalette((Palette *) mypalette)
where the parameter is an array with all the colors; or we can use the function
void SetColorPalette(char ColorNumber, char Red, char Green, char Blue)
that allows us to modify one color in place of the whole palette.
How do we indicate which file from the disk should be loaded?
In the previous section, we have seen how the video memory works. Now, let's explore how we read bytes from a file that is on the disk. This task is handled by MSX-DOS.
MSX-DOS is an adaptation of CP/M for MSX, and it requires its own ROM that provides the BDOS functions. The MSX system should have at least 64KB of memory.
To read files, MSX-DOS defines the File Control Block (FCB), which is a memory structure containing various fields that MSX-DOS uses to manipulate disk tracks and locate the bytes that make up the designated file. Fusion-C already defines the FCB, and to read files, we only need to specify the filename.
How do we convert an image from a PC to bytes that can be read by the MSX?
There is a multiplatform application called MSX Viewer that allows you to load an image from a PC and convert it into different formats for the MSX, depending on the screen mode you want to use. It also supports the reverse conversion, where you can load an image in MSX format and save it in a modern common format like BMP. Additionally, I personally use a Python script that converts a PC image to either screen 5 or screen 7 format. The script doesn't crop the image; it only converts the colors, so the original image should be in the appropriate resolution. This script leverages the OpenCV library and the K-Means algorithm to reduce the image to 16 colors. You can find this script on the following GitLab repository.
Code for loading an image into VRAM
I'm tired of so much theory. Can we see the code already? Of course, here it is. This code can be found on MoltSXalats' GitLab.
Lines 5-7 contain the includes that we will use in this file. The following lines up to 14 are the variables we will use. When compiling with sdcc (at least up to version 4.1), it is better to use global variables rather than local ones, as it uses fewer lines of code. I was able to verify this with the game "Bricks." The text of the scroll on the initial screen, when I defined the character arrays inside the function for painting the text, the compilation time and resulting file size increased enormously. Then, I removed them and made them global, and everything returned to normal. Feel free to try it out if you have a moment. Well, getting back to the code at hand, we have the variable "file," which is of type FCB and matches the structure of the File Control Block we explained earlier. This will serve to store the file information by the DOS. Next, we define a buffer, which will be filled with the data read from the disk in order to transfer it from RAM to VRAM. We defined it to have a size of 20 lines of screen 5: each line consists of 256 pixels, each pixel has 4 bits, and 8 bits make up one byte; so we have 20 * 256 * 4 / 8 = 2560 bytes (one "char" is equivalent to one byte). I chose this number be it was the one used in the Fusion-C example "LoadScreen5Image.c." However, I did not conduct any comparative evaluation with other quantities to see which one is faster. Next, we have the variable "mypalette", which will be responsible for storing the 16 colors and has the structure required by the Fusion-C function "SetPalette." From lines 16 to 34, we have the function "FT_SetName", which fills the fields of the FCB with the name of the file to be processed. Then, from lines 36 to 57, we have the error handling for DOS with "FT_errorhandler".
And now we finally arrive at the "FT_LoadSc5Image" function, which is responsible for reading from the disk and transferring the data to VRAM. It takes as parameters the name of the file we want to load, the vertical coordinate from which we want to start, the buffer where we will store the data, the line width (which will be 256 for screen 5 or 512 for screen 7), and the size of the previous buffer. We define the variable "rd," which will contain the bytes read from the disk, and initialize it with a non-zero value. We set the file name in the FCB and attempt to access it using the Fusion-C function "fcb_open." If it returns an error, we process this message and end. At line 71, we read the first 7 bytes, but we do not use them for anything since both the binary image obtained with the script and the one from the MSX Viewer contain these bytes that are not pixel information of the image.
Lines 75-80 contain the loop that will read from the disk and transfer the data to VRAM. This loop will continue until we no longer read more bytes from the disk (rd). Now, at line 77, we indeed read as many bytes as the buffer size. If the file size is not a multiple of the buffer size, the last read will return a smaller value to rd, and the following read will be 0, ending the loop. Once we have the bytes in the buffer, the "HMMC" function transfers all the bytes from RAM to VRAM. First, we indicate where these bytes are located, then the X origin coordinate (which will always be 0 since we have made multiples of the line), the Y coordinate that will vary by 20 (which is the line size we read), and finally, we pass the width and height of the image block to be copied, in this case, the 256 for screen 5 and the 20 lines of height.
The next function, "LoadPalette", is responsible for passing the color palette to the VDP. It operates similarly to loading the image, but here instead of transferring from RAM to VRAM, we load a RAM vector and call the Fusion-C function "LoadPalette", which fills the VDP registers with the corresponding RGB values. We start by indicating to the DOS the name of the file we want to load, handle error detection, skip the first 7 bytes that do not provide relevant information, and then load the 24 bytes with the RGB information of the palette into an array.
Have you said 24? Impossible! You are right, with 16 colors per 3 color components (RGB), it indeed makes 48 bytes, not 24. However, the Python script compresses this information, as each component can take values from 0 to 7, using only 3 bits. Thus, each byte contains the definition of two color components. Therefore, if we have the definition of 48 color components, we need 24 bytes.
Lines 98 to 100 handle the conversion from these 24 bytes to 48 bytes and then convert it to the format required by Fusion-C at lines 102 to 107. The "SetPalette" function is called to write the VDP registers with the color definitions.
The function "FT_LoadPalette_MSXViewer" is used to load the palette obtained with the MSX Viewer. Like all functions that read data from the disk, it defines the file name and opens it for reading. Unlike the previous function, here we need to skip more bytes because the MSX Viewer generates two files - one with the pixel color indexes (the same as obtained with the script), and the palette is a binary to be executed once the image is loaded. After studying the generated file, we realize that the palette information starts from byte 0x30 (48 in decimal) and is not compressed. Therefore, the adaptation loop is much simpler (lines 145-148). Finally, we call the "SetPalette" function to save the palette to the VDP.
Before the main function, there is another function responsible for calling the images. If the image had been created with the Python script, we would need to use the "FT_LoadPalette" function with the same parameters as the "FT_LoadPalette_MSXViewer" function on line 158. Line 159 is unnecessary. And now we have the main function that selects the screen mode, calls the function to load images, waits for a key, and then finishes by returning to the text editing screen (screen 0), changing the palette back to the original colors, and restoring the values to the registers with "Exit(0)".
Final Comments
VRAM pages
When you run the program, you'll notice that the image loads in a choppy manner. This is due to the process of loading it into memory and then displaying it. To avoid this, you can load the image into a hidden page of VRAM, indicating that it should start at y=256 instead of y=0.
But what are VRAM pages? The pixels of the image to be displayed on the MSX are 256*212. Each pixel is stored in 4 bits (1/2 byte), so we have 256*212/2 = 27136 = 0x6A00 bytes. The VDP of MSX2 has 128Kb (131072 = 0x20000 bytes), which can accommodate 4 pages in memory (27136*4 = 108544) < 131072. Once you have a page filled with pixel values, you can tell the MSX to display that page. In BASIC, you would use the "set page" command, but now in C, we have the "SetDisplayPage" function.
The following diagram shows an outline of the pages and VRAM addresses used by the VDP (extracted from the msx.org wiki):
Another place where it is very well explained is in the Fusion-C manual. It also shows the area in page 0 where sprite information and palette data are stored. You can verify that the other pages have more bytes than pixels to display. These extra pixels remain hidden and can be used to store images for the animation of the main character, letters to create signs, and more.
Click here to see the example in action.
Comments