top of page

Line interruption

  • Writer: MoltS Xalats
    MoltS Xalats
  • Apr 29
  • 9 min read

Introduction

Until now, we had been using the VBlank to control the game's tempo.The VBlank is the time it takes for the beam of light to go from the last bottom line back to the first top line. Depending on the encoding system, PAL or NTSC, this return trip happens 50 or 60 times per second, respectively.However, the VDP 9938 and 9958 allow for a line interrupt — we can specify which of the 212 lines it draws (and the hidden ones) should trigger an interrupt generated by the VDP.


And what’s the use of triggering it midway instead of at the end of the screen? Once interrupted at a certain line, we can modify the VDP registers so that when it continues drawing, it displays another page, changes the color palette, etc. In our case, we’ll use it so that within the scrolling screen, there is a section that remains static, unaffected by the scroll. This section can then act as a status bar or HUD, to show information without it moving.


Before learning about the line interrupt, I had started developing the static part of the screen using copy operations,but the calculations were numerous and there was flickering due to the delays from all the processing.Then I came across this code on msxpen  that used line interrupts in a small program to observe how it worked.And I thought this would be a good solution to the problem of having a fixed area.


How to program a line interruption

The line interrupt is only available on MSX2 or higher. It uses register 19 to store the line number at which the interrupt will occur, and the VDP must be configured to generate line interrupts by writing a 1 to bit 4 of register 0, which Yamaha's documentation refers to as IE1 Enables interrupt from horizontal line.


In addition to enabling it, we’ll have to change the hook, which currently does nothing. This is the same as what we do with the VSync hook — the carriage return — by setting the address the line jumps to so that it points to our routine instead of the default code.


The operations we perform during this interrupt must be short, since the VDP continues drawing whatever is in VRAM. If we take too long, the number of lines with the VDP in an unstable state — that is, in unknown states — will be larger.


Changing the background color

A simple program that helps us learn how to create a line interrupt is the one we’ll look at next. We’ll use the interrupt to change the background color, creating a stripe of a different color that moves up and down.


First of all, we define the routine we've already used before to create the interrupt hook: InterruptHandlerHelper, InitializeMyInterruptHandler, EndMyInterruptHandler.

After that, we define the variable cpt, which will indicate which part of the line interrupt we're in. That is, we need to perform two line interrupts per vertical refresh: the first interrupt is when we make a change on the screen and set the next line interrupt; and the second interrupt happens at the end of the vertical stripe we modified, where we need to restore the original screen.


The variables Ystart and Yend indicate the line number where the interrupt will be triggered — first at Ystart and then at Yend. The variable Y will be used to scroll the background, and the variable d will be used for the scroll direction: if it's going up (d > 0) or down (d < 0).


And on line 83, we define the function that will be executed at each line interrupt: HBlankHook, where the first thing we do is check if we are in the first part of the interrupt or the second, using the cpt variable.If we’re in the second part (cpt = 0), we set the next line interrupt in register 19, change the background colors with the SetColors function, and update the cpt value to identify the new state at the next interrupt. At the following interrupt (cpt != 0), we set the next interrupt for the end, and again change the background color and update the value of cpt.


Then, on line 100, we have the VDP hook, where we check whether the interrupt was caused by a horizontal interrupt using the IsHsync function. If it was, we execute the previous function that handles the line interrupt. If we don’t detect that it came from the line interrupt, this hook still runs on the vertical interrupt — as you may have noticed, it’s the same routine we’ve used to replace the hook in other programs.

We define the FT_Wait function, which is responsible for introducing a delay so we can observe how the scroll evolves — otherwise, it would move so fast we wouldn't see anything.


At line 116, we begin what would be the main function. We define the variable IE1, which will hold the value that was in VDP register 0 before we modified it to allow line number interrupts (we assign it at line 123), and we’ll use it later to restore the original value.


We activate screen 5 on the MSX and write the line number for the next interrupt (which is 50) into VDP register 19. At line 123, we read the value of register 0 and activate bit 4 — which corresponds to HSync — while keeping the rest of the bits unchanged.


In the following lines, we write the new value into video memory and into register 0. We initialize the interrupts, and at line 129, we define a loop that will keep running until the ESC key (number 27 in the keyboard matrix) is pressed.


Inside the loop, we wait and increase the Y-coordinate value where the next interrupt will take place. At line 132, we check if the interrupt point has passed the boundary of the color change area, and if so, we reverse the scroll direction.


When we exit the loop, we restore the original value of IE1 by disabling the HSync bit, remove the interrupt hook, return to screen mode 0, and exit the application.

This progam can be found at MoltSXalats' repository.


We make the line interrupt more complex

Now that we’ve had our first contact with how the line interrupt works, we want to apply this knowledge to our program that performs pattern scrolling, in order to ensure that part of the screen remains stationary and we can display information such as scores, lives, weapons, or other types of data.


Thus, the tasks to be performed during the interrupt are: switching to the next page to display the screen, setting the horizontal and vertical scroll values to show the corresponding part of the screen, removing the sprites, and setting the line interrupt to the next position. If the program is at this next position, it must restore the corresponding scroll values for the pattern mapping and redraw the sprites.


Early implementations

In the beginning, when I enabled the line interrupt, there was a flickering effect, and I couldn’t figure out what was causing it. I asked on the MSX Resource Center forum, and kindly, the user Sandy Brand helped me out. What was happening was that inside the line interrupt, the commands I was using were re-enabling interrupts, and that’s why the flickering occurred.


Once I changed all the functions inside the line interrupt so that they wouldn’t re-enable interrupts, I noticed the flickering had greatly diminished. However, occasionally it would still happen, and that was because the interrupt register sometimes remained active and would trigger again. By clearing the register, I managed to prevent the flickering altogether. But there were times when the line interrupt didn’t occur. After debugging, I discovered that this time it was the VDP command functions from Fusion-C that were disabling interrupts to ensure the operation would complete — and as a result, the line interrupt wasn’t being triggered.


The next issue was eliminating the half-line drawing that occurred when the interrupt was triggered. Between the moment the VDP detects it’s on the interrupt line and when the commands in the hook start executing, the line ends up being drawn at a different length. So how can we fix that? In this article about line interrupts by Grauw, it’s suggested to create a black line, which hides the visual effects. We implemented that, but the problem was that when scrolling, the screen switch would still leave artifacts from other lines. How could we solve that? One option was to increase the number of black lines, so the switch would always happen in the black area. The downside was that this increased CPU usage. Another option was to disable the VDP, just like many programs do when drawing the screen and then showing it. But wouldn’t that turn off the entire screen and cause a flicker? Apparently not — it seems the VDP only disables the video signal generator module but continues running its processes, and when it’s reactivated, it picks up right at the correct line (either that, or it’s so fast I haven’t been able to detect it).


Applying it to pattern map scrolling

In order to avoid copying all the code that has already been explained in another article, here we’ll only explain the functions that have been modified to account for the line interrupt.


These changes aim to eliminate the disabling of interrupts, since Fusion-C ensured that commands would complete by disabling them. But that’s not what we want, because it ends up causing flickering on the screen as the image isn’t refreshed at the right moment.


We begin by defining a set of constant values that we’ll use for the interrupt, and then we define the set_scrollH_NI(char n) function, which is a copy of Fusion-C’s set_scrollH but with the ei and di instructions removed. What we do is read the parameter at line 342 and update registers #26 and #27 with the corresponding values.

Here we do the same: we’ve based our function on Fusion-C’s vdpstatusni and created the function VDPstatusSenseInt(unsigned char registerNumber), removing the ei and di instructions. This function reads VDP register #1, which provides the status to determine the type of interrupt and to clear the flag in case there was one pending in the queue.

To make VDP commands faster and reduce the chance of them being interrupted midway by a line interrupt, we followed Grauw's code presented on this page, which we transcribed into the C function fastVDP_Ni(FastVram2Vram *paramFastVDP). This function is also implemented in the Fusion-C library (fastvdpcopy.c), but using interrupt enablers. The FastVram2Vram structure is defined in vdp_graph2.h. What the function does is to prepare the VDP registers to receive commands and send all the values contained in the structure — which correspond to consecutive VDP registers — using the OutI function.

And then we have the commands that are executed during the line interrupt. The repetirYMMM variable is used as a flag to indicate whether the VDP command interrupted from the main code needs to be repeated. Next, we have the selector to determine whether we are in the first part of the interrupt or the second. If we are in the first part, we turn off the screen by writing the value 0x20 to VDP register #1. We reset the vertical scroll by writing to register #23, and the horizontal scroll using the function explained earlier. We enable the sprites and switch the displayed page to the one containing the drawn tiles. Then, we turn the screen back on, set the new line interrupt position, and read the interrupt register to clear it.

In the second part, we perform similar steps: turn off the screen, set the scroll values for the sign/banner area, turn the screen back on, set the next line interrupt, enable sprites, and clear the interrupt register.

The function guardemLiniesAlBuffer(signed char offset) is used to store the lines that will disappear, in order to hide the visual garbage that appears when the line interrupt is triggered — these lines will be replaced by black lines. Since it’s a block of two lines, we have to take into account the end of the VRAM page — line 255 — because if the block of 2 lines starts there, the second line of the block will be taken from the first line of the next VRAM page instead of the first line of the current page, which is what we want. That’s why at line 529 we check for this case and split the block to be copied into two parts. If instead of copying a block of two lines we wanted to copy three, we would also need to check for line 254 and add another case. But if we only copy a single line, we just need to handle it like in the else block.

Before each command, we set the repetirYMMM flag to zero, and then after each command we check whether it was left incomplete or not.

And the function that does the opposite — it brings the lines that were stored in the buffer back to the visible VRAM page — is the retornemLiniesBorrades(signed char offset) function. It takes a parameter because, depending on whether the calculation is done in scroll_up or scroll_down, it must take one value or another. Just like in guardemLiniesAlBuffer, we need to take into account the boundary at line 255.

The different functions that move the screen also need to be modified to use the function that doesn't disable interrupts. Here, we show how the one that scrolls upwards, scroll_up(), would look.


Conclusion

This article has presented the basic operation of line interruption with the program intlin.c. After experimenting with a simple program to understand its functioning, this knowledge was applied to the program that was created for the pattern scroll, having to debug it many times due to the complexity that wasn't present in the simple program, mainly caused by the disabling of interrupts. These modifications can be found in scfoInLi.c. With this, we have managed to have a part of the screen that remains unchanged and allows us to display informative elements of the game.


Click here to see the example in action.




 
 
 

Commentaires


Send us a message and tell us what you think

Thank you for your message!

© 2023 Created by MoltSXalats with Wix.com

bottom of page