Sub-pixel graphics idea

Basic and Machine Language

Moderator: Moderators

IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

Here's an 80 column display demonstrator for PAL. I don't have a PAL VIC, so it looks like garbage on mine.

Thus, I can't test whether the alignment is correct. You may need to swap RED and CYA in order to get the correct look. But even with the wrong look, you should be able to see the desired effect. Either hook up the VIC to a B&W monitor or hook up the composite output to the LUMA line of an S-Video monitor/TV.

Code: Select all

1 ?"{clr}{blk}"
10 fory=0to7:poke5*1024+32*8+y,0:poke6*1024+32*8+y,0:next

20 fory=0to7:forx=0to2:read a:poke5*1024+x*8+y,a:next:next
30 fory=0to7:forx=0to2:read a:poke6*1024+x*8+y,a:next:next

40 fort=1to23:print"@ab@ab@ab@ab@ab@ab@a ":next

50 w=36868:v=128:red=2*16:cya=3*16
55 b=36869:c=36879:d=253:e=254
60 waitw,v:pokeb,d:poke36879,red:goto70
70 waitw,v:pokeb,e:poke36879,cya:goto60

300 data 243, 32,  0
301 data   0,160,160
302 data 242,207,224
303 data   0, 34,  0
304 data 243,255,240
305 data 0,0,0
306 data 0,0,0
307 data 0,0,0

400 data 162, 32,  0
401 data 242,207,240
402 data 128, 34,  0
403 data 242,207,208
404 data 162,168, 32
405 data 0,0,0
406 data 0,0,0
407 data 0,0,0
It should read "80 Columns" across the screen. Or maybe not. I hand calculated the bitmap->decimal conversion, so...
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

I've figured out how to do it in NTSC! Once I plotted out the overlapping chroma mask and pixel grid...it's shockingly simple. With a 23 character wide screen, it allows 322 pixels across (23*14)--thus allowing an 80 column display with square pixels.

The basic idea is to take each nybble and map 7 pixels to it.

Code: Select all

aaaabbbbaaaabbbbaaaabbbbaaaa <-alternating chroma mask
3333333222222211111110000000 <- nybble of pixels
The 7 pixels are mapped to:

Code: Select all

aaaa                         1000,0000
    bbb                      0000,1000
        aaaa                 0100,0000
       b    bbbb    b        0000,0110
                aaaa         0010,0000
                     bbb     0000,0001
                        aaaa 0001,0000
Notice how each pixel gets mapped to a single bit except for the middle pixel--this "special" pixel gets mapped to a pair of bits. The "special" pixel ends up with extra bits of pixel noise, but this shouldn't be too bad.

With NTSC, the chroma mask alternates every field without switching colors, so the only thing required is to change the character map each field. The color can stay constant.

This technique is suitable for large sprites and graphics, not just small sprites.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

Ah, sweet success! I have determined the required pixel mapping for the color yellow on NTSC:

Given 14 pixels numbered: DCBA9876543210, the required values are:

0 -> 1, field A
1 -> 3, field B
2 -> 2, field A
3 -> 4, field B
4 -> 4, field A
5 -> 8, field B
6 -> 8, field A

7 -> 16, field B
8 -> 48, field A
9 -> 32, field B
A -> 64, field A
B -> 64, field B
C -> 128, field A
D -> 128, field B

Note that each scanline you swap fields A and B. So, for example, if you want to create a nice diagonal line you only end up putting pixels in a single field.

Using this method, a diagonal line is slightly bumpy, but definitely nice looking. The "thick" pixels at positions "1" and "8" are slightly brighter than the other pixels, and they have a slight grey halo. However, as I expected this looks fine.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

On NTSC VICs, this technique can be used in full color. Using color means that small details will be eliminated by the TV's chroma filter (so, no 80 column mode). However, this is still good for a game since you still get doubled resolution for the sprite outlines.

The pixel mapping for Blue is identical to Yellow, swapping fields A and B. Blue is darker than Yellow, but the ghosts around the "thick" pixels are eliminated.

The mapping for Cyan/Red is:

0 -> 1, field B
1 -> 3, field A
2 -> 2, field B
3 -> 6, field A
4 -> 4, field B
5 -> 8, field A
6 -> 8, field B

The mapping is similar to Yellow/Blue, but there are TWO thick pixels per nybble. Pixels 3 and A are thick pixels; they overlap the mappings of pixels 1 and 8. This is unfortunate, and it means a slight more blurring of detail. This is due to the way the chroma mask lines up with the pixel clock...just unfortunate.

The mapping for Green/Purple is as good as Yellow/Blue:

0 -> 1, field A
1 -> 1, field B
2 -> 2, field A
3 -> 2, field B
4 -> 4, field A
5 -> 12, field B
6 -> 8, field A

Thus, you can have high resolution sprites of any color...except black or white ;)

With the standard 22x23 screen, you get a virtual resolution of 308x184. I'm thinking to develop a game using this resolution--something which really takes advantage of the extra resolution.
User avatar
nbla000
Salmon Run
Posts: 2582
Joined: Thu Oct 13, 2005 8:58 am
Location: Italy

Post by nbla000 »

Very nice work, what about Mario Bros or C64 Le mans remake ?
Mega-Cart: the cartridge you plug in once and for all.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

Thanks! I'm still deciding what to do with this technique. I feel like Mario Brothers or a racing game plays just fine at a VIC's normal resolution, so probably not that. Also, they're too sophisticated for me to cut my 6502 coding teeth on.

I'm thinking maybe I'll start with an upgraded hires version of my robotron style game. By splitting each character into 3x2, I get a boosted grid size of 66x46. I won't have to worry about handling sprites that cross character boundaries.

Beyond that, it's easier for me to get excited about a new game concept rather than cloning an old game.

I have an idea for adapting ship combat from Pirates into a space conquest game. In tactical combat, you control a fleet of warships in a line ahead formation (you control the leader, the rest follow head-to-tail). You press either the "fire left" or "fire right" keys to launch broadsides. Depending on how you've been turning, your broadside may end up converging or diverging. I like the idea of ships moving in a stately manner.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

A quick thoughts on RAM usage:

To minimize RAM usage, I'm only going to have 64 custom characters by default. By default only 96-127 are used.

Code: Select all

4.0K - 6.5K : free space
6.5K - 7.0K : Char Set 1 (96-127)
7.0K - 7.5K : Screen
7.5K - 8.0K : Char Set 2 (96-127)
Even though this option only provides 64 characters, I like it because it leaves a comfortable 2.5K available for the program.

It can optionally be expanded to:

Code: Select all

4.0K - 6.5K : 2.5K for programs
4.0K - 5.0K : free space
5.0K - 5.5K : Char Set 1 (0-31)
5.5K - 6.0K : free space
6.0K - 6.5K : Char Set 2 (0-31)
6.5K - 7.0K : Char Set 1 (96-127)
7.0K - 7.5K : Screen
7.5K - 8.0K : Char Set 2 (96-127)
This leaves only 1.5K, but it's compatible with the above. It could perhaps be used for extra graphics/text during an attract mode.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

Hmm...I may temporarily shelve this project for now...I just can't come up with any ideas which really take advantage of the extra resolution.

My original motivation was that I wanted an Asteroids type game with small sprites--but the VIC's wide pixels make rotation of a small ship look horrible. I'd still kind of like to see it, but rhurst's Omega Fury has this genre well covered already.

All of the other game ideas I've come up with seem playable with the VIC's normal resolution. Even the 66x46 grid Robovic can work with 3x4 pixel sprites.

So far, the only really cool HIRES game idea I've come up with is an adaption of the Apple ][ game BOLO. That would really show off the extra resolution. However, it's also a lot tougher than I dare deal with (for an unexpanded VIC, which is all I've got).

[edit added:]

Okay, I've changed my mind. The space battlefleet game does indeed show off the high resolution capabilities. Each ship will look like a short line segment, so a fleet is a moving dotted line. The "cannonballs" can be laser-like line segments to further show off the high resolution ability to draw nice looking lines.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

I've come up with a superior method which allows for mixing of:

1) Normal uppercase text
2) Normal bitmaps (including multicolor)
3) Subpixel bitmaps
4) Tricolor "flipping" bitmaps
5) Hybrid multicolor/hires bitmaps

It works with PAL as well as NTSC, although different methods are required to make subpixel bitmaps work.

You use a single font at 7168, and two screens at 6144 and 6656. Then, you switch screens for each screen refresh.

To display normal text and normal bitmaps, you print the same thing on both screens. You can get some extra colors by printing with different colors on the two screens.

To display subpixel, tricolor, or hybrid bitmaps, you print different things on the two screens.

This method gives you 2K of free RAM. The option to use normal and multicolor bitmaps is good for where they provide sufficient resolution. Since each subpixel bitmap consumes twice as much memory as a normal bitmap, you can reserve them for where the extra resolution counts.

For my space game, I like the idea of using subpixel graphics for ship combat and tricolor graphics for portraits.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Post by IsaacKuo »

It also occurs to me that you can mix subpixel rendering with tricolor techniques. Instead of keeping the same color for the two fields, you alternate between a dark and bright color. For example, you could alternate between blue and cyan. The chroma masks don't line up perfectly, but they're close enough. This gives you a 14x8 hires character that looks like this:

Code: Select all

bCbCbCbCbCbCbC
CbCbCbCbCbCbCb
bCbCbCbCbCbCbC
CbCbCbCbCbCbCb
bCbCbCbCbCbCbC
CbCbCbCbCbCbCb
bCbCbCbCbCbCbC
CbCbCbCbCbCbCb
You can ignore the alternating colors to simply plot hires pictures. Or you can mask your plots to one of the fields for a darker color. Essentially, you have four colors:

black
blue
Cyan
light cyan (Cyan plus blue)

I like this because it combines the high resolution capability of sub-pixel rendering with the 4 level brightness capability of color flipping.
IsaacKuo
Vic 20 Hobbyist
Posts: 147
Joined: Tue Aug 04, 2009 5:45 am

Notes on 73 column mode

Post by IsaacKuo »

Here are some notes and pseudocode for implementing a 73 column x 4 row display. You could improve this to 73x16 rows on an expanded VIC, but this restricted 73x4 display leaves me with a comfortable amount of RAM on an unexpanded VIC (all I have).

Two screens are at 6144 and 6656. Font is at 7168 for 128 custom chars and 128 standard chars. Flipping between the two screens at 60fps allows for subpixel rendering, tricolor, and hybrid modes.

This 73 column demo will use overlapping bitmaps of 21x3 characters, consuming 126 of the 128 available custom chars. The subpixel rendering technique provides a resolution of 21*14x3*8, or 294x24 pixels. Each letter takes up 4x6 pixels, so we have a 73 column by 4 row display.

To minimize the font memory usage, and to simplify things, each glyph is packed into 2 bytes. These 16 bits provide a 3x5 bitmap plus a "shift bit". The "shift bit" signifies shifting the bitmap down, for descender characters (gjpqy). Thus, the 128 character font only consumes 256 bytes.

Code: Select all

Font encoding takes two bytes: FEDCBA98 76543210

F = shift bit (lowers font by one pixel)

E94
D83
C72
B61
A50
The character is rendered in 3 passes--one for each vertical line of pixels. First, bit twiddling is used to reorganize the data into a single byte:

Code: Select all

6
5
4
3
2
1
This encoding is used so the byte may be simply ROL'd for each step. The current pixel is tested for via the high bit, and the loop is complete if the byte is zero.

After unpacking the current line of pixels, the starting address and bitmask are calculated. The first step is to divide the x coordinate by 14, leaving the remainder in A. However, to prevent overflow you divide by 7 instead:

Code: Select all

A = 2*column + [0/0/1]  {half of the x coordinate}
X = -1
LOOP:  {this loop divides A by 7}
A = A - 7
X = X + 1
repeat LOOP if A>0
A = A + 7  {undo overshoot}
A = 2*A + [0/1/0]
The next step is to calculate the starting address and bitmask:

Code: Select all

ADDR = 7*1024+6*row+24*X + [0/256/0]
MASK = LOOKUP_TABLE[A]
For example, the lookup table for green is:

Code: Select all

0 -> 128   01234567ABCD
1 -> 192
2 -> 64
3 -> 32
4 -> 32
5 -> 16
6 -> 16
7 -> 8
8 -> 12
9 -> 4
A -> 2
B -> 2
C -> 1
D -> 1
Note that this table is inverted from the above example, since the x coordinate is from left-to-right.

Next the current stripe is unpacked into a single byte bitmask via bit twiddling, taking into account the SHIFT bit:

Code: Select all

7
6
5
4
3
2
This encoding is used so the current pixel can be tested via the high bit. This byte is ROL'd each step.

The address alternates between ADDR+X and ADDR+32*8+X; this assumes the bitmaps are organized so chars 0-31 are used for one field while chars 32-63 are used for the other field. Thus, incrementing the address involves incrementing X and XORing ADDR's high byte with 1. Thus, the basic loop steps are:

Code: Select all

Test high bit
if false then POKE ADDR+X, PEEK(ADDR+X) AND (255-MASK)
if true  then POKE ADDR+X, PEEK(ADDR+X) OR MASK
X = X + 1
ADDR = ADDR XOR 32*8
This loop cycles 6 times for each vertical stripe; there are 3 vertical stripes.

I don't know if these ideas are at all comprehensible to anyone else. I'm working on a prototype in BASIC which will be SSSSLLLLLOOOOWWWWW, but should demonstrate the principle. The above thoughts are oriented toward machine code, though.
Post Reply