WME TODO: Notes for posterity (including future me)

Here is a list of todos and ideas for further improvement of the WME engine:

1. The new patches contributed by yours truly could probably benefit from some optimization, performance-wise.

In particular,

  • bilinear blitting is very naive, plus it’s all float, so it has large margins for optimization simply by replacing it with a well-known optimized algorithm.
  • Multirect could probably benefit from some fine tuning;
  • It’s a matter of finding the best balance between the additional cost of multirect and the cost of going through tickets and blitting.

On the other hand

  • ui_tiled shouldn’t really require any further efforts unless some weird bug pops up.
  • blending could have some margin for improvement, but it’s basically pretty much fixed as it is.
2. The debugger is a bit of a hack as it is.

It is by far my least-favorite piece of code in there.

The thing is, we don’t really have a compiler giving us debug symbols; instead what the compiler does (and always does, even in released games) is adding a special II_DBG_LINE instruction that keeps a special register up-to-date with the last line traversed.

What we do is watch for those instructions and infer everything from there, including keeping a history of the watched variables and inspecting them to see if they’ve changed.

Among other things, the original debugger does not have a watch feature, as it’s very awkward to do.

What one should do then is hack the compiler in order to have symbols and easily improve on the debugger.

Another interesting side effect of hacking the compiler would be the ability to inject code.

If nothing else, it would help to have the compiler bundle the source file with the code and/or adding a checksum, to avoid copying source files by hand (please note that ATM we have NO way of knowing if the source file matches bytecode or if it’s something completely different, at least some support for generating checksums should be useful and easily added. I’ve found myself wondering what the hell was going on more than once, only to find that my sources were outdated)

Apart from that, I guess there is room for some further polishing/refactoring anyway (it’s become slightly uglier than I’d like over time).

In particular, it would help to improve the command line and let it accept e.g. hex values.

3. Fonts. We tend to have ugly fallback fonts. Find why and fix it. Would we perhaps be interested in selectively applying bilinear on bitmapped fonts only?
  1. Also, results from profiling show three areas of interest performance wise:
  • FMVs are sometimes slower and uglier than they should be.
    Software scaling ain’t terribly efficient.
  • The particle engine can benefit from some optimization, perhaps akin to what’s been done with ui_tiled image to lessen the load on the ticket system.
    It is heavy and it plays an integral role in the speed of J.u.l.i.a.
  • Fonts, again, are sometimes at least as heavy as their ui_tiled backgrounds.
    Perhaps caching text as well, as has been done for the backgrounds, to reduce load?

Apart from this, there are a few TODO’s lingering all over the code which can be taken care of with a quick fix, most of them pre-existing, some of them new, just grep it.

What’s next

Well, looks like GSOC is coming to a conclusion.
These will be the last 10 days of it.

The actual code is basically in place and, so I’m told, not that far from mergeability.

At least one feature-branch allows for glitch-less completion of the two main games I initially set on supporting (Rosemary and Dirty Split), and I hope I can test more (there’s Carol Reed and a bunch of others).

While we are at it: Dirty Split is a FANTASTIC game and I deeply regret having to play it with a walkthrough because of time constraints.
It’s beyond awesome. Everything there is to love in a graphic adventure is there.
Do yourself a favor and check it out (and also, the Carol Reed games – also because these people are INCREDIBLY supportive).

What I’ll be doing next will be:

* Producing a profiling document (I’m midway through data gathering).
This will keep me busy until at least Sunday and will presumably help posterity in further improving performance.

* Producing a TODO-list re:features, bugs and optimizations which I haven’t been able to squeeze in yet.
I’ll most likely do this in the final day before the soft-deadline – that is, Sunday night or Monday.

Then during the last week I’ll probably be busy doing some emergency fixes and I’ll be doing some cleanup, documenting and refactoring, plus squeezing some playtesting here and there (playtesting is an excellent activity to do when you are too cooked to do anything else).

I expect documentation-writing to come last (did I mention I also write documentation like a cow?), so I’ll probably be doing that during the very last couple of days, with cleanup of the actual code and refactoring taking the first half of the week.

There’s not *that* many obvious targets for refactoring, in fact – the code is quite clean, plus some has already been done in-branch.
I still hope I’ll be able to bring it up a notch or two.

Blending

Let-s spend a few words on blending.

Blending is kind of like bringing blitting a step further.

We don’t want to just copy a pixel from source to destination, but we want to combine the source with the destination in peculiar ways.

The most obvious is transparency – we want to overlay the two colors so that you can still see an hint of the previous color.

But we may also want to perform additive blending, which is kind of what happens when you project two cones of light on the same surface.

Subtractive blending is the opposite. It’s like when you have two differently-colored light sources and two objects create two shadows. When they meet, you have subtractive blending.

WME, in addition to opaque blitting, supports “regular” alpha blending, additive blending and subtractive blending.
Each can have a color modifier as a nice added feature – basically, the input sprite gets multiplied by the colorMod, which acts kind of like a pair of colored lenses.

Of course, the original uses the ready-made blending options provided by DirectX.
We, on the other hand, must roll our own.

Here’s some examples.
For starters, subtractive blending for a single pixel.
There are two special cases which are useful optimization-wise but could be stripped away.

void BlittingTools::blendPixelSubtractive(byte *ina, byte *inr, byte *ing, byte *inb, byte *outa, byte *outr, byte *outg, byte *outb) {
    /*
     * in[argb] = input pixel, a,r,g,b channel;
     * out[argb] = output pixel, a,r,g,b channel;
     */
        // BEGIN OPTIONAL PART
        if (*ina == 0) { // Fully transparent... let's not waste time on this.
 
        } else if (*ina == 255) { // Fully opaque. Forget
            *outa = *outa;
            *outr = MAX(*outr - *inr, 0);
            *outg = MAX(*outg - *ing, 0);
            *outb = MAX(*outb - *inb, 0);
            return;
        } else {
        // END OPTIONAL PART
            *outa = *outa;
            *outb = MAX(*outb - (*inb * *ina >> 8), 0);
            *outg = MAX(*outg - (*ing * *ina >> 8), 0);
            *outr = MAX(*outr - (*inr * *ina >> 8), 0);
            // * ina >> 8 is kind of like * ina / 255, ina
            // goes from 0 - 255, so you end up multiplying by 0...1
            return;
        }
}

Another example, additive with colormod.

void BlittingTools::blendPixelAdditive(byte *ina, byte *inr, byte *ing, byte *inb, byte *outa, byte *outr, byte *outg, byte *outb, byte *ca, byte *cr, byte *cg, byte *cb) {
 
        if (*ca != 255) {
            *ina = *ina * *ca >> 8;
        }
 
        if (*ina == 0) {
            return;
        } else if (*ina == 255) {
            if (*cb != 255)
                *outb = MIN(*outb + ((*inb * *cb * *ina) >> 16), 255);
            else
                *outb = MIN(*outb + (*inb * *ina >> 8), 255);
 
            if (*cg != 255)
                *outg = MIN(*outg + ((*ing * *cg * *ina) >> 16), 255);
            else
                *outg = MIN(*outg + (*ing * *ina >> 8), 255);
 
            if (*cr != 255)
                *outr = MIN(*outr + ((*inr * *cr * *ina) >> 16), 255);
            else
                *outr = MIN(*outr + (*inr * *ina >> 8), 255);
        }
}