So, my name is Neil Coffey. I'm a Java developer. Of course, I'm a Java developer with that
surname. And so, this is a talk about a little side project that I started a couple of years ago.
It was kind of, I was just keen to see in Java how far I would get with developing an emulator.
This is the first emulator that I've developed from scratch. And it kind of started, you know,
I had a bit of time, you know, we've had a lockdown and I kind of thought, well,
what do I need to write an emulator? Well, one of the things I might try and do to start with is
get a ROM reader to kind of start from scratch. And then I found, I don't know if you've heard,
but my country left the EU a couple of years ago. And I actually found it hard to source the ROM reader
from Germany. So, the first thing I did, if there's any work, is I built my own, obviously,
that's the first thing. And then, so by the time I'd done this, I was kind of committed at that
point. Okay. So, what I'm going to go through, then, is my experiences of writing an emulator and
kind of, as I say, first time I've ever written decisions, challenges. It's going to be a little
bit of a tour through some of the APIs that there are now in the Java platform, this kind of thing.
And in all honesty, some kind of, there are some pros and cons that I'll talk about. Yeah.
And above all, some kind of little, little tricks in the APIs that aren't always very well documented
that can kind of help us. So, why Java? So, I'll be completely honest. The main reason for me was,
it's the language I'm most familiar with. Yeah. So, I've been using Java now for about 20 years,
about the first JRE that I used came on floppy disk. Okay. So, that's how long.
These days, I'm just obviously cross-platform. And these days, it's got quite a rich set of APIs,
hopefully, everything we need to develop an emulator. It's got good longevity. So, you tend
not to have this thing in Java that you sometimes get in Swift, for example, where you kind of come
in one morning, try and recompile your code and find it won't compile anymore because Apple's changed
something. Java tends not to have that. It's maintained good backwards compatibility over the
years. And so, hopefully, anything I write, moving forward, will also run. I don't have to have an
emulator in a few years' time to emulate the emulator. Okay. There are, as well, from a personal
view, there's some APIs coming up that I was kind of keen to have a benchmark to see, well, in a
couple of years' time, you know, things like the, you know, the foreign function and memory API that's
kind of just about to kind of hit stability. I was kind of interested to see, well, you know,
what will I be able to do with that when it comes out? Okay. So, I set myself some goals
that I wanted to be, my emulator, to be accurate enough to allow most software to run on.
In all honesty, for kind of version one of my first emulator, there were some things that I
decided not to emulate, to things like memory contention issues. There are some weird things
that you can get that I'll maybe have time to talk about in the spectrum with kind of glitches in the
video display. So, essentially, my kind of overall goal was anything that software uses that isn't
a kind of bug in the hardware that people might accidentally get around or use, I'll try and
emulate that. As Roddy mentioned, one thing I was trying to do is get a baseline from the basic
Java APIs and try not to bring in additional libraries as a kind of starting point and want
to be a cooperative applications like not necessarily just full screen, perform enough,
yeah, as I say, I'm not trying to write a one gigahertz Z84 for my kind of first projects.
Which machines do I try to emulate? So, I went for the trusty old ZX Spectrum. So,
apologies to Steve, I'm adding to the pile of emulators now available from ZX Spectrum. And
I also thought that the Sega Mars system, so why these two together? A, these are the machines I
had as a kid, okay? But B, if we look at the technical specs, there are actually some similarities
that are going to help us. So, you can see the video resolution is similar, although the video
chips and formats that they use are very different. The CPU essentially is a data around 3.5. So,
around 3.5, actually, there are different models of the spectrum with different speeds and the
Mars system, very slight. I think it was 3.58 for the Mars system. And you can see then here,
for the, probably everybody in this room is kind of fell if middle of these machines, but for those
who aren't. So, you can see that the Sinclair Spectrum in comparison was all about saving money.
So, you had one custom ULA here that was handling the video and the sound and was also memory controller
compared to the Mars system that had a bit more on-board hardware that we're going to have to
try and emulate. So, just a little bit more detail of some of the difficulties, again, for people
and may be familiar. So, the ZX Spectrum, it renders its video all from RAM, essentially with
kind of no acceleration as such. And it's got this format that really kind of gives the ZX Spectrum
its look and feel. Yes, you had essentially a one bit per pixel bitmap and then over the top of
that, you're allowed two colors, essentially, per rate by itself. Yeah, and this kind of gave the
Spectrum a bit of a unique look and feel as bright and flash as well per cell. Compare that to the
Mars system where you've got an actual dedicated graphics chip, but this was all tile based. Yeah,
so you have a 34 by 24 tile display. Each tile can be 8 by 8 pixels. Yeah, so the eagle eyed
amongst you will notice that you can't actually define enough unique tiles to give each pixel in
that display. It's kind of a unique pixel. So anything that looks like it does, you'll see you
get these kind of almost like little manga cards for some games. Or here where we've tried to fill
the screen, obviously secretly around the edges, we've actually got blank space. So there wasn't
actually enough memory to have unique tiles for every space on the screen. But despite that,
it did have features that were actively kind of advocated by Sega to its developers to make the
most possible use of that of the video chip. So the way it worked, you have a series of registers
to control things like the scrolling, the colors. And there was a mechanism via interrupt to actually
on each scan line or on every nth scan line, depending on how you programmed it, you could
actually change those registers. Yes, you could change the scroll position at different parts of
the screen. You could switch off the screen. You could potentially change the color palette.
And so that's something when we're doing our video rendering, we're going to have to have a
little think about how we can kind of optimize that a little bit. I'll just give a very quick
example. So we're going to see here, we've got some parallax scrolling, where you see how on
different scan lines, we're setting a different X position. And then that's quite a nice fact,
that's a game called Choplifter. On the next example, we're actually going to have a case where
here we're actually, it's not literally turning off the screen, but it's changing the base address
of the screen memory to effectively turn it off at that bottom part. And this is kind of probably
the most one, an extreme example here, where literally on kind of every other scan line,
we're changing the scroll position to kind of give that effect there. So very briefly,
I'll just give a little bit of the kind of the overall organization of the emulators,
kind of the first thing you really need to think about. So it's how we kind of turn this,
this is very high level obviously, but this essentially what the hardware looks like, we've
got an address bus at the top with the ROM and the RAM connector, we've got a data bus at the
bottom with any peripherals, which on the spectrum were fairly minimal, there was a one to eight version
with the sound chip. And then on the master system, you can see again, similar idea,
but notice that the ROM essentially is the cartridge that you plug in. Yes, when you plug a
cartridge in, you're kind of directly communicating with the Z80 and any logic for things like memory
paging, you can have that on the cartridge. And then a few more peripherals going on the data bus,
we've got the video processor there, the programmable sound generator, there's not an FM unit,
which I'll touch on briefly, and the controllers. So then what I try to do, and so there was the
emulator clock there as well. And what I try to do is to abstract that down, so that I'm going to
organize the program this way, we've obviously got the Z80 implementation is obviously a kind of
fairly fundamental part. But then we've, what I've actually done is in my implementation,
I've separated out the Z80 decoder from the actual instruction loop. This is quite nice and we want
to add a debugger as well, then you can go through the same code to decode the instructions
for the debugger. And then we've got an abstract IO bus, from which again then on the master system,
we'll have our master system IO bus on the spectrum IO, etc. A memory of similar ideas,
we have subclasses of these overall base classes. And the clock, which is actually working the other
way round to the way that the hardware, the clock is effectively going to be a kind of break
on the CPU thread and is going to tell it when to pause to keep things at the right
rate of instructions. And there'll be a little bit of feedback as well between the video thread
so that it can interact with the CPU to do the things I've just mentioned about accurately timing
the scroll registers and things. So just an example, I end up with interfaces like this
and then to the Z80, it's effectively, it doesn't care whether it's a master system
or a spectrum it's communicating with, it just goes through these abstract interfaces like this.
A little bit of detail just on, I've just mentioned about the the CPU. The implementation that I went
for, which isn't necessarily the most kind of popular of the traditional emulators, I tried to
really break down the instruction set into more of an object-oriented form. So I've got instruction
types you'll see there and then for each type the individual instruction is kind of returned as
an object that says well it's this type and it's from this source, this destination. So I've tried
to kind of not have to write 900 different routines for all the various combinations that the Z80 had
and that gives quite nice code. There's a little bit of a performance trade-off obviously
but it turns out not to be not to be too bad. Okay and then the other decision I made was well
we're now writing in Java in 2023 now so I decided well I want to make the most of multi-threading.
So the various of the components I've just mentioned will actually sit in their own thread.
Okay and that's kind of nice organizationally and also in terms of monitoring the performance of the
app it means we can break down a little bit more easily what resources are being used for each
component. So just to give a little bit of an overview of this, so we'll have at the top kind
of got our, well this work yeah, is that good? So we've got our the CPU thread at the top there
and which is going to be interacting with the clock and is periodically going to say
you know I've done this many instruction cycles. How am I doing? Do I need to pause to kind of
maintain the correct instruction rate? Then we've got the video controller which is going to be
sending periodically sending V blank instructions every frame to the CPU to notify it.
We've got then also a separate rendering thread which is going to do any of the kind of heavy
lifting rendering that we need to do. So anything like scaling, calculating what the actual pixels
are and then the idea is that here in the event dispatch thread which is a single threaded at
that point we have to kind of have our ducks in a row and know what we're actually going to render.
Then additional complication is it was going to be an audio service in its own thread as well.
So different APIs that we're going to use. There's a standard Java Swing API so there's no
additional open GL plug-ins here. A couple of the Java sounds I mentioned monitoring
Neo kind of a little hidden one but when we're writing data, when we're emulating kind of
cartridge saves and we want to write data actually open a mapped file for that to save the data
and their threading is often important. I'm not going to really mention too much but there are
also desktop and taskbar and integration APIs that help with integrating into the desktop with
the system menus and things. So we'll start with the graphics. The standard Swing and Java 2D APIs
people may be familiar with, the idea is that you override the the jcomponent class and you
implement a paint component method and here in principle we can set various options to
hint with whether we want quality speed etc and then finally we can render an image and it will
be rendered with the with these different settings. But some caveats with that. Unfortunately it turns
out that some of those options effectively end up turning off GPU acceleration and they can be quite
CPU hungry and efficient. It's not clearly documented which ones actually run on the CPU and the GPU
but effectively ends up that the fast options without any quality interpolation are the ones that
run that just go straight to the GPU. So we're going to have to be a little bit careful
not to use too much CPU time for each frame render. And then there's also an additional problem
that the standard API to set and get pixels from buffered images
actually it's quite inefficient for setting individual pixels but we have a workaround. So
this will be the standard API that we'd use. We create our image like this, lovely, we set
different types about 15 different types that we could use and then we can set RGB and whether that
backing star is an input pixel or bytes per pixel or whatever it will work out how to set the RGB
lovely. But in practice we're probably never going to have anything other than an input pixel.
So this is the least efficient way we could possibly imagine to set the pixel data.
Luckily we can actually with a little bit of jigglypokery we can ask Java 2D for the underlying
interay and then we can just directly write to that. The advantage being then things like array
fill, array copy, array dot fill sorry they then become available. There's a caveat that normally
wouldn't do this because if you've got static images that you're rendering lots of times
the what would normally happen is that Java 2D sends that to the GPU once then subsequent
renders are effectively free but we don't really need that for our purposes. We're going to be
rendering a different image on each frame effectively so that's not such a problem for us.
So then just to come back to us I'm showing you earlier with the different scroll per
frame on different raster lines. We kind of want to get the best of both worlds with how we then
end up structuring things. So what I do is I basically I kind of break down the image
and say well for this frame where are the points where the things like the scroll registers
actually change. On some games that will they will just have one setting per frame and I can then
just efficiently render the the the entire frame without without having to you know worry about
clips per section etc. So I don't kind of literally go through pixel by pixel kind of
chasing the beam. Just yes there's just a kind of brief example here so I'll split into sections
and then I can say for that section get me the relevant settings and then go through and fetch
from the from the the tile map data and render it kind of almost as you expect.
So by doing that and by using this trick of getting the raw kind of interay
this does allow us to get quite a good speed up on on the rendering. So if there's kind of one
one thing you're doing in Java the kind of the one kind of speed up to think about is probably this.
Mention so having having none about that trick there's some little little tricks that we can do
obviously people familiar with with with CRTs where they were actually the way these systems
work they kind of render every other scan line and we can if you've got a really good quality
monitor a little like that most people's minds a little bit more that you kind of had bleed in
between the scan lines and you also kind of get ghosting effects this kind of thing. So we can
try and give a little bit of the that look and feel yeah so I'm literally going to do here in
the Java is I'm going to render things that every other kind of scan line I'm going to render the
kind of darkened version of that scan line so I can kind of produce something like this
and then just have to be a little bit careful with the scaling because you can get more effects if
if you've you've got a kind of odd scale factor so do a little bit of extra interpolation to try
and get around that. Then another effect that we can do in Java is to like these kind of ghosting
effects if we can define our effect in terms of a convolution matrix which you may have seen
then we get native library built in that will allow us to render that efficiently and that will
also access the integer data under the hood it won't go through that set RGB every time.
So we can get effects like this again we're kind of at low rendering time
and then this is for my favorite spectrum games from a child
to do something like this combining the kind of CRT effect. Another issue we just have is there
are multiple ways to scale images in Java and depending on which one we pick we kind of get
different different performance characteristics so the thing I'm actually looking at which is kind
of most stable is to actually just hard code just hard code the scaling myself because then I can go
through this you know access the interay directly some of these other built-in APIs unfortunately
you know they go through that get RGB set RGB to be you know support different formats but we don't
really we don't really need that. Okay let's talk about sound so the the mass system and the spectrum
had quite different ways of producing sound the spectrum obviously was this kind of very simple
speaker it could effectively be a one or a zero and you kind of control a square wave literally from
the CPU to produce your sound but then something like the master system that had an actual sound
chip you would control the sound by setting register to say I want tone one to be this frequency
etc so we want to abstract those two ways of producing sound so that we can we can just have
one generate sample data method and then our audio service is going to call into that and so
it's just a brief slip here of what I do so I've got it that'll be the subclass for example for the
spectrum type sound there and then here a bit more complicated but we effectively you know do a
similar thing we're going to be whenever we're asked for some sample data we're going to calculate
that sample data and split it back yeah and then and then the question becomes well given that sample
data production how do we actually pipe it down to the audio output and Java has this slightly
quirky model where you have a notional mixer that's got inputs and outputs and the slightly perverse
thing is that everything is seen in terms of this notional mixer so when you want to output sound
you're actually sending it to an input of the mixer yeah so we call it a source line yeah
whereas to us it's not really a source it's a target but that's the reason for that so if I
you see here they're also tied to particular drivers and I can enumerate the different
drivers on my machine I find that I found out for example that my Mac can listen through my iPhone
microphone that was the first time I found that out so yeah so we we're clearly available mixers
and then we query them for their available source lines okay and then we can we can write the data
and to the source line we open it with a format that we want we write the data and so this is
now where I can call my generate sample data method when there's some frames to send I send them
okay people might have spotted a slight flaw with that I've got a nice infinite loop there
on something like the spectrum I need to be able to tell the difference between there's no audio
and there's no audio yet but but there's some on the way and I don't want to sit in an infinite
loop in the meantime okay so this is where so yeah this was just code examples how I get
we output those ones and zeros and then we translate them but so I'll just skip quickly
we so we get those ones and zeros and then what we're actually going to do is we're going to use
a condition object which is part of the Java concurrency API so that we can basically in our
audio in our audio service thread we can wait for a notification that there's actually some audio
that that we want to send okay there we go okay yeah there's also a little bit that we can do with
yeah hybrid buffering is basically where we we want we want to ideally have a small buffer to
fill to send but that then ensures the problem of we might we run the risk that if we can't
fill our buffer in time we end up with choppy audio and so in practice what we can actually do is
have a larger buffer and detect when it's half full and kind of keep topping it up and so that's
basically how I do it okay and the FM synth which I'll mention briefly I never had one of these
I think they're quite rake and I'll get them in Japan but the master system this was an option
for the master system okay and I'm what I actually do for this I cheat slightly I use javas built in
midi software synthesizer so I translate the instructions to that FM synth into midi commands
and I send these to the soft synth and I don't know if this is going to play on the projector
but I'll turn up the audio here and just see so you'll hear difference you'll hear the the normal
PSG sound chip and then you will hear the FM kind of synth oh I don't hear that it's probably too
quiet and you see there we can then start playing about with things like the the voices that we
we assign to those okay so I'll just touch on very briefly because time is getting to the end
and so I'll just touch very briefly on the timing and concurrency so the CP obviously we need to
maintain it at a kind of our desired instruction rate so the way I do this is I introduce pauses
and but then we want to be able to accurately measure those pauses and we also need to accurately
measure the timings between the frames that were that we're sending and there are there's
obviously standard APIs in in Java to do this a little issue that I did come across the standard
executor framework that we'd normally use for doing this so here we say right okay I want to
frame every every 60th of the second depending on your platform you can actually in practice get
quite erratic intervals between between between the the events so you can see in particular on
macOS I find you could get this kind of 20 error so this is just just one experiment for example
if we and what I luckily found was that if we request low low sleep interval with the accuracy
is actually better for low sleep intervals than the higher sleep intervals and it seems to it
seems to max out a particular amount I'm not exactly sure of the underlying reason for that
it was to meet in Darwin but then what this leads to is we can kind of come with depending on the
platform we can come up with a different strategy for maintaining accurate timing and a challenge
you know it's a perpetual challenge with Java really is then that the the the best strategy
will will depend on the depend on the platform very briefly data manipulation which sometimes
something a bit scared of in Java we all of the types are right well they're generally signed
char is unsigned but they're generally fixed width and signed we can't do what we can in
seeing other languages and defining our own types and so one way to work around this one
want to do things like register access and the audio data is the byte buffer is generally
the kind of the easiest way to do that and you'll notice that when we want when we want bytes
because byte the byte type is signed so if on an unsigned byte then we would normally
and promote it to an int and then we can basically undo the ff and lock lock off the lock lock off
the the lowest bytes and then so there's just a I'll just skip further and there's just a question
with that about well how do we check that the jit compiler is doing what we need to do and so I'll
just let step forward slightly and what we can actually do we can ask it yeah so we can we can
ask it to dump out the the the jit compiled assembler and then we can check if some of those
optimizations are actually going in so this was very simple test I set up it's basically
it's iterating through repeatedly effectively writing a word and then reading it from from a
byte buffer yeah this obviously is slightly contrived this is you know really the kind of the
contrived corner case example but it kind of illustrates the the kind of thing that's possible
yeah so I'm effectively that bts effectively writing a two byte unsigned value into there
via a byte buffer so it looks like I'm creating a byte buffer setting values on it calling a method
on it but by the time we get down to the actual jit compiled assembly code in the best case we're
actually not that just compiles down into a we are storing a half word in there and so that's the
kind of thing that we can that we can do to kind of check for those things okay and I think we're
skipped to the there we go yeah so mentions yeah the method those method calls are completely
optimized out okay so so there you go so in conclusion using those various APIs together
we can write them in Java a few pros and cons caveats around the different platform behavior
a few things that still to add in here this is this is very much kind of version one
however it was at the point where it will actually run quite a lot of the spectrum
master system software if anyone's curious I've got initially released there on github
there's going to be source code and further improvements on the way so watch that repo as
they say a few references there that people may or may not have come across this book here by
Chris Smith is I think kind of a remarkable piece of work about the kind of the very kind of low
level details of how the spectrum works and the usual you know kind of reference guides that over
the years have surfaced on the web and so with that I think I'll hand back