The next month or so will be spent reviewing the engine’s systems methodically and refreshing them. Some sections of the code haven’t been revisited in quite a while, and this is an opportunity to survey and update if needed. I won’t be doing any major reworking. So far I’ve covered the Windows, threading and sound code. I want to write about a few things I encountered.
As I began to rework the Windows code I realised I wanted the following:
- ability to limit platform specific #includes such as’windows.h to a single source file so they are not ‘seen’ all over the project.
- ability to hide data structures pertaining to platform specific code from the rest of the code base.
- no globals, declare all in main() and pass down.
- particularly a way to avoid globals in WinProc
Didn’t take me long to discover I couldn’t have them all. Data hiding means local globals. Declaring everything in main() overloads the stack frame pretty quickly! No way of avoiding globals in WinProc. I made some headway wrapping Windows API calls for things like the performance counters, D3D calls, but ran into the need for persistent Window API objects like handles. To limit the scope of those in the project i thought about using a data hiding approach like PIMPL, but I could see the obfuscation getting messy quickly. Data hiding is easy enough with private vars, but I don’t like to use member functions. In the end I decided that I couldn’t fight the limitations of the language and embraced the #include panoply that is C/C++, declared global objects but passed them in at as high a function level as possible, and will worry about platform agnostic code another time.
When the engine window has the focus the cursor is hidden, clipped to the window and wrapped around when it hits the edges for seamless mouse look. I made sure the cursor is restored properly when the window loses focus.
I’ve rolled my own thread pool for the engine and there wasn’t a great deal needing addressing there, except for two glaring things:
- to wake the main thread after the thread pool finished a job batch I was using events bound to each job! Not particularly robust. I replaced this with a single event which is triggered by the last worker thread to sleep after finishing, where a bit mask set in the shared critical section indicates thread activity.
- lack of proper shutdown. The main thread now waits for the worker threads to exit in an orderly manner before itself quitting.
The sound system is one of the most recent additions to the engine and could really be considered gen 1, in that it hasn’t been revisited since being first written. As such there was a fair bit to tidy, and still more left to do:
- more robust .wav loading. Rather than use strict offsets into the file, I fixed the loader to search for the unique keywords that head each section, ‘RIFF’, ‘FMT’ etc.
- more rigorous understanding: revisiting the sound system meant reinforcing a number of processes I’d only just grasped the first time I wrote it, and I was able to strip away a fair bit of cruft and bring some clarity to the code
- fudging attenuation: my first pass attempt at spatial sound and distance fall off come at with half understood theories and some magic numbers that sounded right. This time I set up a proper mapping with a near and far plane for distance, max and min volumes for the stereo channels mapped to the orientation of the player, and an inverse square fall off.
- real time decompression: the .wav files I am using are all 8 bit UINT PCM format, and I was converting them to reals at load time, as I run a float32 secondary buffer. This now done at runtime, samples are converted, attenuated by the channel volumes, interleaved into frames and mixed into the main buffer, all using vector instructions.
- improved active sound list: part of the sound system’s job is maintaining a dynamic list of sounds currently needing playing. Triggered sounds are added, finished sounds removed. I replaced the existing clunky playlist structure with an ‘indexed linked list’ , patent pending :). This is my take on a linked list but using indices, not pointers.
Future work: Honestly I’m still not 100% clear on the best way to implement a streaming buffer for sound. I’m mixing all sounds in my own buffer and copying that down into the DirectSound secondary buffer each frame, which seems straight forward enough, but there’s a few wrinkles. DirectSound provides a write cursor beyond which it is safe to write to after locking the buffer, but how much to write? I tried tracking the amount the play cursor had consummed in the previous frame and writing that, but it’s sometimes not enough. My solution was to write ahead of the write cursor somewhat, so there’s a lag of about 60ms in the sound delivery, which is not noticeable, and appears to work well, but still doesn’t seem like an ideal solution. An improvement on that, which I will be implementing in the future, is to write from the write cursor but oversupply by an amount, perhaps 2-2.5 x an average frame’s worth. Then next frame write from the new write cursor position, overwriting some of the last write, but at least leaving a reasonable play buffer for longer frames. Always stuff to do!