Finally I began working on the main source for Doom itself. The goal of the developments was to fix a few of the bugs that we present in the released version, and to add some new bits to make it fun. It's a pretty open remit, and it gave a lot of flexibility to do fun things. Andrew Rawnsley and I bounced back lots of ideas prior to the work, and there were a lot of debug and test versions with little features in them that we tried out.
Most of the features actually made it into the final release, as I found it very difficult to resist letting out a feature once it had been implemented, despite not working as well as I'd liked. When it came to later RISC OS work, I became a lot more cautious on features, only releasing them when they were ready, but for Doom there were many things that in retrospect could have been omitted and it wouldn't have been any the worse for it.
Initially, when I got the source code it was really nice to see - I'd worked so much with the leaked version previously that it was great to see that that code that I saw for the routines I'd examined was exactly the way I had expected it - if nothing else it's very reassuring to know that what you had been doing before had been based on solid ground. Other things were less good. Some of the code was still quite broken, and wouldn't even compile cleanly. First task, therefore, was to create a proper makefile, and build up a version so that I could actively work on it.
It was made more fun by the fact that there were 4 executables supplied for the game - the original 320x240 fixed resolution games, and the 'high resolution' ('HR') versions of the game which could use different resolutions configured from the command line, and each of those were in 8bit-per-pixel and 24 bit-per-pixel. Some of the assembler code was specific to each version, compiling customised code that was more efficient for each one. The static resolution versions were faster than their HR companions as many calculations and lookups didn't need to be performed at render time.
I split these apart so that there was a single makefile which could build any of the versions I wanted and only needed to build the parts that were changed by any modifications. For the most part work concentrated on the 8-bit-per-pixel, high resolution version (8HR), as this was best trade off for speed and quality. In most of the cases there was no need to try the other versions for every change because it was the core engine being worked on.
When I'd initially got the game there was a short list of bugs that had been reported and a few feature suggestions. Mostly the remit was to make it faster and add in any features that were cool. Compared to the other versions that were around - id Software having released a GPL version of the game - this was still the most advanced (as far as I could see). The OpenGL variants that had just begun to be produced were still incredibly poor and didn't really do anything but rework the engine for OpenGL.
There had been a few reports about the save function not working, which remained there throughout much of the development. This turned out to be quite terrible code that would store the data to be written to the off-screen buffer before writing it out - 'safe' in the knowledge that this was a large area and could be caused to be redrawn completely on the next frame. This assumption was correct for Doom levels as supplied so long as nothing bad had happened (level 30 could generate a very large number of objects, especially ones that were outside the 'world'), but got worse with increasingly complex levels. Plus, the size of the screen buffer would vary with the HR variants of the build so if you has a smaller than the default resolution screen you could really break things.
People were quite active in creating levels - there was even an Acorn User competition to create them, I believe - and levels such as the 'Aliens Total Conversion' had been brought across to the RISC OS versions of Doom quite successfully, except for the saving. It was very easy for these complex levels to generate very large save files.
Eventually the whole problem was addressed by extending the buffer as it filled, this time safe in the knowledge that we're running on a larger machine than previously so an extra few hundred KB wouldn't really matter - and in any case they should only cause textures to become uncached from the zone, which would then be reloaded on the next frame.
The total conversions and other patches that had been produced had encouraged Rob Kendrick and Luke Graham to write !DeHackEd, which would let you replace information in the static data blocks of the game. In the PC version of the tool (created completely independently) this was achieved by patching the executable at known locations. There were only a few 'real' released shareware/commercial versions, so there were fixed addresses to patch messages and the like. This was clearly unworkable when there were a few releases of the RISC OS version and a stream of betas being sent to people to try out.
To make this easier, I created a marker block in the executable which
would contain the locations of the tables so that they could be patched
safely by such tools. The
DEHACKED table was used by a few tools, and
went through a few different versions before we had a table that they
could use safely and contained all the detail that was needed - at least
I was never asked for any others, to the best of my knowledge.
As a game, Doom was generally quite popular, but a common complaint from the people that had spent money on joypads, or joysticks, was that games didn't support them. Acorn had tried to encourage more and better support by standardising a joystick support module interface. It was pretty simple, and so adding support for it - and the additional buttons that were generally available on joypads - wasn't all that difficult. It was made a little more complicated by needing to add operations to the internal interfaces for these new mappings. Unlike the keyboard you couldn't allocate 7 buttons so that you had every weapon available to you.
So instead I added support for a 'change weapon' button. This would change to the next most powerful weapon available which has ammo. The difficulty with adding new key bindings for things that don't exist in the basic game is that these need to be passed through the network interface so that they can be supported by the remote clients. Any changes that are made have to either be mapped down to operations that already exist, or negotiated safely during the network configuration.
Later - much later - I added scroll-wheel support to the mouse handling, which needed a little care to ensure that it worked with the existing operations. The simplest way to do it was to just put the code alongside the change weapon operation. The configuration for the scroll-wheel isn't present in the front end at all. There's also no extra support for the additional buttons that the mouse can support (up to 8 buttons, I believe).
Anyhow, back when I was doing it proper the mouse support was pretty decent. The main complaint about the mouse - aside from it not working like we expect in modern games now (or like Quake did, as it was then) - was that exiting back to the desktop appeared to lose the mouse. This was mainly because the mouse bounds had been set to be outside the desktop and were never restored when the game exited.
Build switches and optimisations
Much of the work that went on inside the source itself was made conditional. Mainly this is because it made it easier to switch implementations when optimisations were made or to disable code that had been added experimentally and was found to be unsuitable. This meant that in many cases when a bug came in from a tester about behaviour (say) with a particular PWAD file, the code could be debugged and switched in and out of the relevant sections easily to check whether the behaviour was new or had been introduced by a particular group of changes.
This was most obvious when we were operating on the visible planes (visplanes) which was previously a fixed sized list and became dynamic so that we could support larger PWAD files. At the same time, finding the correct visplane to reference became much harder, so a hash table was added to lookup the correct plane reference during the rendering loops. This made quite a difference to the rendering time for the reference scene (opening scene of Doom 1 shareware), and even more significant differences to other areas where there were more planes in view - stepped regions were bad for this. It was made worse when there was more visible vertical space, as is the case when we have higher resolution modes and wide regions with many different levels in them.
Even simple things like optimising the initialisation code so that it used multiple registers made a difference. Not enough usually to warrant recoding in assembler, but enough that a simple examination of the compiled output would show that a few tweaks to the way the data was written could be very beneficial. These days I usually use the argument that I'll let the compiler get on with what it does best, but I wouldn't trust it to generate more efficient code on many of the things that were optimised in this way. Nor should I trust that modern compilers would apply the same optimisations that the one I was using at the time did - that's the penalty for not writing it in assembler.
Much of the time that Doom spends in performing operations that aren't already cached involves looking up lumps by name. Mostly this happens at the start of the level, but can happen a lot. To make this faster, and more standardised, I create a sorted list of lumps and their locations. The initial implementation would just use a linear search on the lumps that had been found during the initial search. Because we need to return the same results, the sorted list is pre-initialised to contain only the last occurrence of a given lump name. There were a few times when I needed to rework the algorithm I'd used to populate the sorted list. In particular I needed to merge groups in the right way so that the results worked. The usual recommendations for PWADs that needed to do this was that they could be merged with the main IWAD details, and tools existed for this. In practice, my merged groups for the sorted list removed that need - you could usually just drop a PWAD on Doom and it would work without any special merging.
One of the interesting bugs that came up in some levels was the 'fire lines' bug. The floor, usually an animated lava flow, would extend the full height of the screen (or to the base of the screen), rather than stopping at another floor segment. The bug was caused by the a node generator splitting lines in order to make segments, and the split occurring at a non-integer position on the line.
Because the line positions can only be stored as integers, this means that the line is slightly away from where it needs to be and thus there is an opening where the sector isn't fully enclosed. The effect being that an assumption that the floor must be able to extend until it hits another line, is therefore not met.
These flaws cannot be fixed in all cases, but they can be mitigated slightly by modifying the data as it is loaded so that the lines which are split to make segments are done in such a way as to avoid the problem. The principles of the fix were investigated and implemented in Boom by Lee Killough, and I implemented a similar fix to his, with a refinement to avoid the overlarge corrections that occurred in some circumstances.
In use, I found that a cascade could occur, and the 'fix' would end up moving lines more and more until they were significantly out of place. The Doom+ engine identifies such cases and backs out the fix - better to have a small line problem than for the entire region to become a mess of lines.
Even still, the problem remained in some maps and could be an amusing distraction, albeit not a dangerous one - unless you were on the receiving end of a Cacodemon attack at the time.
Filtering and translucency
During the development Andreas Dehmel released 'DIY' Doom, which was pretty impressive in its own right. Not only was it a full source release with some nice features and quite fast, but it also had bilinear filtering of textures which was... nice. Partly because of this, it was necessary to add the same to Doom+. It's not an overly complex effect but it does mean more operations per pixel during the plot, and that means slower framerates. Not only the number of operations, but also the number of registers that can be held at a time is reduced, which means that we need to stack a little more, and we can't do the two columns at a time that was more efficient on the StrongARM.
In the 24bit version we need to do calculations on RGB triples. As you can configure the filtering to be linear (vertically) or bilinear (both vertically and horizontally) this gives a little granularity for the framerate choice for the user. It's also possible to control which items will be filtered, as they're all handled by different routines. The floors (planes) are separate from the walls, which are different again from objects and monsters (and walls with holes in). Floors and walls are 'easy', but objects need a bit more care, as their calculations may include sections which are transparent, so cannot be blended.
If you then add this to the transparency of objects, like fireballs and explosions you very quickly find that you're reading and writing a lot of data, never mind the number of calculations per pixel. It can get a bit slow. I did try to make it as fast as I could - I'm pretty certain there's still a lot to gain from the render code with a bit of thought.
The 8bit version was a slightly different matter. It's quite possible to take every pixel, look up its palette entry, perform the same RGB transformation on the data then convert that back to a pixel again. Possible, but very, very slow. I took the simpler solution of pre-computing lookup tables for 25%, 50% and 75% - 64K × 2 - for blending two palette entries together at those factors (and for 75% it's just the inverse of the 25%). This means that the 'blending' in the 8 bit-per-pixel mode is a lot less accurate, but it does improve things very slightly. Not enough to make me happy with it, though.
I was thinking on this earlier today and came to the conclusion that for a small extra cost in speed, and double the memory usage I might have provided a colour pair - which could have been alternated between to give a dither pattern. It might have looked a little better. Of course it might have looked worse, as these would have been continuous blends of colour which dithering doesn't lend itself to, but it might have been worth trying.
8 bits-per-pixel, normal
8 bits-per-pixel, all filtering on
24 bits-per-pixel, normal
24 bits-per-pixel, all filtering enabled
Having a first person shooter is all very well, but wouldn't it be better if it was really 3D? Looking 3D, rather than just being a 3D image on a screen? Surely it isn't that hard? We already render one scene, and the original Doom engine was set up to render a 'left' and 'right' view for additional screens in network play, so giving it a second view port shouldn't be that hard.
Really it isn't that hard. The display becomes one that has only red and green components - nothing else - which means that the rendered images have effectively 4 bits per pixel (as we're using a 256 colour mode - I didn't expand it to the 24 bit-per-pixel modes). Every frame was rendered twice to different buffers as a greyscale and then merged into the red/green palette.
The gamma correction could be changed for each lens through '
-greengamma' switches. Similarly, the spacing between the
'eyes' which would view the scene, and the focal angle used could be
controlled by other switches. You could also flip the colours so that red
and green were the other way around - just in case you had glasses that
were the other way around to mine.
In general, I found that it worked quite well - at least as well as many of the other 3D effects using the same anaglyph technique. Something bad tended to happen though and the red image tended to be visible to the green eye. This was part of the reason for the gamma correction, as a compensation so that the green image was stronger. However the ghosting still appeared, and spoilt the effect sometimes - it depended on the scene, and particularly when there was movement, the ghosting wasn't as apparent. I decided that the problem probably lay more with the quality of the lens than my representation.
Since then, I've found that this is a common problem with such methods used on games. Maybe that means it wasn't all that bad.
There was also a debug feature on the map, when the game was built with the 3D switch enabled. Two lines would be drawn from the apparent positions of the eyes, at the focal angle - which would show where the focal point was. As this was the point at which the left and right images would be coincident, it was easy to check that the actual image on the screen matched what was expected.
There are two major ways of using the 3D version - with a eye separation that approximates a normal person (which is pretty easy to check on the map) and a focal distance which is a little more than the normal fighting distance (I think I used about 1 degree), or with an eye separation of 0, and the same focal distance (about 1 degree).
The former feels more sensible, as it equates to what you expect to work as that's what each eye is seeing. This means that objects about 4 or so metres away are coincident in the image - everything closer than that will 'pop out' of the screen. This has issues with the borders as if you try to focus on the areas that are in front of the screen they will appear to overlap the monitor surround, destroying the effect.
The latter, however, makes only things that are right on top of you coincident and everything further away separates more and more. At least that is if I've remembered rightly!
I tried the former method a lot more than the latter, because you are focused on the centre of the screen and that makes the effect less likely to break down - as you start to look at the edges however, you do tend to lose the effect.
However it was done, the game tended to give me a headache far more quickly than playing normally did. Even 30 minutes of play was painful - and that wasn't helped by the cheap 3D glasses I had, and had to wear over my real glasses. Although it was kinda fun, I put it on one side and it remained a build time only option.
I remember there were quite a few PWAD files which I used during testing.
In addition to downloading a large portion of the PWADs from
(seemingly now dead), I tried a few from other contributors as well, but
the main PWADs I remember testing were Runes (from the public sites), and
Christopher Bazley's 'Medieval Castle', which I'm pretty sure showed up
a few little oddities. Runes works very nicely when put together with
'The Key' by 'Strangers On A Train' playing in the background .
I did an interview with IconBar when I finished doing the Doom+ things, which reads badly in places, but covers a bit of stuff.
There's a bunch of information I put on my website some years ago which seems to have been placed out of the main areas. It's got a little guide to the front end, and a few details about the plug-ins that were created.