Wednesday, July 30, 2008

Arch-Traditionalists Rejoice!


I don't have a 320 x 200 version of it; sorry.

I've also just squashed what must be one of the more obscure Quake bugs. I have no recollection of ever seeing any references to it anywhere, ever. The server sends a blank centerstring to the client in some places (e.g. the Nailgun room in E1M1), so the fix is as simple as adding a check for

if (!str[0]) return;
to the start of SCR_CenterPrint.

Tuesday, July 29, 2008

Internal hard limits are bad

The following internal hard limits have now been removed:

  • Lightmaps.
  • Particles.
  • Textures.
  • Client-side visible entities (cl_visedicts).
  • Max alias skin size.
  • Max alias verts.
  • Max alias tris.
  • Max alias frames.
More to come.

Monday, July 28, 2008

Menus of the day!

It might not be actually a daily thing, but I like what I've done with menus, so here's a sample or two to keep things going...

A small change to texture loading

This one might make some folks unhappy...

We all know that there are beautiful texture packs out there, with 1024 x 1024 representations of textures which were originally 64 x 64. And we all download them, load them up, and marvel at the exquisite detail we're getting while running at 10 FPS. Then we either go looking for a smaller texture pack, or we make our own by shrinking these down to some more acceptable size, like 256 x 256 or 512 x 512.

Hey, there are even folks with low spec 3D cards who'd like to join in the 32-bit fun, but can't because options are limited.

MHQuake will now handle these textures differently. One of the reasons behind detecting the amount of Video RAM available is to ensure that all textures and stuff will fit in nicely. To help in this, depending on the amount of VRAM you have, MHQuake will now scale down incoming external textures to some sensible multiple of the original size. Typically, a 64 MB card will clamp textures at 2 x the original, a 128 MB card at 4 x original, and a 256 MB card at 8 x original. Only if you have 512 MB or more will you be allowed to use the full external texture size. Is you have less than 20 MB VRAM you're really out of luck, as external textures will scale all the way down to the same size as the original.

Also, if you're running in the "degraded mode" I mentioned earlier (no hardware T&L, max texture size < 512, only supporting OpenGL 1.4 with extensions, Intel card, and maybe a few other conditions to trigger it) you'll have an absolute clamp of 2 x unless it would be even lower.

The name of the game here is playability - it's one thing having a beautiful looking slideshow, but sometime you're going to want to actually play the damn thing. If you really want to gonzo-out your textures you'll have to use a different engine; I've seen enough people loading up 2048 x 2048 textures into DarkPlaces and then complaining that it's running slow to fall for THAT one...!

Sunday, July 27, 2008

The return of Joystick support!

One of the nicest things about DirectInput is that it uses largely the same code for all devices. This means that once you have the basic code done for one device, it's utterly trivial to add in the extra you need for another. One day, MHQuake may support steering wheels, Wii-style controllers, and so on, all with about 20 extra lines of code each.

Anyway, one of the first things I removed from MHQuake was Joystick support. Today I added it back in a DirectInput stylee. It took literally 10 minutes to do. I've decided not to get too fancy with this, so I'm only using the basic Joystick device, but as that supports up to 32 buttons it should be sufficient (the DIJOYSTATE2 struct supports up to 128 buttons, but unfortunately the basic Quake Input subsystem won't be happy with that!)

I don't have a Joystick to test it on however, so this should be considered as very experimental code. Once I find a nice cheap and cheerful USB device, I'll finish things off and add in the cvars. Oh yeah, and because DirectInput is a standardised interface, I can make these archive cvars too! Who knows, I might even add force feedback!

More fun and joy

I've had a great time troubleshooting the old Intel lock up problem, caused by the driver going into an infinite loop. I'd long suspected that the command buffer was overflowing, and have peppered a few calls to "glFinish" around the code, which not only fixes it, but also seems to confirm my suspicions. So now I detect if we have an Intel card on the premises at startup, and enable a "degraded mode" (i.e. lots of glFinish's all over the place) if so.

It doesn't run too bad - I can get about 20-40 FPS in most scenes on an Intel 915 in this "degraded mode", which is not too far off what the card is capable of anyway.

I've also been working on better capabilities enumeration. It's frustrated me that OpenGL doesn't have a proper capabilities API - you've got a few glGets, but they're severely limited, and in a lot of cases the only way to really find out if something is possible is to actually try it and see what happens.

Of course some might say this is preferable, that you know for certain whether something will work, and that certain vendors may lie about their capabilities when queried, but even so, there's basic stuff that not even "try it and see" covers, like whether you have hardware T&L or how much video RAM you have (although you can make a good guess at that by creating small textures until one fails...)

Anyway, I've stuck in a few Direct3D and DirectDraw calls at startup to get these capabilities. They don't need to be tied to a window, as you're only creating the objects, not the devices, but I've also added a splash window in case I ever do want to create a device for something.

One interesting side-effect of this is observing the interaction between Direct3D and DirectInput. This ties in with my observations on potential threading issues in VCPP 2005/2008 earlier on, as it now appears obvious that DirectX in a multi-threaded environment needs very careful handling otherwise it will go all over the place. The really weird thing was that the issues persisted even after the Direct3D object was released and NULL'ed. And I don't even startup DirectInput until well after that. So one would reasonably expect that this should have never happened. Anyway, I've resolved it by sending the Direct3D and DirectDraw stuff off on it's own thread, so that the processes are kept isolated from DirectInput.

The Win32 threading API is nice and neat by the way. Very easy to use, and takes up minimal lines of code. I'm sure that there's other places in the engine where performance would benefit from being able to run batches of code at the same time.

Friday, July 25, 2008

i am teh ninja c0d3rz. ph34r my sk11lz!

I've struggled a bit getting a modal messagebox working, but it's almost there now. A nice new construct for me - an array of function pointers!

 typedef void (*m_print_func_t) (int, int, char *);
m_print_func_t M_PrintFunc[2] = {M_PrintWhite, M_Print};

------------------------

M_PrintFunc[this->ActiveButton] (184, ypos + 20, "Yes");
M_PrintFunc[!this->ActiveButton] (240, ypos + 20, "No");
Just 4 lines of code, but doesn't it make your eyes bleed?

Thursday, July 24, 2008

Fun with Visual Studio 2005/2008

I've been developing on Visual Studio 2003 for now, but had intended to switch over to 2005 or 2008 at some point in time. Simply because they're nicer environments, they have some neat productivity enhancements, and they have free (as in beer) Express versions which enable anyone to dabble. Way to go, Microsoft!

One thing that had put me off was the Intellisense support. Bottom line is that (for C++ at least) it's utterly dreadful. You get a whopping big NCB file (average 10 x the 2003 size) which is too slow to parse, resulting in struct members (in particular) not popping up. I likes me some Intellisense, so this was a deal-breaker.

Today I finally twigged that the way to fix it was to shut off Class View. So I loaded up in 2008 Express, made some small changes (for loop scoping, character set, nothing dramatic), compiled and ran.

Now, before I continue, I should mention that Microsoft have removed support for the single-threaded CRT from 2005 and 2008. All applications are now multi-threaded, whether you like it or not. This could be fixed by using a different compiler, but the ability to use a differrent compiler is also removed from the Express versions.

First thing I noticed was that mouse input was all over the place. It's obviously got something important running on a separate thread now, and is updating behind the scenes. This was the case for both DirectInput and software. Fixing it would probably require rewrites of code that has already been carefully crafted and debugged. Deal breaker.

Second thing I noticed was that shutting down Quake caused a crash in PF_VarString. Further investigation revealed that this was an "exiting from a map" issue, rather than a shut down issue. I didn't spend too much time on this, as I had already had one deal breaker, but the finger of paranoia is pointing to threading issues again.

The same happened in 2005, so it looks as though I won't be releasing a 2005 or 2008 project. Too bad, but having something that works properly is far more important. You can probably work on the code in these environments, but when it comes to compilation you'll need to use an external tool. Or just get your favourite free (as in speech) IDE and use that.

Way to go, Microsoft.

Wednesday, July 23, 2008

Some small things

I've removed the hard-coded limit on the number of particles. You can now have several billion if you want, although your PC will probably crash before you get to that point.

I've had great fun with the video menu again. Bottom line was that I was trying to be too fancy with what I had, so I went back and simplified it totally, and at the same time managed to make it more flexible and player-friendly.



I'm removing the bug list from the site, primarily because I never updated it. Code changes were coming too fast, and a bug posted on there could easily have become invalid (or 10 times worse) within half an hour. Instead of that, I think a much more useful addition would be a "to-do" list. This will give you information on what I consider to be outstanding before I release.

Monday, July 21, 2008

Total Startup Change

MHQuake startup has been totally changed. This was a sensible thing that had been on my mind for a while, but now it's done. There are 4 phases to startup:

  1. Bringing on all baseline stuff. This mainly involves bringing on all Cvars and Cmds, as well as all items that are always required irrespective of the game selected.
  2. Bring on the filesystem, then exec quake.rc in a special "cvars only" mode ("exec" itself is the only command allowed) - this gets the cvars up to user-defined values, and will be the entry point when starting a new game or changing games.
  3. Bring on everything else: video, sound, input, etc. Deferring these until here will mean that MHQuake will be able to initialize them at user-defined values on first run.
  4. Do another "exec quake.rc", allowing commands as well as cvars this time. This will be a second run of the cvars, but my change-tracking code will prevent things from being shut-down and re-initialized.
I'm pleased as punch with this, as it's going to let me write dedicated startup and shutdown functions for everything, and re-run them as many times as I like with a good measure of total control.

Now I need to go back and rewrite the video startup to support this fully...!

Saturday, July 19, 2008

So you want to load a map?

Wednesday, July 16, 2008

The Joys of DirectInput

Just two things for now:

  1. The changeover to C++ has been a total eye-opener. I did promise that I wasn't going to use any wacko C++ features, but I've already got multiple inheritance in the input subsystem (the DirectInput mouse class inherits from a CDIDevice and a CBasicMouse). The big surprise is how clean the code is.

  2. DirectInput is really rather nice to use under these conditions.

Monday, July 14, 2008

Direct X Fun

Some things I've discovered:

  • You need a co-operative level of DISCL_FOREGROUND | DISCL_EXCLUSIVE or you'll still receive WM_MOUSEWHEEL messages from Windows. This was tremendous fun to debug. I bet there's some silly backwards-compatibility reason for it too, like a lot of Direct X 3 programmers relied on it.
  • If you have a function that returns a HWND, don't pass it as the first parameter to SetCooperativeLevel or interesting things will happen. Input seems to work OK, but it breaks sound.
  • Direct Input seems a nice enough API, so long as you can avoid having to pass structures around as parameters. The major exception so far is setting an input buffer size, where you have to use SetProperty.

More fun...

Here's my entire sound menu, just as a taster of what the new menu framework can do.

CMenu menu_sound ("gfx/p_option.lmp", "Sound Options", M_Menu_Options_f);

void M_Sound_f (void)
{
key_dest = key_menu;
m_state = m_sound;
m_entersound = true;
}

void M_Sound_Init (void)
{
menu_sound.AddOption (&nosound, "Sound Effects");
menu_sound.AddOption (&nomusic, "Music");
menu_sound.AddSpacer ();
menu_sound.AddOption (&volume, "Effects Volume");
menu_sound.AddOption (&bgmvolume, "Music Volume");
menu_sound.AddSpacer ();
menu_sound.AddOption (&ambient_level, "Ambient Volume");
menu_sound.AddOption (&ambient_fade, "Ambient Fade");
}

void M_Sound_Draw (void)
{
menu_sound.Draw ();
}

void M_Sound_Key (int k)
{
menu_sound.Key (k);
}
Nice, isn't it?

I've decided to keep the _f, _Draw and _Key functions separate rather than adding them into the class, as for some menus there's a little bit of pre and post setup required. Of course, I could just put them in as callbacks, but that would complicate the _Init function a bit more. Plus, it makes for better code legibility this way, as it's easier to follow the execution path.

I've done a total reworking of video startup and video mode handling. The registry is now no longer used for storing the initial video mode; instead it's good ol' config.cfg. That's the least of the changes - gl_vidnt.c is totally unrecognisable, and the copy-and-paste folks won't be happy with what I've done here... (evil grin)

Saturday, July 12, 2008

The Great Changeover

MHQuake is now a C++ program. I changed over today, and have made the necessary amendments to the codebase to play nicely with C++. There are no classes (aside from my menu framework pseudo class), just C++ language written in a procedural manner.

Why did I do this? The main reasons are:

  1. Support for C in a lot of Microsoft libraries is weak.
  2. I wanted to be able to use function overloading (I've already come across a few cases where I needed it and have had to resolve things using function pointers instead).
  3. I wanted to clean up the DirectSound mess.
  4. I wanted to switch over the keyboard input to DirectInput.
  5. I wanted to have the advantage of the stricter type checking.
  6. I wanted to be able to use modern libraries like GDI+ for image loading, rather than having to rely on 10 year old unmaintained libraries like libjpeg.
  7. I wanted the advantage of constructors and destructors rather than having to keep track of things in global variables.
  8. I wanted to get rid of all cases where I had mixed C and C++ code in the existing codebase.
I won't be using the full gamut of el-bizarro C++ "features", and I won't be doing all of the above at once, but there are definitely valid reasons why this switchover will ultimately produce cleaner and more robust code.

Friday, July 11, 2008

Status Update

MHQuake marches on. I've extended my embryonic pseudo-generic menu framework to be truly generic (it now - rather strongly - resembles a C++ class, even to the extent of passing a "this" pointer as the first function parameter), and have put it into use in 5 menus: Options Top Level, Video, Sound, Input and Effects. I've got support for slider-cvars (any range, inverse range handling), toggle-cvars (inverse toggle handling), spacers and commands (which can be a call to another menu or a function that does something). There's a real danger of it becoming a controls library, especially when I add in string-cvar/textbox support, but it's been enormous fun to write, and a pleasure to see it up and running.

I'll probably move most of the standard menus over to it, although there are some where the menu setup is sufficiently wacko to make it probably not worth the effort.

Fixing the underwater luma bug involved yet another rewrite of the surface refresh. I think I must be into double-figures for the number of rewrites I've done in this engine alone. This time I think I like what I've got - it's simple, powerful and flexible.

I've also begun a new memory management system (for the second time in this engine) and am in the process of moving memory allocation over to it. This one's good, as it is capable of handling dynamic memory expansion and contraction (in 4 MB chunks), so there's no longer any need to use -heapsize. Even the most onerous memory requirements are supported, and there is no hard upper limit (aside from the amount of memory in your PC). Behind the scenes it works like a combination of the old Hunk system and of true dynamic memory.

It's weird, but I definitely get more enjoyment from working on "unglamorous backend stuff" like this. It also has the bonus that success or failure is down to my own abilities and imagination, rather than API/drivers/file format limitations.

In other news, I've switched broadband providers. My previous (who will remain nameless - how very tactful of me) had been extravagantly useless for quite some time, and their latest bout of p!ssing around was the final straw. Old account cancelled, new account set up, and their customer service drones told a thing or two about reality. My only regret was not doing it ages ago. It's a buyers market, so I definitely recommend not putting up with bad treatment.

Tuesday, July 8, 2008

More occlusion culling

I've updated my entity occlusion culling code to operate more in software. It already does regular entities in software on the server side, but now it's also handling static entities in software on the client side (using much the same algorithm). It's occurred to me that now that I already have everything doing cull checks against the world in software, I only need to do hardware cull checks if inline brush models are being drawn. This significantly speeds up the whole process as it means that generating and reading back a Z buffer need not be done all of the time.

I may even be able to further speed this up by projecting inline brush model bounding boxes to screen coords and checking if there is any possibility of them occluding first, but that's not too high on the priority list right now.

So now that I have this working fine without hardware occlusion queries being available, the next logical step seems to be to replicate it for cases where hardware occlusion queries are available. However, in tests, running hardware occlusion queries involves an explicit sync/wait, which is actually significantly slower than my method.

Monday, July 7, 2008

Old Status Bar Reversion

I've added the necessary option to revert to the old status bar (cl_oldsbar 1 and a menu option). This isn't a total reversion, as there are still some elements of my new HUD present - namely the mini deathmatch overlay and the scoreboards. I feel that these are justified as the classic Q1 versions involved adjusting screen elements and overwriting useful information. But the bulk of it (health/ammo/armour/inventory) can now revert cleanly.

Arch traditionalists should note that I didn't bother with adding alpha to the sbar and ibar background images; they draw solid, just like in the good ol' days of 320 x 200 DOS Quake.

I've also added a bug list to this page. It's pretty sketchy for now, but it will expand to contain lists of bugs as I come across them (and will hopefully shrink as I fix them!) This is the best place to have this for me, as I can quickly and easily make use of it, and also give useful updates on current status without having to write a mini-essay.

Thursday, July 3, 2008

Fun with menus

Fancying some easy brain-dead work, I decided to add in a "Special Effects" menu with options to switch some enhanced options on or off...

...and I ended up with a fairly good chunk of a generic menu framework that allows for easy addition of menu options (2 lines of code max, no extra code required to either display or adjust) and supports both checkbox or slider options straight away. This kind of thing tends to happen, and I apologise for it.

Some Q&A:

Q: "Where can I get MHQuake?"
A: "You can't - yet. It hasn't been released."

Q: "When will it be released?"
A: "When it's done."

Q: "When will it be done?"
A: "See answer to question 2."

Sorry, but I can't give definite timescales for anything now. I know in my own head what the main items I need to finish up are, but there's a whole list of minor items that need implementing too, and I just do not want a substandard release. One person has been given an earlier cut of the source code, but things have changed quite a bit since then.

If I was to sit down and do nothing but this for a week, I would probably bring it into a releasable state, but Real Life keeps me busy enough with other things. I have a job, I have friends, I have a family, I have other hobbies, I like to socialise, stuff like that. There's also the risk of burning myself out on it, in which case you'd get a half-hearted release but no follow-up. This has happened before, so trust me, it's better this way.

Wednesday, July 2, 2008

Latest Update

I warned you all this might happen...

I've gone back and worked more on my torch coronas/depth testing code. Identified a few bugs in it, cleaned up some items, and made it more compatible with variable resolution and FOV settings. I'm fairly satisfied that the implementation I have is quite solid now, the sole remaining item being to fade out coronas as they become occluded, rather than suddenly making them invisible. This will handle cases where a corona would otherwise pop in and out of visibility. It's a tradeoff between a corona fading when it would otherwise be occluded (which I can explain away as "afterglow" anyway) versus major ugliness.

While working on this, I discovered an interesting "feature" of glDrawPixels. The spec allows for any size, but (at least on my GeForce) it barfs badly if you don't use a size that's a multiple of 8.

One more not-so-nice thing is that ATI's drivers seem to be unhappy reading GL_DEPTH_COMPONENT data with a format of GL_FLOAT. Just Google it for many tales of woe. Unfortunately I'm using GL_FLOAT, so my options are:

  • Switch to a format that ATI supports.
  • Switch to Direct 3D and get rid of all ATI weirdness once and for all.
  • Include an "ATI compatibility mode" which hard-disables anything ATI has trouble with (overrideable on the command line, in case they ever fix their rubbish drivers).
Sorry to admit that I'm leaning towards the third of these, and I recommend that anyone working on any OpenGL app does the same. If people keep on implementing workarounds for ATI vileness, they will never fix things, and the situation will never improve. It's in the interest of everybody to get good drivers, and anything that prolongs the current mess is a Bad Thing. How they ever pass the conformance tests, I just cannot understand.

Anyway, moving on...

I've also added some motion blur, occurring when you're damaged (like on Doom 3), dead (I want death to look like it's something truly awful in the Quake world) or underwater (looks nifty and is a good replacement for underwater warping).

I need to make all these effects cvar controllable and add menu options for them, by the way. I also want to add a menu option for reverting to the classic HUD and particles, for all the arch-traditionalists out there.

Did I mention that I removed the BSP texture mipmap level 0-3 reading? It turned out that it had an abnormally high level of isotropy - so high as to be unacceptable.

A bug in the surface refresh has popped up, so I need to revisit that - again!

Hey, you can have this engine now or you can have it the right way......