Author Topic: When a VWF ... really isn't.  (Read 296 times)

elmer

  • Hero Member
  • *****
  • Posts: 2148
When a VWF ... really isn't.
« on: August 20, 2015, 07:20:26 PM »
I'm replying to NightWolve's question here to avoid polluting the original thread ...


Custom VWF font (with added drop-shadow) ...




Impressive work to be sure! How did you pull that off ? Like, say, how much PC-FX assembly code did you write/mod to accomplish the task ?


I'd love to be able to say wonderful things about how smart I am in hacking a VWF into Team Innocent ... but I'm afraid that I can't.

The truth is, that what you're seeing in that screenshot isn't a "true" VWF ... it's a "BWF", a bi-width font.

The characters that are displayed are either 4x16, or 8x16.

The actual font itself is designed in such a way as to minimize the clues that would give away that fact that it isn't a "true" VWF.  :-"

Now, the reason for doing this instead of a real VWF, is that Team Innocent writes all of it's strings to a "virtual" screen in 4x8 pixel chunks, and then uploads any "changes" to VRAM in a totally separate "task" that runs every-now-and-again.

I couldn't figure out how to hack a VWF into such a bizarre scheme, but it did eventually occur to me that although the code normally just prints 8-wide or 12-wide fonts (2 or 3 4-pixel chunks), there was no reason that it couldn't print single chunk wide characters (i.e. just 4-pixels).

So, the entire "VWF" hack was just telling it that certain characters should be printed with a width of 1 4-pixel chunk instead of 2 or 3 4-pixel chunks.

Then it was just a case of hacking in a custom set of font data that took advantage of the new capability.

So, the new "VWF" code is just ...

_get_glyph_width:
        movea   0x3fff, r0 ,r1          // glyph $8000-$ffff (12x12)
        cmp     r1, r27
        mov     3, r28                  // width=3
        bgt     1$

        movea   0x1fff, r0 ,r1          // glyph $0000-$00ff (8x8)
        cmp     r1, r27
        mov     2, r28                  // width=2
        ble     1$

        addi    -' ', r27, r1           // glyph $2000-$20ff (6x12)
        andi    0x7f, r1, r1

        movea   lo(_step8x14v), r1, r1  // read the width (in 4-pixel chunks)
        movhi   hi(_step8x14v), r1, r1  // from a table.
        ld.b    0[r1], r28

1$:     jr      _got_glyph_width


If that looks too strange, this might help ...

V810 CPU Architecture Manual
http://www.goliathindustries.com/vb/download/cpu/U10082EJ1V0UM00.pdf

Now, as to "how" I actually got to the point of knowing how the Team Innocent code was working in that strange way, and exactly where to hack in that "solution".

I'm afraid that that's just the usual boring hacking job of identifying interesting-looking memory accesses, and then tracing and hand-disassembling a few thousand instructions back into their equivalent 'C' code to understand what they're doing.  :(

What seems a little sad, is that while Zeroigar has "proper" VWF font printing, with kerning, too ... the truth is that most people won't notice the difference.

And that was a significantly larger and much more complex piece of code to write!

I think that the big thing for anyone reading this to take away, and a good lesson that I'd totally forgotten myself, is that you sometimes don't need the "perfect" solution in order to get something that actually looks good on the screen.  :wink:
« Last Edit: August 20, 2015, 07:25:31 PM by elmer »

ccovell

  • Hero Member
  • *****
  • Posts: 2245
Re: When a VWF ... really isn't.
« Reply #1 on: August 20, 2015, 10:59:21 PM »
Yes, interesting.  It's got a futuristic-looking font where everything is the same space and would be fine in monospace... except for l, i, t...

esteban

  • Hero Member
  • *****
  • Posts: 24063
Re: When a VWF ... really isn't.
« Reply #2 on: August 21, 2015, 12:51:27 AM »

I'm replying to NightWolve's question here to avoid polluting the original thread ...


Custom VWF font (with added drop-shadow) ...



Impressive work to be sure! How did you pull that off ? Like, say, how much PC-FX assembly code did you write/mod to accomplish the task ?

I'd love to be able to say wonderful things about how smart I am in hacking a VWF into Team Innocent ... but I'm afraid that I can't.

The truth is, that what you're seeing in that screenshot isn't a "true" VWF ... it's a "BWF", a bi-width font.

...

I think that the big thing for anyone reading this to take away, and a good lesson that I'd totally forgotten myself, is that you sometimes don't need the "perfect" solution in order to get something that actually looks good on the screen.  :wink:

That's an elegant solution. :)

  |    | 

Bonknuts

  • Hero Member
  • *****
  • Posts: 3292
Re: When a VWF ... really isn't.
« Reply #3 on: August 21, 2015, 03:48:53 AM »
Ahh. So it's a hybrid. Looks great.

elmer

  • Hero Member
  • *****
  • Posts: 2148
Re: When a VWF ... really isn't.
« Reply #4 on: August 21, 2015, 05:59:35 AM »
Now, if anyone is curious what real VWF code looks like on the PC-FX, here's an example.

Remember that the PC-FX uses the same VDC chip as the PCE, so you can directly compare this to how you'd do it on the PCE, the output data is identical.

This is the code that I wrote to produce the yellow "speech" text in Zeroigar.

The V810 is a RISC processor, so some of the instruction ordering looks a little strange in order to avoid pipeline stalls.




//
// ****************************************************************************
//
// _print_6x14_015_vwf_glyph
//
// r6 = VDC I/O address (VDC-A=$400, VDC-B=$500)
// r7 = glyph number
// r8 = X lhs-coordinate (0..255) | (X rhs-coordinate << 16)
// r9 = VRAM address of start of row
//
// N.B. Each row is stored as 'n' columns of 8x16 (i.e. 1x2 chr).
//

#define rVdcPtr  r6                       // PRESERVED!!!
#define rChrNum  r7
#define rXCoord  r8                       // PRESERVED!!!
#define rDstRow  r9                       // PRESERVED!!!

#define rCol0Ptr r10
#define rCol1Ptr r11
#define rCol2Ptr r12                      // Only if max font width >  9 pixels.
#define rCol3Ptr r13                      // Only if max font width > 17 pixels.

#define rOldShd  r14
#define rCurFnt  r15
#define rCurShd  r16
#define rCurBkg  r17

#define rLinCnt  r18
#define rRotCnt  r19

#define rFntPtr  rChrNum

#define rTmpVal0 rOldShd
#define rTmpVal1 rCurFnt
#define rTmpVal2 rCurShd
#define rTmpVal3 rCurBkg

#define rChrCnt  r19

_print_6x14_015_vwf_glyph:
        addi    -32, rChrNum, rChrNum
        mov     14, r1
        mulu    r1, rChrNum
        movea   lo(_font6x14v), rChrNum, rFntPtr
        movhi   hi(_font6x14v), rFntPtr, rFntPtr

_print_015_vwf_glyph:
        add     -4, sp
        st.w    lp, 0[sp]

        andi    0x0018, rXCoord, rCol0Ptr
        shl     3, rCol0Ptr
        movea   0x0040, rCol0Ptr, rCol1Ptr
        andi    0x00c0, rCol1Ptr, rCol1Ptr
        movhi   hi(_workspace0), r0, r1   // 32x16 4-bpp workspace,
        movea   lo(_workspace0), r1, r1   // 256-byte long, 16-byte aligned.
        add     r1, rCol0Ptr
        add     r1, rCol1Ptr

        andi    0x0007, rXCoord, rRotCnt  // Calc rotation + 16. This prints 2
        addi    16, rRotCnt, rRotCnt      // columns (max font width <= 9 pixels).

        mov     r0, rOldShd               // If we're on pixel 0 of a new chr, then
        bne     2$                        // it's not *guaranteed* to be blank.
        mov     rCol0Ptr, r1              // Clear the current chr's workspace.
        mov     (64/8), r30
1$:     st.w    r0, 0x00[r1]
        add     -1, r30
        st.w    r0, 0x04[r1]
        movea   8, r1, r1
        bnz     1$

2$:     mov     14, rLinCnt               // Glyph height.

        add     2, rCol1Ptr               // Center 14-pixel high glyph in
        add     2, rCol0Ptr               // 16-pixel high buffer.

3$:     ld.b    0[rFntPtr], rCurFnt       // Get the next line of font data.
        add      1, rFntPtr
        shl     24, rCurFnt

        mov     rCurFnt, rCurShd          // Get the next line of shadow data.
        shr     1, rCurShd
        or      rCurFnt, rCurShd

4$:     mov     rOldShd, r1               // Add in previous line's shadow data
        mov     rCurShd, rOldShd          // to make it a "drop-shadow".
        or      r1,      rCurShd

        // font data pixel  = color 5
        // font drop shadow = color 1
        // background color = color 0

        // Process RHS column.

        st.b    r0, 0x01[rCol1Ptr]        // rhs plane 1 (cleared)
        st.b    r0, 0x11[rCol1Ptr]        // rhs plane 3 (cleared)

        shr     rRotCnt, rCurShd
        st.b    rCurShd, 0x00[rCol1Ptr]   // rhs plane 0 (set on font or shadow)

        shr     rRotCnt, rCurFnt
        st.b    rCurFnt, 0x10[rCol1Ptr]   // rhs plane 2 (set on font only)

        // Process LHS column.

        shr     8, rCurShd
        ld.b    0x00[rCol0Ptr], r1        // lhs plane 0
        or      rCurShd, r1
        st.b    r1, 0x00[rCol0Ptr]        // lhs plane 0 (set on font or shadow)

        shr     8, rCurFnt
        ld.b    0x10[rCol0Ptr], r1        // lhs plane 2
        or      rCurFnt, r1
        st.b    r1, 0x10[rCol0Ptr]        // lhs plane 2 (set on font only)

        add     2, rCol0Ptr
        add     2, rCol1Ptr

        andi    0x000e, rCol0Ptr, r0      // End of plane 0&1 data?
        bnz     5$

        movea   16, rCol0Ptr, rCol0Ptr    // Skip to next chr down.
        movea   16, rCol1Ptr, rCol1Ptr    // Skip to next chr down.

5$:     add     -1, rLinCnt               // Draw the next line of font data.
        bgt     3$

        mov     0, rCurFnt                // Draw the bottom line's drop shadow.
        mov     0, rCurShd
        bz      4$

        // Now copy the glyph's columns to vram.

        andi    0x00f8, rXCoord, r1       // Setup the VDC write address
        shl     2,  r1                    // (starting row from rDstRow)
        out.h   r0, 0[r6]
        add     rDstRow, r1
        out.h   r1, 4[r6]
        mov     2, r1
        out.h   r1, 0[r6]

        mov     rXCoord, rChrCnt          // Calc (rhs-chr - lhs-chr) to
        shr      3, rChrCnt               // see how many chrs to upload.
        andi    0x1fff, rChrCnt, r1
        shr     16, rChrCnt
        sub     r1, rChrCnt

        movea   -64, rCol0Ptr, r7

        movea   (64/4), r0, r1            // Upload LHS column to VRAM.
6$:     ld.w      0[r7], rTmpVal0
        add        4, r7
        add       -1, r1
        out.w     rTmpVal0, 4[r6]
        bne       6$

        add      -1, rChrCnt              // Do we need to upload another column?
        movea   -64, rCol1Ptr, r7
        bn      8$

        movea   (64/4), r0, r1            // Upload RHS column to VRAM.
7$:     ld.w      0[r7], rTmpVal0
        add        4, r7
        add       -1, r1
        out.w     rTmpVal0, 4[r6]
        bne       7$

8$:     ld.w    0[sp], lp
        add     4, sp

        jmp     [lp]
« Last Edit: August 21, 2015, 07:16:36 AM by elmer »

NightWolve

  • Hero Member
  • *****
  • Posts: 5277
Re: When a VWF ... really isn't.
« Reply #5 on: August 21, 2015, 06:07:41 AM »
Thanks for sharing John! Yeah, as I thought, you sure know what you're doing! That wasn't child's play right there when dealing with your real VWF implementation...

We could've *really* used a guy like you ~10-15 years ago for this stuff! Not to mention a PCE/TG-16 emulator with a good internal debugger - I had asked David Michel many times to someday add that to MagicEngine, but he never got around to it.