Showing newest 51 of 55 posts from December 2008. Show older posts
Showing newest 51 of 55 posts from December 2008. Show older posts

Wednesday, December 31, 2008

About time too!


Although I have become rather fond of the old monochrome lighting over the past few weeks.

Other news: checked out XAudio a bit more and realised that it appears to be a very thin wrapper around DirectSound (you could probably just #define everything and make it look identical). I can understand Mictosoft pushing for it's use, but making setup and operation virtually identical to DirectSound except for changing the names around DOES NOT WIN ME OVER. Still no support for loading media files in the API either. 1 out of 10, must try harder.

I wrote the texture flushing mechanism, so video RAM headroom is going to be back in the next release. It's scary when you think that a 2048 x 2048 RGBA texture can take up to 16 MB! I might load really large (think over half max size) textures as 16 bit - I'm already doing it with water warp updates, so why not? Texture compression is also an option, although as I lock the miplevels to generate mipmaps it might be tricky. D3DXFilterTexture is another option here.

More map testing; I totally choke on WARPC. Not surprised either, that map even exceeds the limits of the standard Q1 protocol (over 256 models, net buffer sizes in excess of 8192), so it's not really a fair test. Give me an engine killing map that stays within Protocol 15 bounds and then we'll do a real test. The warpspasm start map is fair enough though, it did help me find a bug I had in R_TranslatePlayerSkin.

Some strange bugs in; crashing on screenshots, HOM effects, etc. I don't really know what to think; I can't reproduce them so I don't even know where to go looking for a fix. It might help if some people grabbed the source code and ran a debug build, although in fairness I suppose that having to download and install Visual Studio (2008 Express) and the DirectX SDK might be a bit off-putting.

All for now.

Tuesday, December 30, 2008

XAudio and XInput

These are interesting APIs. I've already done some XInput code for the XBox 360 controller, which will probably make it into the next release, but I'm curious about XAudio. What I read about it indicates that it's just a layer on top of DirectSound, rather than something that talks directly to the HAL, but DirectSound is a bit ancient and creaky these days, so if this still manages to do things more efficiently it'll have to be taken seriously as an option.

So I'm getting up to speed with the API right now, just doing a basic "play a WAV file" application to see how much I like it. I've already moved the sound code on to DirectSound 8 since release 1.2, and have cleaned out a lot of the old weirdness that was in there (especially in the initialization), but I'm seriously curious about this one.

First bugs are in, and 1.3 is underway

Two definite bugs so far: r_wateralpha is broken on some cards, and it seems I also broke mapshots right at the very end while cleaning up the viewport code. For now, and until 1.3 comes out, a mapshot will be confined to a 256x256 region of the top-left of your screen.

Some small changes for 1.3 to begin with; the cvar protection system I talked about a while back is in. Mod developers may not like the idea, but the whole purpose of it is to protect user's settings from abuse by QC. How would you like it if a mod jumped all over your input settings, or decided to play with some rendering cvars you had set for performance reasons?

I'm not out to break mods, so I give QC it's own fake copy of the cvar to play with all it wants. The whole system can be overridden by setting "cvar_allowqc 1" (QC can never play with that one).

The following cvars are protected:

  • _snd_mixahead
  • b_gamma
  • bgmvolume
  • cl_bob
  • cl_bobcycle
  • cl_bobup
  • cl_crossx
  • cl_crossy
  • con_lineheight
  • crosshair
  • ds_musicdir
  • g_gamma
  • gl_clear
  • gl_conscale
  • gl_cshiftpercent
  • gl_finish
  • gl_flashblend
  • gl_texturedir
  • host_savedir
  • in_joystick
  • lookspring
  • lookstrafe
  • m_boost
  • m_filter
  • m_forward
  • m_look
  • m_pitch
  • m_side
  • m_yaw
  • r_drawentities
  • r_drawviewmodel
  • r_dynamic
  • r_gamma
  • r_lerpframe
  • r_lerplightstyle
  • r_lerporient
  • r_lightupdatefrequency
  • r_lockpvs
  • r_norefresh
  • r_novis
  • r_shadows
  • r_speeds
  • r_wateralpha
  • scr_ofsx
  • scr_ofsy
  • scr_ofsz
  • scr_screenshotdir
  • scr_screenshotformat
  • sensitivity
  • sound_nominal_clip_dist
  • v_centermove
  • v_centerspeed
  • v_gamma
  • v_idlescale
  • v_ipitch_cycle
  • v_ipitch_level
  • v_iroll_cycle
  • v_iroll_level
  • v_iyaw_cycle
  • v_iyaw_level
  • v_kickpitch
  • v_kickroll
  • v_kicktime
  • vid_mode
  • volume
A somewhat arbitrary list, based on what I thought seemed right at the time. These are cvars that I think are designed for users of the engine to fine-tune their own gameplay experience with, or for map developers to use as aids; not for mod developers to party on.

No doubt the list will change as time goes on.

Monday, December 29, 2008

1.2 Released

Everything went better than expected, so I'm putting this out now before I start getting stuck into anything new again; who knows how long it would take if that happened.

It's a long list of changes, sorry about that. I'll try not to let it happen again.

  • Incorporated changes from 1.1 Emergency Releases.
  • Fixed bug where a spawned backpack could sometimes inherit the angles of the dead player/monster.
  • Added positional interpolation (r_lerporient, default 1).
  • Added frame interpolation (r_lerpframe, default 1).
  • Ported menu framework code.
  • Moved Options menu to framework.
  • Moved Main menu to framework.
  • Changed clear colour to black now that I no longer need to check for clearing.
  • Moved Single Player menu to framework.
  • Fixed centerprints so that positioning is consistent.
  • Fixed SCR_CenterPrint empty string bug.
  • Moved Help menu to framework.
  • Changed help menu so that it starts at page 1 if playing the registered version.
  • Reworked Quit menu as a SCR_ModalMessage.
  • Added last line of console output at bottom of screen for when menus are active (user feedback).
  • Fixed occasional bug where Windows keeps a file open lock on config.cfg even after it's been closed.
  • Fixed chase camera so it can never go inside (or through) a solid leaf.
  • Adjusted number of save games to infinite; removed name restrictions.
  • Moved save/load menus to framework.
  • Fixed all remaining chasecam clipping/noclipping problems.
  • Changed default m_boost value to 1.
  • Changed appearance of SCR_ModalMessage messages slightly (added "Confirm" header and "[Y/N]" prompt).
  • Fixed occasional DirectInput lockups/stalls.
  • Removed "no multi-threaded" restrictions from Direct3D startup.
  • Changed alias model bounding box cull and chasecam clip tests to per-frame bounding boxes.
  • Removed old sky warp and r_oldsky cvar.
  • Added Q2/etc style noclipping (was this in 1.1?)
  • Changed speed and direction of new sky warp to more accurately mimic old sky warp.
  • Removed surface subdivision from sky.
  • Removed brush model specific stuff from model_t struct.
  • Added struct header pointers for each model type to model_t struct.
  • Massive cleanup, restructuring and explanations in submodel setup.
  • Added fading of chase model when chasecam gets near it owing to geometry clipping.
  • Added translucent viewmodel when one has the ring.
  • Added translucent chase model when viewed from nearby.
  • Switched mipmap chain generation to manual as automatic doesn't work too well on some cards.
  • Restored HARDWARE_VERTEX_PROCESSING (need to test for lockups on NVIDIA).
  • Changed default scr_conspeed to 3000 and scr_printspeed to 20 (from 300 and 8).
  • Restored lightmap texture rectchange system (easier in D3D as top is 0 and it uses r and b, not w and h).
  • Left lightmap textures locked for the entire map (to save locking/unlocking per frame).
  • Removed glpoly_t from gl_model.h to gl_warp.cpp as that's the only place it's used any more.
  • Gave water and sky their own custom vertex structs.
  • Moved Multiplayer menu to framework.
  • Commenced moving Setup menu to framework.
  • Wrote a *real* textbox for "Hostname" and "Your Name".
  • Removed display of Help page 0 ("Ordering") from the registered version.
  • Fixed bug where the save menu would crash if there were no save games but was otherwise valid.
  • Removed IPX Code.
  • Restored r_wateralpha functionality (mimics stock GLQuake, no fancy changes).
  • Cvar-ized light animation interpolation (r_lerplightstyle, default 1).
  • Restored gl_flashblend 1 mode and made gl_flashbend an archive cvar (retained 0 as new default).
  • Added r_lightupdatefrequency cvar to control how many times per second lightmaps update (default 0 = always).
  • Added CDAudio_Stop to S_StopAllSounds.
  • Added DirectShow code for media playback for when no CD is present.
  • Added render to texture code for water warp updates.
  • Removed surface subdivision from water surfaces.
  • Replaced gl_warp_sin.h with internally generated sin table.
  • Finished render to texture for water surfs (we lose a few frames here but the improvement is worth it).
  • Expanded BSP light data to 3 component.
  • Removed interpolation from muzzleflashes on viewmodels.
  • Moved keybindings menu to framework.
  • Fixed FPS loss with render to texture.
  • Finished Multiplayer Setup Menu.
  • Switched cvars to static storage of name and string (code simplification).
  • Added more generally usable "Draw_TextBox" routine.
  • Fixed bug in Save/Load menu where this->CurrentOption == NULL would crash on an unhandled key.
  • Added sound/cdtracks as an optional music directory (first preference, fallback on music if not present).
  • Allowed spaces or non-alphanumeric characters in save/load names.
  • Added detection of invalid windows filename chars in save/load names.
  • Added con_lineheight cvar to control height of console lines (default 8, same as classic Quake).
  • Added external texture loader (not linked in to internal loader yet).
  • Added mapshot drawing system.
  • Added external BSP texture support: link, dds, tga, bmp, png and jpg formats supported.
  • Adjusted default vid_gamma to 1.0 (consistency between external and native textures).
  • Added scr_screenshotformat cvar (default tga), supports tga, bmp, png, jpg and dds.
  • Finished mapshot writing.
  • Cvar-ized screenshot directory (scr_screenshotdir, default "screenshot") - not as robust as it could be, don't try anything fancy!
  • Cvar-ized external textures directory (gl_texturedir, default "textures") - see comment above.
  • Cvar-ized save directory (host_savedir, default "save") - see comment above.
  • Added hardware gamma control - uses old v_gamma cvar, works with -gamma cmdline (which is still retained).
  • Moved TCP/IP - new game menu to framework.
  • Fixed bug where the game would crash if a menu with no options at all was entered.
  • Moved TCP/IP - join game menu to framework.
  • Fixed bug where the game would crash on a key other than ESC in a menu with no options.
  • Reverted alias models to whole model bbox cull tests (reliability).
  • Moved game options menu to framework.
  • Added deathmatch 0, coop 0 and teamplay 0 to load command.
  • Added Input Options menu.
  • Added Effects Options menu.
  • Added Chase Camera Options menu.
  • Added Content Directories menu.
  • Made cvars self-registering and cmds self-adding.
  • Added initial load of configs (setting cvars only) to get correct values in before anything else comes up.
  • Increased several internal limits to match those of FitzQuake.
  • Tested Marcher Fortress; ensured load and correct appearance.
  • Fixed textbox startup when the length of the initial string was > 16.
  • Fixed bug in textbox where holding down shift to type a capital letter would trigger the "deny" sound.
  • Removed limit on number of static entities.
  • Verified we can at least *load* cavetest2. ;D
  • Removed software gamma calculations (WinQuake legacy).
  • Added independent control of red/green/blue gamma (in addition to master control): r_gamma/g_gamma/b_gamma, defaults 1.
  • Removed scr_copytop, scr_copyeverything, scr_fullupdate, sb_updates, Sbar_Changed (software legacy).
  • Removed limit on number of particles; initial 4096, extra 2048 as required, cleared between maps.
  • Fixed unsigned short to short translations in BSP loading.
  • Fixed bug where a desktop resolution with 800 height would give a mode with 768 height as a valid windowed mode.
  • Fixed bug where the vid_mode cvar was not being set correctly (at all, actually!) on startup.
  • Fixed bug where dlights shine on backfacing surfs.
  • Added smoother transitions between r_viewleafs with dfferent contents types.
  • Moved "Search for Local Games" to framework.
  • Added support for reading vid_mode from the configs and setting the correct mode according to that.
  • Added r_lockpvs cvar for locking PVS to the current viewleaf; map development aid, default 0.
  • Fixed bug where Alt-Tabbing back to a fullscreen mode would leave the screen garbled (viewport code needs a huge cleanup!)
  • Finished porting of menus to framework.
  • Fully removed gl_ztrick; it was *not* a happy bunny.
  • Cleaned up the mess I had made of viewports.
  • Fixed loss of LOD with warp update render to texture (more FPS loss as a result).
  • Restored r_speeds 1 wpoly counter, left broken epoly counter (consistency with previous versions & other engines).
Where to next? There are some obvious unfinished items, even given the list above. External textures need to be worked over to accomodate all the wacky stuff Quake does, I still haven't added proper texture flushing, I still don't have fullbright colours, I want to do some serious work on the filesystem code, and as I think I've earned a treat I'm also going to do some input code!

Even some of the new stuff needs completing; I have the ability to display mapshots in the server list menu, but I don't do so, for example.

Plus more bugfixes as the reports come rolling in, of course!

Updates for 2008-12-29

I've decided to leave video mode changing at "You must restart directQ for this setting to take effect". It's just one of those things I'll have to go off and experiment for a while with, and I'm not happy to spend the time on it so close to my release date.

Menus appear to be pretty much done; the last of them were finished off today, and I've confirmed correct operation with some LAN testing.

I want to clean up the viewport code (it needs a good overhaul) and do some more tidying here and there, hopefully not too much, but a release very soon looks likely!

Video mode changing - AND MORE!

Currently working on video mode changing; all is going - kinda - well. This is unexplored territory for me, straight into the unknown of changing video modes under Direct3D.

Even if I don't get the full thing working, we will have the ability to select a value for "vid_mode", have it stored in config.cfg, and have it set properly on the next startup. Which I guess is something better than GLQuake had. This is already coded and working beautifully, took all of 10 minutes or so, and the best thing is that since I switched the cvars over to self-registering, I can now pre-load the configs immediately after the filesystem comes up (i.e. before video) so you won't have to contend with double-switches.

I also took the opportunity to test out 16 bit modes while doing this, happy to report that they work fine.

I have another Alt-Tab bug; right now the screen gets garbled a little after switching back (fullscreen only). I suspect that the cause is the weird way I'm handling viewports, but I intend changing them to a more standard method before release. For now the fix is to either bring down the console or bring up the menus, and all will be back to normal.

Speaking of menus, today I added a whole load more Options submenus, so you'll be able to see quite a lot of functionality exposed. The ordering of these isn't finalised, but what I have seems fine to work with for now. I really need to get it to a stage where I can just delete the old menu.cpp from the project.

I also added the "Search for Local Games" menu and started on the Server List. Tomorrow I'm going to finish that, tidy up a little, and start running LAN tests.

We're still on track for release before the 4th; if I don't get the video thing sorted I'm just going to release without it. It's still a step forward from where I was, so I'll be happy.

The size of the change log for this one is scary, by the way. You'll see the full thing when I release, but you can get an idea from the updates I've been posting. With hindsight I should have, and could have, split it over 3 releases, and worked on the menus behind the scenes, but done is done.

Sunday, December 28, 2008

Particles! And More!

No, it's not a new particle system, don't worry Trad Quake fans (although I must admit I'm quite fond of the old one).

Been doing some digging into particles while I've been removing the limit on the number of them you can have, and have come up with some interesting stats. Rounding up to the nearest multiple of 1024, what we get is:

  • demo1 has up to 4096 particles on the go at any one time.
  • demo2 has up to 2048 particles on the go at any one time.
  • demo3 has up to 3072 particles on the go at any one time.
This surprised me a little; I thought demo3 would have been the particle monster, but apparently not so. I'm also surprised to find out that 2 out of 3 of the stock ID1 demos exceed WinQuake's/GLQuake's internal particle limit.

Based on this I've revised my previous estimates and have chosen an initial allocation of 4096 particles, to be increased in batches of 2048 as required (I had assumed half of each would have been sufficient).

Particles are allocated on the Hunk and therefore cleared between maps, so if map A is a particle monster, map B won't suffer on account of it. I've written a Hunk_AllocFast to handle this, as it had always bothered me that the standard Hunk_Alloc/Hunk_AllocName did a memset to 0. Of course there are cases in Q1 where the code assumes that the data is 0 (the BSP loader is a biggie), but there are also cases where we know that an allocation is going to be filled with data immediately after, so the overhead of 2 passes through it seems excessive, particularly when it happens at runtime.

The upshot of all this is that there is a bit more particle activity in the engine than there used to be in Trad Quake (noticeable even on demo1), but it reflects the actual intentions of the particle system, unconstrained by limits.



Other news; I've been testing how well this engine copes with some of the "engine killer" maps out there. I don't have Quoth, so I haven't tried some of the more recent ones, but the two I have tried are The Marcher Fortress and CaveTest2. Happy to report that it's able to load both without any trouble. Marcher looks perfectly fine in it to me, although without the skybox it's just plain wrong (not this release, probably in the next). CaveTest2 loads, but performance is dreadful. I'm not too worried by that, It's supposed to be dreadful!

I've bumped quite a few internal limits to support this, and in many cases what I've done is just increase the size of static arrays - ugly as a box of frogs, a far more elegant solution would be dynamic allocation (up to the maximum allowed by the protocol in cases where something needs to cross a client/server boundary, or throw the protocol out the window in SP games), but that will have to come later.

Tests and more!

The problem with NVIDIA cards and hardware T&L appears to have been resolved. I'm not entirely certain what it was that fixed it, but I'm going to try removing the "Sleep (0)" from my main game loop to see if that brings it back.

The Intel 965 is running faster than before; a few tweaks and optimisations have been of benefit there.

The Intel 910/915 has dropped about 30 FPS. I'm putting this down to my Render-to-Texture warp updates; I assume that as the card is so primitive it's not supporting this properly in hardware (assuming that it's capable of supporting anything properly in hardware, that is). I'm still getting about 70 FPS on timedemo demo1 with it, but I'd like to get those 30 back so I might yet investigate an alternative water warp strategy here.

Memory usage has gone up. There are a couple of things causing this; increasing certain internal limits for starters, expanding lightdata to 3 component on load secondly. Right now a typical ID1 map needs about 13-15 MB Hunk, as well as the regular system memory overhead. Before it was 8-10 MB Hunk, and I'm planning on moving more stuff that was previously in static arrays to the Hunk (I've already done static entities). This is still small beans compared to more modern games, but I wouldn't run this on a machine with less than 512 MB system memory (do those still exist?)

I don't have a texture flushing system in yet, but having support for external textures means that I now need one. A 128 MB card can fill up pretty fast if these are large enough.

Standards and Standardisation

Apologies for coming over a bit Jane Austen in the title.

It's bothered me for some time that we don't have any proper standardisation. This has resulted in a lot of engines going off and storing (and looking for) custom content in all sorts of weird places. Someone actively developing a new engine has a few options: (1) just copy what DarkPlaces does, (2) do it similar to how Quake II and III do it based on the assumption that people will be expecting that, (3) do what seems like a good idea at the time.

I favour (2) above, but you still can't keep everybody happy, particularly in cases where (1) is different. So I decided to let the user choose for themselves:



This is just a first cut so hence the "not very robust" warning, but so far it works fine in all normal cases. A few bugs include changing "textures" won't flush already loaded textures, handling spaces in content directories is not quite working, user choices don't actually become active until the configs are loaded, and now that I'm letting the user specify these I need to do some validation on the input. But it's like I said, a first cut.

Getting somewhere...

Progress has been good since I sorted out that old menus issue, I'm now at the stage where I have 3 items left to do - slist, join game at and video. Once I have the first 2 (in particular) done I'm going to run some LAN tests to establish correct operation of everything.

I also want to run some more tests of the renderer on a few different cards. Didn't get to check out NVIDIA today but I will tomorrow. All going well it should be released by the 4th January latest.

Why the delay? Quite simple really; this is probably the biggest update I've ever done, even counting the old GL engines from years ago. Firstly that means there's a lot of work involved, secondly it also means that there's a lot of testing and bugfixing involved. Of course I won't know all the bugs until it goes public, but at least I want to be satisfied that I've done my best to reduce them to as few as possible.

But don't panic - when I say "biggest update" I don't mean something that's suddenly going to wildly diverge from classic Quake. Most of the changes were very much behind the scenes, with menus being the only really visible one. They don't diverge too wildly either; they still use the classic font and graphics.

Now that a date is looking more likely, here's a summary of some of the main (overdue, in some cases) features that have made it in (or have made it back):

  • Interpoaltion.
  • r_wateralpha.
  • gl_flashblend 1.
  • External textures.
  • Better chase camera.
  • Improved water and sky warps (more solid and WinQuake-ey).
  • Faster dynamic lightmaps.
  • MP3 support.
  • Hardware gamma.
  • Menus.
  • Better configurability.
  • Tons of bugfixes and behind-the-scenes tweaks.
Some (to me, anyway) important stuff didn't make it yet, and likely won't make it for this release; look for it in the next!
  • r_shadows 1 improvements.
  • Fullbright colours.
  • Exposing more options in the menus.
  • Support for water and sky surfaces on ammo boxes.
  • Loads more bugfixes and behind-the-scenes tweaks!
Given the extent of this update, don't be surprised if the 5th item on that list ends up taking priority over everything else!!!

Saturday, December 27, 2008

Documentation, Microsoft! DOCUMENTATION!

IDirect3DDevice9::SetGammaRamp, cool or what?

Some parts of the documentation claim it doesn't work on Windowed displays, other parts claim that it does but affects the entire screen (it doesn't work at all windowed for me, but who knows for somebody else).

Nowhere in the documentation does it mention that you need to bit-shift left by 8 to get the correct values in, otherwise you get an all-black screen.

This is crazy. I just lost about 3 hours trying to figure out what I was doing wrong, including having to undo all sorts of nasty things I had done in the process. In the end I decided to look up the old GDI function (SetDeviceGammaRamps) that does much the same, and - lo and behold - there was the necessary info. An array of WORDS (i.e. unsigned shorts) with the ramps stored in the most significant bits. It seems obvious that the D3D function is just a wrapper around the GDI one, and also does the necessary GetDC/ReleaseDC stuff (as well as screwing up windowed mode gamma, as a bonus!)

Anyway, I'm dropping D3D gamma and using GDI. This experience has left me with a baaaaaad taste.

Engine Configurability

I'm spending a lot of time working on the new menu system for the next release, and while I could push everything else aside and just focus on that, (1) I'd get burned out on menus, and (2) the next release would have nothing else new in it. I don't think we want either of those to happen.

Nonetheless, this is an inportant update to the engine, as it allows me to expose a lot of user configurability very quickly and easily. Adding new options to the old menus was a bit messy, not difficultly so but definitely tiresomely so. It's reasonabe to suppose that a lot of other engines didn't expose their options fully for that reason. Now I can add a new option with a single line of code. I can switch the order of options around, move options from one menu to another or create new submenus very quickly and easily.

In theory it's even possible to extend it to a scriptable menu system, but that's well off in the future, if it ever does happen.

This lets me expose a lot more useful things to the casual user, and even the more experienced user doesn't have to bother going searching through cvars to find things out. It will all be there.

Naturally getting the original menus fully ported over is priority 1, so the upcoming release will only have those, and not much else (I added "Save Current Configuration" to Options).

No doubt some of the hardcore ultra-trad die-hards will be clamouring for the original menus to be brought back, but this is a point-of-no-return change. I think it's very much for the better.

External textures!

While playing around with the save/load menu, I started adding mapshots to it. It began with writing-only code to just display the console image and get positioning right, then I had a look at D3DXCreateTextureFromFileInMemoryEx and thought about reading mapshots, so I wrote a simple loader for them, and realised I had an external texture loader ready to roll.

So the next one will have external texture support for BSPs and possibly a few other things. I haven't fine-tuned support for a lot of the custom wacko stuff Quake does, but that's going to be coming in the one after (I've enough to be doing to finish this one!)



That's sourced from a TGA screenshot, by the way. It's annoyed me a little (as you'll see from some of the comments in my code) that the D3DX library contains support for reading TGAs but not for writing them, so I wrote my own writer. Throw it a filepath and a LPDIRECT3DSURFACE9 and it will spit out a TGA.

While doing that I fell prey to "wouldn't it be cool if..." and quickly added a cvar to allow the user to select different screenshot formats; tga/bmp/dds/png/jpg (scr_screenshotformat, default TGA).

What else? Well, here's the latest batch of updates:

  • Replaced gl_warp_sin.h with internally generated sin table.
  • Finished render to texture for water surfs (we lose a few frames here but the improvement is worth it).
  • Expanded BSP light data to 3 component.
  • Removed interpolation from muzzleflashes on viewmodels.
  • Moved keybindings menu to framework.
  • Fixed FPS loss with render to texture.
  • Finished Multiplayer Setup Menu.
  • Switched cvars to static storage of name and string (code simplification).
  • Added more generally usable "Draw_TextBox" routine.
  • Fixed bug in Save/Load menu where this->CurrentOption == NULL would crash on an unhandled key.
  • Added sound/cdtracks as an optional music directory (first preference, fallback on music if not present).
  • Allowed spaces or non-alphanumeric characters in save/load names.
  • Added detection of invalid windows filename chars in save/load names.
  • Added con_lineheight cvar to control height of console lines (default 8, same as classic Quake).
  • Added external texture loader (not linked in to internal loader yet).
  • Added mapshot drawing system.
  • Added external BSP texture support: link, dds, tga, bmp, png and jpg formats supported.
  • Adjusted default vid_gamma to 1.0 (consistency between external and native textures).
  • Added scr_screenshotformat cvar (default tga), supports tga, bmp, png, jpg and dds.

The list doesn't really reflect it, but I resolved a major sticking point I had with menus, namely how to read back values of modified string cvars from textboxes. While cleaning up the class structure I had made this a little too private, and didn't really want to go back, nor did I want to allow in-place editing of string cvars (if nothing else, broadcast prints for name changes would break). Now I just copy the original cvar to a dummy cvar, edit that in-place, then write back and/or do whatever needs to be done in an "Apply" custom menu function. It's a hack, but so far it works very well.

I might miss my objective of a release before the new year, but it won't be too long after it (think in days rather than anything else).

Friday, December 26, 2008

Fun with Render to Texture

Just broken the back of the performance loss with Render to Texture, we're right back up to full speed now. The secret is to not use Automatic Mipmap Generation, but instead get the surface for every mipmap level in the chain and render to that.

One would have thought that would be slower, but it's not - throw logic out the window. I can only guess that Automatic Mipmap Generation is one of those "non-features" which looks great on paper (or in a CHM file), but presumably involves a lot of moving of texture data between video memory and system memory and back. Or else the card I'm currently working on (Intel 965) sends it through a software emulation path.

Either way, go figure.

Postscript
I'd like to get some performance metrics between this method and the "official" method (automatic generation) on a Real Card before utterly committing to this. Depending on how it pans out, I might set up the engine to do this for me and select the best method at startup time.

Render to Texture Rocks!

This is probably one of the nicest parts of the D3D API, and I've just had a total pleasure setting it up and getting the basics working. Surface subdivision is now totally gone from the engine, and once I complete this (porting my old GL code for updating the texcoords is all I need now) I'll have totally cool non-breaking-up water surfaces.

Gotchas included setting up the textures correctly, and having to cover them in my device reset code (as they need to go into the default pool); I also need to test automatic mipmap generation on the Intel 910 as that seemed a little ropey back when I was using it for regular textures.

Here's the latest batch:

  • Moved Multiplayer menu to framework.
  • Commenced moving Setup menu to framework.
  • Wrote a *real* textbox for "Hostname" and "Your Name".
  • Removed display of Help page 0 ("Ordering") from the registered version.
  • Fixed bug where the save menu would crash if there were no save games but was otherwise valid.
  • Removed IPX Code.
  • Restored r_wateralpha functionality (mimics stock GLQuake, no fancy changes).
  • Cvar-ized light animation interpolation (r_lerplightstyle, default 1).
  • Restored gl_flashblend 1 mode and made gl_flashbend an archive cvar (retained 0 as new default).
  • Added r_lightupdatefrequency cvar to control how many times per second lightmaps update (default 0 = always) (not really that big a deal, but I felt it right to have it).
  • Added CDAudio_Stop to S_StopAllSounds.
  • Added DirectShow code for media playback for when no CD is present.
  • Added render to texture code for water warp updates.
  • Removed surface subdivision from water surfaces.
I hope too many people don't get too upset about the removal of IPX support. I seriously haven't even seen IPX in a real network for almost 8 years now, and TCP/IP setup is so trivial these days that the old advantages of that protocol are well gone. I intend paying the networking code a good visit sometime anyway, but that's well into the future at this stage.

Thursday, December 25, 2008

Music Wherever We Go

Music is back!

I've revisited some old DirectShow code and had a great time trying to figure out how the thing works all over again. It's pretty much in there now, but as a supplement to rather than a replacement for the original CD player. Basically if there is no CD in the drive it attempts to fall back on MP3s or whatever (I'll come to that in a bit...) before giving up.

Aside from the fact that the music files must be in a directory called "music" under your gamedir (some day I'm going to cvar-ize all of these fixed directories I've created) I've removed all limitations on the names. So you can have 10 files called whatever you want and I'll play them in order for you. Nice.

What about formats? That's where the really nice thing (one of them anyway) about DirectShow comes in. It'll play almost anything; if you can play it in Windows Media Player then this baby will play it for you too. Theoretically it can even stream audio over the internet, but I haven't added code for that (don't think I will, somehow...)

Experiences with DirectShow were mixed. I know my initial implementation from years ago was a mess, and the first shot I'd taken at it this time was getting real ugly real fast too, so I ended up wrapping all of the DirectShow stuff in a class and using that to manage my COM pointers and stuff. It was much much cleaner overall, but there are still some weird things in the API itself that had me doing some head-scratching for a bit. Never mind.

At this stage a massive shout out needs to go to http://www.flipcode.com - they have some excellent resources, containing exactly the kind of info you need to implement DirectShow in this kind of app, which really helped with getting back up to speed.

And no thanks at all to http://msdn.microsoft.com.

Happy Christmas!

Not a release as a present, but a taster of something you can expect. Guess where I'm using this code...?

void CQMenuCvarTextbox::Key (int k)
{
// get the real position of the cursor in the string
int RealTextPos = this->TextPos + this->TextStart;

switch (k)
{
case K_ENTER:
// commit modification
if (!this->Modified) break;

// don't save empty strings
if (this->TempStorage[0] == 0)
{
menu_soundlevel = m_sound_deny;
break;
}

// store back to cvar
if (this->MenuCvar == &cl_name)
{
// this is a hack as name must be changed via the "name" command so broadcasts will work
Cbuf_AddText (va ("name \"%s\"\n", this->TempStorage));
}
else Cvar_Set (this->MenuCvar, this->TempStorage);

// not modified any more
this->Modified = false;
break;

case K_INS:
// toggle insert mode
this->InsertMode = !this->InsertMode;
break;

case K_LEFTARROW:
this->TextPos--;
menu_soundlevel = m_sound_option;

if (this->TextPos < 0)
{
this->TextPos = 0;
this->TextStart--;
}

if (this->TextStart < 0)
{
this->TextStart = 0;
menu_soundlevel = m_sound_deny;
}
break;

case K_RIGHTARROW:
this->TextPos++;
menu_soundlevel = m_sound_option;

if (this->TextPos > strlen (this->TempStorage))
{
menu_soundlevel = m_sound_deny;
this->TextPos = strlen (this->TempStorage);
}

if (this->TextPos > 16)
{
this->TextPos = 16;
this->TextStart++;
}

if (this->TextStart > strlen (this->TempStorage) - 16)
{
this->TextStart = strlen (this->TempStorage) - 16;
menu_soundlevel = m_sound_deny;
}
break;

case K_HOME:
// return to start
menu_soundlevel = m_sound_option;
this->TextPos = 0;
this->TextStart = 0;
break;

case K_END:
// go to end
menu_soundlevel = m_sound_option;
this->TextPos = 16;
if (this->TextPos > strlen (this->TempStorage)) this->TextPos = strlen (this->TempStorage);

this->TextStart = strlen (this->TempStorage) - 16;
if (this->TextStart < 0) this->TextStart = 0;
break;

case K_DEL:
// prevent deletion if at end of string
if (RealTextPos >= strlen (this->TempStorage))
{
menu_soundlevel = m_sound_deny;
break;
}

// simulate the deletion by moving right then deleting before the cursor
this->Key (K_RIGHTARROW);
this->Key (K_BACKSPACE);
menu_soundlevel = m_sound_option;

break;

case K_BACKSPACE:
// prevent deletion at start of string
if (!RealTextPos)
{
menu_soundlevel = m_sound_deny;
break;
}

// delete character before cursor
menu_soundlevel = m_sound_option;
strcpy (this->ScratchPad, &this->TempStorage[RealTextPos]);
strcpy (&this->TempStorage[RealTextPos - 1], this->ScratchPad);

// fix up positioning
this->TextStart--;

if (this->TextStart < 0)
{
this->TextStart = 0;
this->TextPos--;
}

if (this->TextPos < 0) this->TextPos = 0;
break;

default:
// non alphanumeric
if (k < 32 || k > 127)
{
menu_soundlevel = m_sound_deny;
break;
}

// conservative overflow prevent
if (strlen (this->TempStorage) > 1020)
{
menu_soundlevel = m_sound_deny;
break;
}

menu_soundlevel = m_sound_option;

if (this->InsertMode)
{
// insert mode
strcpy (this->ScratchPad, &this->TempStorage[RealTextPos]);
strcpy (&this->TempStorage[RealTextPos + 1], this->ScratchPad);
this->TempStorage[RealTextPos] = k;

// move right
this->Key (K_RIGHTARROW);
}
else
{
// overwrite mode
this->TempStorage[RealTextPos] = k;

// move right
this->Key (K_RIGHTARROW);
}

break;
}

// check for modification
if (stricmp (this->TempStorage, this->MenuCvar->string))
this->Modified = true;
else this->Modified = false;
}

Wednesday, December 24, 2008

Latest batch of updates

Just doing my best to avoid the tacky wholesomeness of the festive season...

  • Fixed occasional DirectInput lockups/stalls.
  • Removed "no multi-threaded" restrictions from Direct3D startup.
  • Changed alias model bounding box cull and chasecam clip tests to per-frame bounding boxes.
  • Removed old sky warp and r_oldsky cvar.
  • Added Q2/etc style noclipping (was this in 1.1?)
  • Changed speed and direction of new sky warp to more accurately mimic old sky warp.
  • Removed surface subdivision from sky.
  • Removed brush model specific stuff from model_t struct.
  • Added struct header pointers for each model type to model_t struct.
  • Massive cleanup, restructuring and explanations in submodel setup.
  • Added fading of chase model when chasecam gets near it owing to geometry clipping.
  • Added translucent viewmodel when one has the ring.
  • Added translucent chase model when viewed from nearby.
  • Switched mipmap chain generation to manual as automatic doesn't work too well on some cards.
  • Restored HARDWARE_VERTEX_PROCESSING (need to test for lockups on NVIDIA).
  • Changed default scr_conspeed to 3000 and scr_printspeed to 20 (from 300 and 8).
  • Restored lightmap texture rectchange system (easier in D3D as top is 0 and it uses r and b, not w and h).
  • Left lightmap textures locked for the entire map (to save locking/unlocking per frame).
  • Removed glpoly_t from gl_model.h to gl_warp.cpp as that's the only place it's used any more.
  • Gave water and sky their own custom vertex structs.

On Programming Languages...

I'm a programmer by training but not by trade. This means that I'm fortunate in being able to do something I enjoy in my own time, without any external pressures, and can dip in and out as suits myself. I can also step back from the current hip trends and buzzwords (I've my own set of those to contend with anyway!) and evaluate anything and everything based on how genuinely useful it is to me (rather than on how much of a bonus it will give to the salesman who's trying to push it on me).

However, I do also get the chance to do some coding in the Day Job from time to time, but it's at my own discretion and I can freely choose my tools.

This introduction I suppose serves as a justification for what I'm going to write here, which is a bit of an analysis of those particular tools, as well as some others I've come across - occasionally in a non-programming context. Think of it as "who am I anyway and how dare I say that!"

C#/.NET Framework 2.0
For me, C# and the .NET Framework are the language and platform of choice. I manage big Windows servers in a corporate environment, at the very top-level Enterprise Admin authority, and I've seen enough fly-by-night merchants to write several best-selling fantasy trilogies. When I need a specific tool to do a specific job I normally write it myself, and these give me all of the power and flexibility I need without any (or without much, anyway!) complexity or baggage. I chose .NET 2.0 as it hits a sweet spot for me, more lightweight than 3.0/3.5 but not as primitive or crude as 1.0 or 1.1; Visual C# 2005 Express is my workhorse here. It's also great for prototyping C or C++ code; you can normally do a line-for-line translation (so long as you avoid any Framework-only classes), it's quicker and easier to work with (very good if you want to try something out without having to spend hours just getting the basics up) and you can get output to the screen very easily, so it's great for debugging algorithms.

C and C++
I'm going to be honest; I don't really see any reason to write code in C any more. If I wanted to write simple procedural code I'd still use C++ but just avoid all the OOP stuff. Being able to declare variables in the middle of code blocks is probably the one big thing that C programmers who don't need OOP are missing out on. I'm deeply suspicious of C++ programmers who seem incapable of even devising a routine to add two numbers without thinking about template-izing it and setting it up for multiple inheritance, but these are great features when used with appropriate discretion.

Visual Basic Script
For much the same reasons as C#, but where a quicker and dirtier (and admittedly blunter) tool is needed. The tool of choice for a lot of COM programming too; COM is just beautiful under Visual Basic, so simple, clean and elegant. It's also great for WMI code. This is for situations where a nutcracker would be ideal, but a sledgehammer would do do job just as well, and in a fraction of the time.

Java
I'm finishing on this because I hate it with a deep deep passion, and it will leave me with a bad feeling. The ideal of OS independence is cool for sure, but every single deployment involving anything Java-based I've ever been involved in (and I've overseen tens of thousands of deployments) has been deeply ugly and has caused havoc. I see it as a developer's plaything but unsuitable for end use; for example, a lot of the time it still requires end-users to configure their own settings. Fine if you're on a standalone machine with only a single user, but if you have more than 10 or so users it very quickly becomes Not Funny. Bring that number into the thousands and remember that there is no centralised admin, no centralised config and no centralised versioning and you have utter disaster on your hands. Been there, done that, will trade organs to avoid having to do it again.

Tuesday, December 23, 2008

Beginning DirectX Pitfalls - Number 56746574845 of an ongoing series

Something like the following should be on the very first page of every single section in the DirectX SDK, preferably prominently boxed with the word "IMPORTANT" written above it in a nice boldface font:

IMPORTANT
DirectX components will spawn separate threads of their own, even in a single-threaded application. Your application should be written to accomodate this fact, otherwise you may experience temporary locksups or stalls. Unless you are specifically designing your own thread management system, calling Sleep (0) once per frame is normally sufficient. Please refer to the documentation for the Sleep function at blahblahblahblah.
How very unlike Microsoft that would be. Firstly divulging what goes on behind the scenes, then telling what you have to do about it, then telling how to do it, then referring you to associated documentation, and all in the very same section.

Sleep (0), by the way, is your friend. It sounds like a frightening thing to put into the main loop of any game; you don't want to be sleeping while actually running do you? - but with a value of 0 the behaviour is actually quite different. Quoting MSDN:
A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.
Any application that does multi-threaded stuff should always use this, otherwise the main thread will take all of the CPU and other threads in the application will get nothing.

Monday, December 22, 2008

Updates for Release 1.2

Coming along well, here's the current list:

  • Fixed bug where a spawned backpack could sometimes inherit the angles of the dead player/monster.
  • Added positional interpolation (r_lerporient, default 1).
  • Added frame interpolation (r_lerpframe, default 1).
  • Ported menu framework code.
  • Moved Options menu to framework.
  • Moved Main menu to framework.
  • Changed clear colour to black now that I no longer need to check for clearing.
  • Moved Single Player menu to framework.
  • Fixed centerprints so that positioning is consistent.
  • Fixed SCR_CenterPrint empty string bug.
  • Moved Help menu to framework.
  • Changed help menu so that it starts at page 1 if playing the registered version.
  • Reworked Quit menu as a SCR_ModalMessage.
  • Added last line of console output at bottom of screen for when menus are active (user feedback).
  • Fixed occasional bug where Windows keeps a file open lock on config.cfg even after it's been closed.
  • Fixed chase camera so it can never go inside (or through) a solid leaf.
  • Adjusted number of save games to infinite; removed name restrictions.
  • Moved save/load menus to framework.
  • Fixed all remaining chasecam clipping/noclipping problems.
  • Changed default m_boost value to 1.
  • Changed appearance of SCR_ModalMessage messages slightly (added "Confirm" header and "[Y/N]" prompt).
I'm not likely to have a release in time for Christmas, but I am likely to have one before the new year.

Sunday, December 21, 2008

Menus!

The new menu framework I worte earlier on this year and talked a lot about at the time is going to finally put in a public appearance in the next release. I've been sitting on this for a long time, so it's well overdue.

I had ported a part of it to the interim working engine I had earlier on this week, and now I'm bringing it across for real, taking the opportunity to tidy it up even further.

What this means is that menus will no longer have the "traditional Quake" look and feel; but on reflection, the old menus were very clunky and awkward; obviously thrown together very quickly and a bit sloppy.

They were awkward to work with as a developer too, even the simple task of adding a new item to the options menu was a bit messy (especially if you wanted to drop it in the middle of the list). This is going to let me easily and quickly expose many options to players, often with just a single line of code (adding a cvar-based option).

Right now I'm stuck in the middle of scrollboxes; messy to code but something that only needs to be done once, thankfully.

The initial objective is to get the original menus ported across before I add anything new, so don't be holding out hopes for maps/demos/games/etc just yet!

Version 1.1 Emergency Refresh

The previous release was not happy with Hardware T&L cards, so I've temporarily removed hardware T&L from the startup options. Should work fine now.

Version 1.1 Point Release Available

This has quite a lot of new and/or fixed stuff in it, so enjoy! More to come.

  • Renamed to "DirectQ" to avoid confusion over the name.
  • Reverted to Visual C++ 2003 to (hopefully) prevent PF_VarString crash on changelevel.
  • Fixed crash in video menu when more than 27 modes are available.
  • Removed all "ChangeDisplaySettings" calls to fix crash when Alt-Tabbing.
  • Added splash screen so that you know which version you have.
  • Fixed crash on dedicated server startup.
  • Made DirectInput the default, use -nodinput to disable.
  • Rewrote DirectInput code to use C++ style.
  • Rewrote mouse code to use immediate mode rather than buffered mode.
  • Added mousewheel support to DirectInput.
  • Added 8-button mouse support to DirectInput.
  • Added m_look cvar to give mouse looking.
  • Changed m_forward default to 0.
  • Added m_boost cvar to give additional boost to DirectInput without affecting sensitivity value.
  • Renamed K_AUX buttons to K_JOY (it's what they are, after all).
  • Added 4 K_POV buttons to keep POV hats separate from the joystick buttons.
  • Added previous weapon option to keys menu.
  • Removed _windowed_mouse cvar.
  • Added "colour" command (same as "color").
  • Fixed team colours are greyscale.
  • Adjusted all projections to the correct aspect for the selected resolution.
  • Added scalable 2D widget drawing (gl_conscale, 0 to 1).
  • Fixed widescreen FOV.
  • Removed alias models from the caching system.
  • Fixed remaining Alt-Tab crashes.
  • Added "vid_restart" command.
  • Cleaned out a lot of legacy code from draw, surf and vidnt.
  • Fixed window centering so it works properly on widescreen monitors.
  • Cleaned out unused header files.
  • Increased MINIMUM_WIN_MEMORY to 16 MB and MAXIMUM_WIN_MEMORY to 64 MB.
  • Fixed water and sky surfaces on inline brush models not being rendered.
  • Removed original Quake (all versions) crash when health goes below -19.
  • Changed mode enumeration to be more WinQuake-like.
  • Added Z-fail sky sphere warp (r_oldsky 0).
  • Restored gl_clear functionality.
Version 1.2 is already underway; this one will have interpolation, (hopefully) fullbrights and (hopefully) fixed water warps.

Saturday, December 20, 2008

Adventures with alias models

Lost a lot more time messing around with alias models, but it was interesting and I did learn a few things from it, so it's not a total loss.

First I decided to implement Direct3D lighting on them, which was great fun as the SDK and online examples were completely impractical for this. There's also misleading information; light colours go from 0 to 255, not 0 to 1. That threw me off for a long time, trying to figure why my models were rendering all black.

Anyway, I got a good result in terms of accuracy to the original, and it was much faster than Quake's software emulation, but it ended up looking very tacky and plasticky so I rolled back.

Secondly I started messing with the in-memory format; I've been badly unsatisfied with this for a long time, so I wanted to make a change that would enable cleaner passes through it. I ended up spending too much time on it though, so in the end I decided to put it off and rolled back again.

Right now I'm getting down to what I should have done a long time ago; sky and water surfs on brush models. I think I'm going to have a pop at doing them on ammo boxes too.

The joys of device loss

So far so good with handling it; I lost an awful lot of time of trying to figure why my call to Device->Reset wasn't working until I went back to the SDK documentation and realised that I had some state blocks lying around that I wasn't releasing before (and recreating after). I don't need to release/recreate anything else as I had thought ahead and put it all in managed memory from the outset.

Now at least an Alt-Tab back event will restore the main window properly without crashing, but textures seem to get a bit messed up. The alpha channels are OK, but the RGB channels all go to black. (UPDATE - all OK, I just had some missing state on my texture stages).

If this seems like an awful lot of work for something that could be handled easily by just creating a frameless window, setting Windowed to TRUE in the present parameters always, and relying on good old ChangeDisplaySettings instead of letting Direct3D handle mode changes, well it is. But a fullscreen Direct3D device is almost twice as fast as a windowed one at the same resolution, so it's well worth the extra effort.

Friday, December 19, 2008

The Alt-Tab Problem

Here we hit another classic example of things that probably bedevil everybody starting out with Direct3D. I'll quote from MSDN:

"By design, the full set of scenarios that can cause a device to become lost is not specified. Some typical examples include loss of focus, such as when the user presses ALT+TAB or when a system dialog is initialized."
However, while they certainly do provide a comprehensive enough set of warnings that you must recover from a lost device, nowhere can I see any simple step-by-step instructions for doing so. Instead you're largely left to jump around the documentation frantically searching for the correct methods to use.

So far as I can determine, the correct procedure seems to be:
  • Keep all of your resources in managed memory; this gives you more memory overhead but it saves a lot of pain later on.
  • Check for a D3DERR_DEVICELOST return from Present ().
  • Suspend all rendering for the remainder of the operation.
  • Go into an infinite loop and test Device->TestCooperativeLevel () until you get either D3DERR_DEVICENOTRESET (you're good to go again) or D3DERR_DRIVERINTERNALERROR (time to die). It might be an idea to Sleep (10) after each test so that you don't tie up the CPU.
  • Reset the device using Device->Reset (). You'll need to specify new D3DPRESENT_PARAMETERS to do this.
  • Go into another infinite loop and test Device->TestCooperativeLevel () until you get either D3D_OK (you're good to go again) or D3DERR_DRIVERINTERNALERROR (time to die). It might be an idea to Sleep (10) after each test so that you don't tie up the CPU.
  • Recreate any resources that were not originally created in managed memory.
That seems like a mightily ugly way of going about something that the API should be able to handle automatically!

DirectQ 1.1 - Point Release Status

I've started working on a "point release" for my previous release, and have decided that this will become the new codebase. The other work during this week isn't wasted, as it can all be ported eventually. But I think it's important to focus more on completing the basic functionality and bugfixes, and to move forward with that engine, rather than to have a mess of two concurrent codebases with different levels of fixes and functionality, one of which seems to be moving away from trad Quake in certain areas, the other of which is trad Quake with a lot of the stuff you wish wasn't broken being fixed.

I've added in quite a bit so far, so here's the list:

  • Renamed to "DirectQ" to avoid confusion over the name.
  • Reverted to Visual C++ 2003 to (hopefully) prevent PF_VarString crash on changelevel.
  • Fixed crash in video menu when more than 27 modes are available.
  • Removed all "ChangeDisplaySettings" calls to fix crash when Alt-Tabbing (kinda didn't work - see below).
  • Added splash screen so that you know which version you have.
  • Fixed crash on dedicated server startup.
  • Made DirectInput the default, use -nodinput to disable.
  • Rewrote DirectInput code to use C++ style.
  • Rewrote mouse code to use immediate mode rather than buffered mode.
  • Added mousewheel support to DirectInput.
  • Added 8-button mouse support to DirectInput.
  • Added m_look cvar to give mouse looking (default 1).
  • Changed m_forward default to 0.
  • Added m_boost cvar to give additional boost to DirectInput without affecting sensitivity value (default 1.5).
  • Renamed K_AUX buttons to K_JOY (it's what they are for, after all).
  • Added 4 K_POV buttons to keep POV hats separate from the joystick buttons.
  • Added previous weapon option to keys menu.
  • Removed _windowed_mouse cvar.
  • Added "colour" command (same as "color").
  • Fixed team colours are greyscale.
  • Adjusted all projections to the correct aspect for the selected resolution.
  • Added scalable 2D widget drawing (gl_conscale, 0 to 1).
  • Fixed widescreen FOV.
As you might guess, I wasn't joking about enjoying writing input code!

I have a serious bug when Alt-Tabbing fullscreen modes, which is annoying me a little. Right now it doesn't crash (I kinda wish it would, as I could at least examine what's going on in the debugger) but instead forces the desktop to the top of the Z-order when you Alt-Tab back (in front of every other window), and keeps it there even if you try to Alt-Tab away again - no way out!!!

I more than half suspect that this is either a HWND gone bad or something inherited from legacy gl_vidnt.c code, and that a lot of the old Alt-Tab code that was needed under OpenGL just ain't necessary with Direct3D, so I need to investigate correct ways of implementing Alt-Tab functionality under Direct3D.

Thursday, December 18, 2008

Status Update

Here's what I'm currently working on and/or planning:

  • Porting my menu framework from the GL engine. This is going well enough; I have most of the basic functionality across, and some of the actual menus implemented in it. It's basically a set of C++ classes that function as a control library for all common Quake menu items, which can have custom events hooked in as well as default handling for sliders, toggles, commands, submenus, etc. I'm taking the opportunity to clean up and streamline the class structure while I'm porting, so it's going a bit slower than simple copy and paste.

  • Working heavily on input code. I've completely rewritten the mouse DirecdtInput code to be more stable and predictable, and have removed the old software support. I'll be doing the same with keyboards and other controllers (most of joysticks are already written). I've also got full support for the XBox 360 controller using XInput. See my previous posts on input for more.

  • Reworking parts of the surface refresh. I currently have all surfs in a single VertexBuffer which I lock and update each frame where required, but this is far from an optimal approach. I'm going to be moving towards each model having it's own VertexBuffer, which can be then more easily transformed without having to touch any verts. That brings me to the next part, which is...

  • Designing a new alias model in-memory representation. Actually the design is already done, and some proof-of-concept code has been written. This is necessary because the old in-memory representation (order, poseverts, commands, etc) may have been fine for single passes and immediate mode drawing, and was definitely memory-efficient, but it's horrible to use for more modern techniques (this would be true of OpenGL VBOs as well).
A lot of this is already mostly done, so there shouldn't be too much in the way of delays on a new version, but completing it will require a bit longer than a few days.

Until then, I might update the previous release with some critical bugfixes that have made it into this version. Current bugs (that I'm aware of) and their status are:
  • Video menu crashes.
    This was caused by me overflowing an array; I basically just reused the old GLQuake video menu code but wrote the Direct3D modes into the menu, without bounds-checking the items array. Basic mistake.
  • Team colors are greyscale.
    Draw_Fill was reading it's palette colours incorrectly; this has been updated to read correctly and also to standardise on use of d_8to24table rather than host_basepal.

  • Crashes on changelevel in multiplayer.
    This is caused by the PF_VarString function emitting the " exited the level" text, and seems to only happen with executables built under Visual C++ 2005 or 2008 (I built on 2005). I know that Microsoft have made changes under the bonnet to the CRT in these versions, some of which are pretty drastic (such as removing single-threaded support), and this is where I'm pointing the finger. Unfortunately I'm not able to reproduce it, so it seems to have been resolved by something on my machine. Currently debating options for fixing this, but the most likely approach will be a reversion to Visual Studio 2003.

  • Alt-Tab Crash.
    This is caused by me forgetting to remove a few ChangeDisplaySettings calls. There's a related bug where Alt-Tab back to a fullscreen mode doesn't restore the screen properly at all, so for now the workaround is not to Alt-Tab.

  • Sky and water surfaces in brush models not being rendered.
    This one is currently under consideration. It's important to have it for mod support (it's possible to have these on even an ammo box in stock GLQuake!), but it will complicate the surface refresh quite a bit. The most likely approach will involve transforming them in software at runtime and adding them to texturechains, but I'm not sure how to best approach instanced models (i.e. ammo boxes). I can definitely see why ID moved away from using BSPs for these in subsequent engines. In other words, I don't see it being done for instanced models any time soon, but it will be done for inline models.
So anyway, a more basic update incorporating these fixes might be forthcoming sooner rather than later, but if it does happen it should be viewed as a separate evolution of the previous codebase rather than anyway representative of the current codebase, which has moved significantly on in other areas.

The last remaining item is the matter of the name. It has rightly been pointed out to me that there is already a D3DQuake, and although my version might be viewed as more entitled to the name, on account of being active and a native port, that version correctly has the right of precedence. Inside3D seem to have dubbed it DirectQ, which is fine by me, if a little bland. I want to move away from "MHQuake"; there's too many engines with that name already, and I've been guilty of confusing matters by not really differentiating as well as I should between my own releases. XQuake would also be appropriate, but that's already taken too. The interim release will likely be "DirectQ 1.1", but the next full release may be something entirely different.

Tuesday, December 16, 2008

Change Log - 2008-12-16

  • Added generic joystick code: buttons and axes.
  • Removed force-feedback effects from engine (they really belong in QC).
  • Fixed greyscale team colours.
  • Commenced porting of menu framework code from GL engine.
  • Added joystick/gamepad controller menu.

On DirectInput

I'm one of those strange people that enjoys writing input code. Stop laughing at the back; it's a cliché but input is certainly one of the most important and overlooked aspects of any game. I've had many great games ruined on me by having awkward controls and no (or limited) configuration options available. If I had €5 for every time I've snarled "if only that button were there instead of here", I guess I'd have enough to bribe the programmers to go back and rewrite it different.

A while back I started learning the DirectInput API. Now, I know that DirectInput is virtually deprecated, that the recommendation is to use standard messaging instead, and that there will be a push to the new XInput API, but DirectInput is a very very nice API. Easy and quick to get started with, but lots of power under the bonnet. Plus it's been around for long enough to have become a standard, and no matter how weird or wacky a device is, it's most likely going to be supported.

So I'm moving all of the input code to DirectInput, for a number of reasons. As I said, I like the API a lot, but also because it means that I can handle input in a very consistent manner for all devices, which makes my job easier, and which means I have more time to work on the good stuff.

The only exception is the XBox 360 Controller which uses XInput, as it's capabilities are crippled under DirectInput.

Me, I'm a keyboard and mouse person, I could never get the hang of using a controller or joystick, but it's important to be able to support how other people like to run the game.

Monday, December 15, 2008

User Config Protection

We have a lot of engines but only one config.cfg file, which engines gleefully stomp all over. You run engine A, it writes it's own config, you run engine B, it writes it's own, and so on; and you need to go back in and reset all your options and keybindings every time.

NOT GOOD ENOUGH.

The config.cfg file belongs to the user, not to anyone's engine (aside from ID's, of course). So why not check the exec command, and if it's "config.cfg" silently exec "myengine.cfg" afterwards, then write everything out to "myengine.cfg" on exit?

Every engine's peculiar configurations can now be preserved, and nobody will get their settings thrashed just because they decide to try a different engine every now and then.

Change Log 2008-12-15

  • Fixed crash on entering video menu with more than 27 supported modes.
  • Removed legacy software mouse code.
  • Removed legacy DirectInput mouse code.
  • Added new DirectInput mouse code with support for wheel and up to 8 mouse buttons.
  • Added friendlier error message if Direct3D is out of date.
  • Added XBox 360 controller code for movement and looking (using the analog pads).
  • Added XBox 360 controller button code.
  • Added basic force feedback effects.
  • Completed XBox 360 controller code.
  • Added basic joystick button update framework.

Marching On

Some good feedback from the release so far, thanks to everyone who downloaded it and tried it out.

Since then I've moved on a bit and started working on the DirectInput component. This is of fairly vital importance, as Quake's input code is old and clunky, and a legacy from a time when DirectInput was neither reliable nor mature.

So far I have 8-button mouse support with full wheel functionality (which even works with trackpads!)

Once I finish the mouse input part, I'm going to move my keyboard input code from the old GL engine over to this, then do some joystick/aux-device code. I think that XBox 360 controller support would be a very nice thing to have.

So it's looking like the next release will fix any major bugs that get reported, as well as include a full DirectInput implementation.

Sunday, December 14, 2008

Stick a fork in it...

I've decided to serve this one medium-rare.

It's not quite finished in other words. The only major remaining item is to add in support for sky and water surfs on brush models, but nonetheless, they're rarely enough used, and not even in ID1 so far as I can tell, so here it is.

I've put it on Sharebee pending Sourceforge approval; hopefully in a coupla days it'll appear there too, and that will become the permanent home for all future versions.

Have fun, now.

Depth/Stencil Performance Issues

I've traced the source of my recent massive FPS drop. I was blaming something from Windows Update unfairly, it turns out. This is actually a massive heads-up for anyone who ever wants to create a stencil buffer, so here goes:

It turns out that most cards will only support two formats of depth buffer: a 16-bit depth buffer or a 24-bit depth buffer. There is no point whatsoever in trying to create a 32-bit depth buffer, as you will only ever get 24 bits at best. This is as true for OpenGL as it is for Direct3D; check the results of your PixelFormat if you don't believe me.

The other 8 bits are available for use as a stencil buffer. You can of course choose not to use them for one if you wish. This gives a total of 32 bits, which is why clearing the stencil buffer is a free operation if you also have a depth buffer: either way it's a single 32-bit overwrite of everything in the combined buffer.

Where trouble starts is if you create a stencil buffer but don't clear it every frame. Now you're in a situation where you have a 32 bit buffer but you're only clearing 24 of the bits. This can be a much slower operation; I lost about 25% of my framerate on account of it. You will lose performance if you do this, even if you don't actually use the stencil buffer (say you just created it for later use).

So you have two options:

  • Don't create a stencil buffer; you have a single 32 bit operation to clear.
  • Do create a stencil buffer, but make certain that you clear it every frame, irrespective of whether you're using it; likewise a single 32 bit operation.
In case you're wondering, the 16 bit depth buffer with no stencil is the slowest of them all, so there's no way you want to create one of those unless it's all that your hardware supports.

All that aside, it's getting very near to "stick a fork in it" time... :D

Almost there!

Remaining items are:

  • Multitexturing the sky warp (UPDATE - done).
  • Tweaking particle size to match GLQuake (UPDATE - done).
  • Adding in the remaining vid_* commands.
  • Fixing up the loading disc (UPDATE - deferred).
  • Removing some legacy mess from vidnt (UPDATE - deferred).
  • r_shadows 1 mode (UPDATE - done).
  • Water and sky textures on brush models.
  • Changing the viewport to match scr_viewsize values (UPDATE - done).
Some of these are higher priority than others; some are not even required for a release but would be nice to do.

Aside from that it's very playable as it is. If you're not too bothered about any of the above, you could have a solid game of traditional ID1 on it and not notice anything too different. For reference, on an Intel 910/915 D3DProQuake gets about 56 FPS, this one gets near 90 (it was over 120 until Windows Update decided it wanted to be my friend), and with ample headroom for optimization.

More updates...

Alias models have just gone back in. The whole thing is starting to look very complete at this stage. I've also knuckled down and done player picture and texture translation, which I had been putting off as it looked horrible.

Right now I'm debating whether to use point sprites or textured quads for particles. I've already got the textured quads code written, but I really like the idea of point sprites, so I must check out if there are any device cap limitations on them. I do recall trying them out in OpenGL a good while back, and thinking that they were horrible there, but also seeing samples of the Direct3D implementation, and thinking that it was done right, so I've a pretty good feeling about this.

I think I'm going to remove the software fallbacks for input and sound. They were understandable enough in 1996 when DirectX wasn't that mature, and there was always a good chance that any given setup didn't support it properly, but it's not 1996 any more. Both APIs are well proven in the field, so no need for any caution there.

I'd also like to replace the entire networking layer with DirectPlay, but I can't seem to find any good or comprehensive documentation on it. It's a shame, as it looks to be a good basis for a solid networking layer that avoids a lot of the messy winsock code that's currently in the engine, and is also NAT-aware.

These last two items are for the future, however.

Saturday, December 13, 2008

Updates

Nothing done yesterday because I went to the pub. :)

Finished off the 2D drawing today; I'm reasonably happy with how I've got it done although I'm thinking that I'll probably want to go back and rework the vertex structs at some future time. That puts me pretty much feature-complete so far as porting is concerned, I think.

I had ripped out all alias model code a while back, so I'll be putting that back in later on, doing some cleaning up of stuff I wrote while figuring out what to do, and then hopefully it's release time!

Regarding the GL engine, it's coming near decision time as regards what to do with it. I'm certain that it's current shape and form is going to be abandoned at this stage; I don't see much mileage in continuing with it as I've been finding GL too frustrating to get any personal satisfaction out of.

Way I see it now is that GL is easy to get started with but annoying once you start getting advanced. D3D on the other hand is the exact opposite; annoying first, then really easy. I think Carmack maybe just didn't try hard enough back in 1996.

Specific example: the surface vertex format in Quake is extremely Direct3D-friendly. You can draw an entire surface in a single line of code. That's an actual API call, not an internal function call:

d3d_Device->DrawPrimitive (D3DPT_TRIANGLEFAN, surf->vboffset, surf->numedges - 2);
You can do the same with OpenGL vertex arrays, of course, but I'm referring here to the specific way GLQuake was written and to the specific examples in Carmack's famous rant. Other nice things are lightmap uploading (really neat and elegant, no need for keeping the data in main memory), resolution changing, external texture loading (not done yet, but it will rock like a mofo).

True, OpenGL has the advantage of portability, but at what cost?

Anyway, the big decision is do I port the GL engine to D3D or do I port the new stuff from the GL engine into the D3D engine? I'm inclining towards the latter option, as - while there's a lot of new stuff to port over - there's also an awful lot of mess that was required to get things working in GL (mostly things that should "just work") that needs to be cleaned out. Moving forward seems to me to be more fulfilling than cleaning out a mess.

I might release the current GL codebase as an "unfinished work", although I do also think that what Reckless has done with one of my earlier codebases is a great representation of where I was wanting to go, and I fully endorse and approve of it, and so I might also just leave things be.

Thursday, December 11, 2008

Let there be lightmaps!



I went with 64 bit in the end; 128 bit is a floating point format which would totally play havoc with performance. As it is, I still need to bitshift by 1 and do a 4x modulate blend to get the correct scale, as blocklights can exceed 65535 at times.

Nonetheless, the extra headroom is well worth it, eh? No stair-step effects either (well, a lot less than GLQuake with a similar light level enabled).

I still support 32 bit lightmaps if your card isn't able to do 64 bit textures, but it's a deprecated mode. I suppose this would be equally as easy to do in OpenGL, and I do highly recommend it.

Lightmap modifcation, by the way, is done. It's actually quite fast, seems faster than GL.

I've temporarily removed entities/etc pending a reworking of the horrible code I had previously written.

Fixed it!

Hopefully! ;)

CreateDevice needs D3DCREATE_FPU_PRESERVE; this is actually a fairly well documented "feature", whereby Direct 3D defaults to a low precision FPU mode ("to enhance performance") unless this flag is specified. Unfortunately, it plays unholy havoc with Quake's internal timers.

Other news - just restructuring the surface Vertex Buffer code a little, I've been reading up on simultaneous data streams for them, and it seems like I can drop most of the old BuildSurfaceDisplayList function, and create a number of streams. I like the flexibility this gives me, as it lets me lock and unlock individual streams, and store all of my static geometry on the hardware with quite a main memory saving. I know you can do it with OpenGL VBOs, but the API for that is horrid.

I'm going to do lightmaps as 128 bit textures! I've been experimenting with them a little, and it means that I can keep the lightmap granularity but still use 2x overbrighting (or even 4x overbrighting) to get a good look on it.

Wednesday, December 10, 2008

Weird bug alert!

I have this weird bug where the engine just seems to get progressively slower. It starts to feel noticeable after a half minute or so, then will really kick in after another half minute or so. So far I've established that:

  • It's not input lag, everything moves slow.
  • It's not memory consumption or similar.
  • It's not hardware, drivers or DirectX; same symptoms on different machines.
  • It's definitely isolated within Quake; other Direct3D apps run fine.
  • It's not the renderer, even in slowmo mode a timedemo still runs at normal speed.
  • This led me to a possibility of host_framerate creep, but it's not that either.
  • Putting cl.time out to the console reveals that it's definitely affecting cl.time. The initial kick-in seems to be about 25 seconds.
  • Checking the deltas for all of Quake's timers reveals that they don't creep, yet cl.time is getting progressively slower.
Currently working on isolating it; I've narrowed it down to something in the rendering code (by replacing everything else with stock Q1 source); yet it doesn't affect timedemos as I said above, so I know that the renderer is fundamentally sound. I fear that it's either a pointer being stomped or = instead of ==. Ugh.

Monday, December 8, 2008

Video Startup

Been working on the video startup code today; previously I was just running in a 1024 x 768 window. It's all coming together well; a little bit more involved than I had originally hoped, as Direct3D requires a lot of format enumeration before you can run a fullscreen mode (outside of demo programs, anyway). Here's where an equivalent of ChoosePixelFormat would be a Good Thing; just being able to specify what you'd like and having the API go off and find a good match is something of a luxury.

There's so much else that's different though that it's almost a full rewrite of the old gl_vidnt code; which is - to be honest - something that was much needed. And on balance, Direct 3D wins out by a clear mile, as once you get over the enumeration you're pretty much good to go; none of this lark of checking extensions, doing GetProcAddress on function pointers, and always being concerned that ATI or Intel will blow up somewhere.

The fact that the Direct 3D device will even switch resolution for you without having to do ChangeDisplaySettings is rather nice too.

I also reworked texture loading a little; I'm not wrapping the data in a TGA anymore, and I'm resampling at the same time as upsampling. It's considerably more memory efficient as Direct 3D lets you get direct access to the texels. Currently I have a simple nearest-neighbour resample, which I guess is roughly equivalent to ID's GL_ResampleTexture.

Been thinking about the status of my OpenGL code base; I'm starting to feel that I might release it "as is", as unfinished code, and focus on the Direct 3D port moving forwards. I'm in a position where I have a lot of code to port over whichever becomes my new codebase, and while I could just stick with the OpenGL codebase, I've about had my fill of OpenGL right now; it's not really the API's fault, but rather the vendors and regulators who are killing it.

Needless to say, the Direct 3D engine will be released sooner rather than later, as a feature-free basic port, before I start moving forward with anything (whatever I do decide to move forward with). I'll make a decision on the main codebase roundabout that time too.

Aaaaaarrrrggghhhhh!!!

Advice to Microsoft re: Direct 3D (slight return)...

WHY ON EARTH does the default presentation interval (D3DPRESENT_PARAMETERS::PresentationInterval) use the default system timer resolution? This has, like, very poor granularity, resulting in ultra-jerky screen updates. D3DPRESENT_INTERVAL_IMMEDIATE or D3DPRESENT_INTERVAL_ONE would be much more sensible options, IMO. OK, I'm sure that there's antiquated 1995-vintage hardware that only supports D3DPRESENT_INTERVAL_DEFAULT, but I'm also sure that it's possible to check for this at device creation (in the runtime, don't make stuff like this a developer requirement, we've enough to be doing without having to cope with this kind of thing) and fallback to default if the requested interval is unavailable.

It's not 1995 any more, folks! Ultra-caution gone mad, I tells ya!

How many people learning D3D must have given up when they hit this one, and didn't know how to proceed?

Sunday, December 7, 2008

The Start Map



Running in Direct 3D 9 with pretty much everything aside from lightmap updating done.

The world geometry is running from a single Vertex Buffer object, dynamic geometry is drawn using User Primitives (which I might change to dynamic Vertex Buffers, as I've read some bad things about User Primitives!)

That's not bad for just over a day's work - a 95% complete port of Quake 1 from OpenGL to Direct 3D. Useful resources were the DirectX SDK help documentation, Beginning DirectX 9 by Wendy Jones, http://www.directxtutorial.com and http://www.toymaker.info/Games/html/graphics.html.

I found the SDK to be a good reference, but not so much use for practical "how do I" stuff. I got stuck very quickly when I tried to move beyond the scope of the tutorials. The book was great for getting off the ground, showing what's required in a friendly usable style, although it ran out of steam after a while. The two sites deserve special mention for actually daring to provide code for techniques one is likely to use in a 3D FPS. A rare thing among tutorial sites.

Over the next few days I hope to finish this and release it.

The return of the OOP Zombies!

They're back, and this time brains are off the menu!



Yes folks, in order to use RotateAxisLocal functions on a D3DXMatrixStack, you need to instantiate a D3DXVECTOR3 class and fill in it's members, just to pass 3 float args!

Lesson for OOP programmers - lighten up! You don't need to write everything as a class, sometimes keeping it simple works best.

Here's some advice for Microsoft regarding Direct 3D:

  • I've ported most of Quake to Direct3D in less than a single day, including time off for sleep, food, and other essentials. So should Microsoft. It's one of the simplest real-world OpenGL applications out there, and would provide a great practical example of using real-world Direct 3D as part of the SDK.

  • They should swallow their pride and accept the GPL. It won't poison them, and there would be a fantastic block of usable (and re-usable) Direct 3D code out there for people to learn from and do what they want with.

  • Don't use fancy pants wrapper classes or frameworks, keep it as simple procedural code so that people can see step-by-step what's happening where and when. Their job is to demonstrate an API, not show off how great their programmers are.

  • Relate each block of Direct 3D code to it's corresponding block of OpenGL code by way of comments; this would make things so much easier for anyone else porting an application.

  • Document things correctly! Don't scatter vital information off in obscure places. Do the D3DXCreateTexture functions accept non power of 2 textures? Do they scale them up or down? Can the programmer choose the behaviour? I'm sure these are all documented somewhere, but where is anybody's guess. The OpenGL Reference Manual is a great example of exactly the kind of precise documentation about a function's behaviour I mean.

  • Write the process up as a case study so that people can read it and determine exactly what decisions were made and why they were made. This would be fantastic reference material for anyone in a position where they're making similar decisions, and would be straight from the people who know the API best.

  • Finally, a pet peeve - sort out Matrixes! Seriously, the API is truly vile here. Either use the D3DXMatrixStack methods in the SDK examples (so much easier and cleaner) or just copy how OpenGL does it (ditto).
Anyway, rant over. Alias models and sprites are done!

Moving to Direct 3D - Part 2

The world render (currently textures only) is now done. :)



Matrixes were a bee-hatch. The problem is, and I find this with OpenGL too, a lot of the samples and tutorials out there focus on the most simplistic ways of doing things, whereas in practical real-word scenarios things are actually very different. An example that gives single axis rotation, for instance, is of no use whatsoever when you're working on a game engine. The way Direct 3D matrixes work make this even worse, it's not just useless, it's dangerous too. There are other factors too; I realised quickly enough that you don't actually need a view transformation, but it would have been nice if the documentation had actually said that.

The next steps are to finish off the basic world render (adding lightmaps and sky), then add in alias models. That will get things to a stage where there's really just polishing off to do; some simple things (particles, sprites, some 2D stuff) that I haven't done yet, and a bit of final polishing off. I also want to move the static geometry to a Vertex Buffer, the API is just so much nicer than OpenGL here, so there seems no real reason not to.

I think I'm going to release this engine. It started out as a proof of concept (was it really only yesterday?) but it's becoming a viable port. It's just a straight port of stock GLQuake to C++ and Direct 3D, no real flashy added features or anything, but as basic as it is, I'm feeling somewhat fond of it.

Moving to Direct 3D - Part 1

Most of the 2D stuff is now done; I have yet to hit Draw_Fill (couldn't really be bothered at this stage) and Draw_TransPicTranslate. Otherwise, it's all rendering nicely in Direct 3D.

Lessons learned so far:

  • You read a hell of a lot about Vertex Buffers in the documentation and examples, but they're not really suitable for a lot of data that's dynamically changing. Having to set up a vertex buffer for every possible screen element makes no sense. Locking and unlocking to just draw a single 8 x 8 character seems like a daft thing. There is a IDirect3DDevice9::DrawPrimitiveUP member which is hardly ever mentioned, and is just perfect for this.
  • Carmack may have made a comparison between the amount of code required to get a triangle on screen in his OpenGL vs Direct 3D rant, but the point he missed (at the time) is that you're not really meant to use the API directly. Instead, you write wrappers for your common functionality around the API. You only need to write the wrapper once, and then Direct 3D actually requires substantially less code for the single triangle. It means a steeper initial learning curve, which I guess would put a lot of people off.
  • Textures are truly horrible. I've figured that the best approach is to build a TGA format in memory around the texture in GL_Upload32, then use D3DXCreateTextureFromFileInMemory to create it. Must rank as one of the nastiest hacks ever, but I really see no reason to do it any different.
  • After having been used to OpenGL for so long, Direct 3D matrices and co-ordinates seem slightly wacko. Even in pure 2D. Getting used to the differences is easily the biggest challenge.
More as it comes!

Saturday, December 6, 2008

Direct 3D Beckons...

I'm about 99% that certain I'm going to switch the engine to Direct 3D. At this time, it seems a pragmatic approach; OpenGL has been in an awful state for some time, and the recent release of 3.0 doesn't exactly fill me with confidence. OpenGL 2.0 took too long to appear, it seems to me that 3.0 has been rushed out, and the whole thing reminds me of the dying throes of 3DFX more than anything else.

There's also driver considerations; I totally despair of ATI's and Intel's abilities to write a conformant OpenGL driver, and overall Direct 3D seems right now to be a better option going forward. At least it's behaviour is more predictable.

Over the past few days I've been getting up to speed with the API, and replicating a lot of Quake's basic rendering in a standalone application. I'm happy now that it's viable and won't involve too much work, so for the next step I'm going to do a port of stock GLQuake. This is the "proof of concept" stage, and will provide me with a lot of code I can reuse in my own engine.

More news as it happens!

Friday, December 5, 2008

Cvar shadowing is done

It's all in place now.

As mentioned below, the objective of this system is not to break mods, but to protect the user's own chosen settings from unwanted abuse by trigger-happy misbehaving QC.

To demonstrate, I set the cvar "skill" to protected mode; here's what you get:



Instead of stomping on the real copy of the cvar, QC gets it's own shadow copy to play with, which it reads from and writes to. This is important, as it means that the QC will remain internally self-consistent.

Thoughts on Cvar control, safety and protection

I wrote this over on Inside3D:

It's unlikely, but you never really know when a mod is going to use one of those cvars in an unexpected manner. I mean stuff like "my mod only works on GLQuake, I need a spare cvar to stuff a setting into, so - hey! - I'll use vid_mode". Party like it's 1996.
While I was at least half joking at the time, a bit later I got to thinking about it, and realised that yes, it is a fairly serious hole in the engine. User settings do belong to the user after all, and while I would certainly hope that most mods would be reasonably behaved and respect that, the lack of control that's currently present means that misbehaving code, or a rogue mod (or even a Hipnotic one ;) ), could wreak all manner of havoc.

At the same time, that perspective has to be balanced with mods that genuinely do modify user settings for honest and above-board purposes, that use that modification for something cool, and that go about it in a reasonable and well-behaved manner.

So I'm going to implement a solution that accomplishes the following purposes:
  • User settings are protected from modification by mods.
  • Users are warned when a mod attempts to modify a user setting.
  • Users have the option to allow this to happen or not.
  • The option can be toggled on and off.
  • Mods have no control over the toggling of this option - it's entirely at the user's discretion.
This is going to be achieved as follows:

Cvar Protection
Every cvar can have a "USAGE_PROTECTED" flag set at initialization. If a cvar doesn't have this flag, QC is free to party on it. If a cvar does have the flag set, we go over to the next stage, which is...

Cvar Value Shadowing
As well as their normal value, cvars will also have a "shadow" value. If a cvar is protected, all reads and writes by QC use the "shadow" value; the normal value is therefore sacrosanct and can only be read or written by the engine. Reads by the engine never use the shadow value, writes by the engine write to both the normal and shadow value; therefore a misbehaving mod can always be overruled by what the engine thinks is right (and what the user thinks is right), and what a mod writes to a cvar will never affect how the engine works (or how the user wants it to work). This should cover all cases where QC wants to use a cvar for it's own internal purposes, but what if QC wants to actually modify a user setting?

Protection Overriding
The user can choose to protect or unprotect cvars. Setting "cl_cvarprotect 1" (the default) will use the protected mode; setting "cl_cvarprotect 0" will revert to the default Q1 behaviour - all cvars are fair game. This is an "all or nothing" setting; I had thought about using commands to protect or unprotect individual cvars, but decided in the end that most people will just want either the new way or the old way. Nothing to stop misbehaving QC from issuing these commands and making the whole exercise meaningless, anyway. QC can never write to this cvar; it's doubly-protected, if you like. Behaviour is entirely at the choice of the user, and the user alone.

User Notification
If QC attempts to write to a protected cvar, the user will be notified, and will be informed of what to do. Something like "Allow always", "allow this time" or "allow never", with an appropriate warning that allowing it may cause consequences, some of which may be unpleasant. Yes, it's Vista's UAC in Quake, and yes, it's for the same reason - handing control over things that could damage their system back to the user.