OK, so I've done what I set out to do (along with some mission creep) with the SMS core. My personal build now has a menu item, "Masked left column", with the following options:
"BG", short for background, which uses the background/overscan colour when the left column is masked:
- fAPrP9C.png (3.02 KiB) Viewed 10581 times
This is how it's handled on original hardware, and the default in my version.
"Black", which uses black to mask the left column:
- 8vdhJlh.png (3.09 KiB) Viewed 10581 times
This is not accurate to original hardware, but makes sense on MiSTer where the rest of the overscan area is blacked out unless you have the "Border" option turned on. This maintains the 256 pixel width of the game image.
And "Cut", which cuts off the left eight pixels when the left column is masked:
- tTh0fES.png (2.95 KiB) Viewed 10581 times
The difference between this and "Black" is subtle but significant: the image produced here is 248 pixels wide instead of 256. That means that scaling set up for 256 width will be distorted when this is set. Since many SMS games turn left column masking on and off depending on what's happening in-game, even if you change your aspect ratio to match the 248 width you'll wind up with distortions when 256 pixel images are scaled for that AR. The advantage of this setting is that it only includes the active area of the screen, I guess. It's the current behaviour of the official SMS core.
My new menu option becomes inaccessible when the "Border" option is turned on:
- 4vl7mKA.png (3.23 KiB) Viewed 10581 times
Because showing the borders already includes the masked column shown in background colour, my new menu item is redundant when they're on. But wait, I hear you say, why is that last image 239 pixels tall? Surely that should be a multiple of 8?
- yXMPvUO.png (3.13 KiB) Viewed 10581 times
Well spotted, my observant friend. I also tweaked the overscan area a little - I added one more pixel row to the bottom of the image to make it 240 high, as in the image above. I could be completely wrong about this, but I believe the theoretical limit of SMS vertical resolution is 240 pixels, so this seemed more appropriate. Also 239 is a prime number, so if I want to fiddle with custom aspect ratios for border on mode (which I do), I won't be able to simplify them very well. 240 is very divisible, so I'll be able to have smaller numbers in my mister.ini. The horizontal overscan is kind of odd-seeming as well - twelve pixels on the left (20 if the column is masked) and 14 on the right - but the benefit of tweaking that is less obvious to me and I found the code that sets the sizes very confusing and I'm trying not to break anything, so I left it alone.
Anyways, here's how I did it. Bear in mind, I figured all this out by reading the code, trying stuff out, and looking up what I could find information on. I haven't made a serious effort at programming since 2001. What I'm saying is, if my code is sloppy or inefficient, or my explanations of why what I've done works is completely wrong, that's why. The target audience of this explanation is me a week ago when I was trying to figure this all out.
OK, first up: some changes to
SMS.sv. I added the following below line 180:
Code: Select all
"D5P1OST,Masked left column,BG,Black,Cut;",
This is the menu option. D5 disables it when the fifth bit in the menumask is set, P1 nests it in the audio/video options submenu, OST makes it an option modifying bits S and T (28 and 29) in the CONF_STR, and the rest is the title and options. Next, change line 268 from
Code: Select all
.status_menumask({~gun_en,~raw_serial,gg,~gg_avail,~bk_ena}),
to
Code: Select all
.status_menumask({status[13],~gun_en,~raw_serial,gg,~gg_avail,~bk_ena}),
This sets the menumask's fifth bit to reflect whether the border is on or not. I need this so I can disable my menu option when the border is on. This is not strictly necessary, since my changes are ignored anyway when the border is on. OK, now for line 498:
Code: Select all
494 .x(x),
495 .y(y),
496 .color(color),
497 .mask_column(mask_column),
498 .black_column(status[28] && ~status[13]),
black_column here is new. This is how I convey the request to use black instead of the background colour to mask the left column. status[28] gets the state of bit 28 of the CONF_STR, which is set by my menu option. If BG is selected, it will be zero. If Black is selected, it's one. If Cut is selected, it doesn't matter because the column won't be shown (but it'll be zero). status[13] is the border option. The tilde inverts the result. So basically black_column is positive if Black is selected in the menu and Border is not. The reason I check on the border here is that an earlier build did this:
- 2D0pDZk.png (3.21 KiB) Viewed 10580 times
when the border was on. Undesirable. Anyway, I'll get in to how the mask colour is set a bit later, for now there's one more change in SMS.sv, at line 639:
Code: Select all
632 video video
633 (
634 .clk(clk_sys),
635 .ce_pix(ce_pix),
636 .pal(pal),
637 .gg(gg),
638 .border(status[13] & ~gg),
639 .mask_column(mask_column),
640 .cut_mask(status[29]),
cut_mask is the new item here. It reads bit 29 of the CONF_STR, which is 0 when BG or Black is set, and 1 when Cut is set. Putting this here sends that information to video.vhd, which is the part of the code that determines which parts of the screen are included in the image MiSTer produces. That's the last change to SMS.sv, so let's take a look at what I've done to
video.vhd:
First off, I added a line at line 14:
Code: Select all
6 entity video is
7 Port (
...
14 cut_mask: in std_logic;
This is within the port section of the definition of entity video. So cut_mask takes the value specified in the .cut_mask line I put in to SMS.sv. "in" is for input, meaning it reads but cannot write the information. I think I know what std_logic is about, but not well enough to explain it. Mainly I just copied it because that's what all the other lines in this port section had. Anyway, the code in video.sys now has access to a variable called cut_mask that's set to 1 if the user wants to cut off the left side of the screen and 0 if they'd rather obscure it. Next, I changed line 135 from
Code: Select all
134 hbl_end <= conv_std_logic_vector(500,9) when border = '1' and gg = '0'
135 else conv_std_logic_vector(008,9) when (border xor gg) = '0' and mask_column = '1'
136 else conv_std_logic_vector(000,9) when (border xor gg) = '0'
to
Code: Select all
134 hbl_end <= conv_std_logic_vector(500,9) when border = '1' and gg = '0'
135 else conv_std_logic_vector(008,9) when (border xor gg) = '0' and mask_column = '1' and cut_mask = '1'
136 else conv_std_logic_vector(000,9) when (border xor gg) = '0'
By putting "and cut_mask = '1'" at the end, it checks whether that variable is set before implementing this. And what is it implementing? Well, here goes: hbl_end is the point in each row where it stops ignoring the pixels and starts writing them to the image. So line 135 is saying, if the border is off, the system isn't in game gear mode, the column is supposed to be masked, and the user has requested the masked bit be cut off, ignore the first eight pixels. Line 136 is saying if the border is off, it's not game gear, and either the mask is off or the user has asked to include it, then don't ignore the first eight pixels.
Note that the first line is saying that if the border is on and it's not a game gear, start reading pixels from number 500. This confused the heck out of me for a while, but each horizontal row goes up to 511, so when the border is on the overscan is actually read from the end of the previous line before it wraps back to zero. Sort of. The vertical count increments at horizontal count 487, so the end of the previous line is actually the start of the current one. Or something. The horizontal count also skips from 295 to 466. I'm not really sure why, and it doesn't matter for this.
Anyway, the above code either includes or cuts off the left column when it's masked, depending on menu setting. That's what I initially set out to do. The other thing I did in video.vhd was to change line 120:
Code: Select all
else conv_std_logic_vector(215,9) when border = '1' and gg = '0'
to
Code: Select all
else conv_std_logic_vector(216,9) when border = '1' and gg = '0'
This is setting vbl_st to 216 instead of 215. vbl_st is the setting that determines when the MiSTer starts ignoring rows of pixels. So by increasing it one I added an extra row to the bottom of the image when border = '1'. This is how I got my bordered screen's vertical resolution up to 240 instead of 239. 216 is 24 higher than the SMS's active vertical resolution of 192.
OK, so that's the mask cutoff and the extra row of pixels. How about the colour of the mask? Well, that was a little more complicated. If you remember back in SMS.sv I had a line ".black_column(status[28] && ~status[13]),"? This was under system, so it passed the result of that (should the masked column be black yes/no?) to
system.vhd. So at the start of that file I added
As line 58, just under where mask_column is. I stuck it next to that already-extant variable because they seemed like an appropriate pair. This gets the information from the menu into system.vhd. But system.vhd doesn't do the masking. Further down in the file is "vdp_inst: entity work.vdp", and under that there's a port section. In there at line 247, just under mask_column again, I added
which I think takes the information in system.vhd and passes it to vdp.vhd (vdp being video display processor). So then I open
vdp.vhd and insert the same two lines at lines 28 and 141 respectively, taking the information and passing it on to vdp_main.vhd (which is where this will actually be done). In
vdp_main.vhd I add "black_column: in STD_LOGIC;" as line 29. That gets the information in. Now, how exactly vdp_main.vhd does its thing is still a bit of a mystery to me. It sets a bunch of variables like cram_a and cram_d (I assume the address and data of the cram being worked on). Then there's the out_color and the vram. Which of these does what? I spent a while looking at it and the various if else statements relating to the mode of the SMS and so on, and then I noticed down the bottom:
Code: Select all
color <= cram_D when smode_M4='1' else
"000000000000" when out_color="0000" else
"000000000000" when out_color="0001" else
And the list of colours to set went on for all the values of out_color. Could it be that I just needed to ignore all the preceeding stuff and set color to black when the mask was on and set to be black? Let's try it out:
Code: Select all
color <= "000000000000" when black_column='1' and mask_column0='1' and x>0 and x<9 else
cram_D when smode_M4='1' else
"000000000000" when out_color="0000" else
So here I've set it to make the colour black ("000000000000") if the mask is on, the colour is set to black, and the pixel currently under consideration is part of the leftmost column. I was worried this would screw up the image under the mask, which is required for processing of horizontal scrolling, but it doesn't seem to have. So far as I can tell, everything is working as intended.
Man, that's a lot of post for what would have been a very rudimentary programming job if I'd had any idea what I was doing. Hope my explanation makes sense. I think that this is an improvement to the core, and I hope people agree. If so, what do I need to do to get it incorporated into the official version?