From 771b942197d61efec6d6491e0bffaddaca61f198 Mon Sep 17 00:00:00 2001 From: Sacha Chua Date: Fri, 19 Mar 2021 00:47:37 -0400 Subject: Update transcript for 27 --- ...emacs-chip8--vasilij-wasamasa-schneidermann.vtt | 630 +++++++++++++++++++++ 1 file changed, 630 insertions(+) create mode 100644 2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt (limited to '2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt') diff --git a/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt b/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt new file mode 100644 index 00000000..3d655630 --- /dev/null +++ b/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt @@ -0,0 +1,630 @@ +WEBVTT + +00:00:00.880 --> 00:00:04.520 +Hello everyone and welcome to my talk, "The State of Retro Gaming and Emacs." + +00:00:06.960 --> 00:00:08.639 +First of all, a little bit about myself. + +00:00:08.639 --> 00:00:12.000 +My name is Vasilij Schneidermann. I'm 28 years old. + +00:00:12.000 --> 00:00:14.719 +I work as a cyber security consultant at msg systems, + +00:00:14.719 --> 00:00:17.359 +and test other people's web applications + +00:00:17.359 --> 00:00:20.160 +and review the source code for security problems. + +00:00:20.160 --> 00:00:22.080 +You can reach me by email. + +00:00:22.080 --> 00:00:25.039 +I have my own self-hosted git repositories, + +00:00:25.039 --> 00:00:28.160 +and I have a blog where you can occasionally find new posts by me + +00:00:28.160 --> 00:00:32.160 +on all kinds of things, not just Emacs things. + +00:00:32.160 --> 00:00:34.559 +The motivation about this one... + +00:00:34.559 --> 00:00:37.600 +I found that Emacs is the ultimate procrastination machine, + +00:00:37.600 --> 00:00:39.600 +and there are lots of fun demonstrations. + +00:00:39.600 --> 00:00:41.200 +I'll go over a few of them. + +00:00:41.200 --> 00:00:45.840 +For example, someone made a thing to order salad for himself online, + +00:00:45.840 --> 00:00:48.239 +so he doesn't have to walk over to the shop. + +00:00:48.239 --> 00:00:51.760 +There's plenty of IRC bots. There's some game things. + +00:00:51.760 --> 00:00:55.600 +There's an emulator for the Z-machine +which you can use to play zork. + +00:00:55.600 --> 00:00:57.440 +And so I asked myself, at this point, + +00:00:57.440 --> 00:00:59.920 +can you actually emulate retro games at 60fps? + +00:00:59.920 --> 00:01:02.079 +I looked around a bit and found some projects, + +00:01:02.079 --> 00:01:06.159 +but none that were actually able to do it at 60fps. + +00:01:06.159 --> 00:01:08.000 +So I set out to do my own one, + +00:01:08.000 --> 00:01:09.200 +and looked out for a console + +00:01:09.200 --> 00:01:11.119 +that you can actually emulate at that speed, + +00:01:11.119 --> 00:01:14.690 +using Emacs with its very, very limited rendering. + +00:01:16.320 --> 00:01:19.200 +And here's the project, chip8.el. + +00:01:19.200 --> 00:01:20.560 +It's pretty much finished. + +00:01:20.560 --> 00:01:24.000 +It clocks into under 1000 source lines of code. + +00:01:24.000 --> 00:01:26.159 +It supports the superchip 8 extensions. + +00:01:26.159 --> 00:01:27.280 +It runs at full speed. + +00:01:27.280 --> 00:01:29.600 +All games behave okay, as far as I'm concerned, + +00:01:29.600 --> 00:01:31.680 +and yeah, I'm pretty happy with it. + +00:01:31.680 --> 00:01:34.479 +It's very much the hello world of emulation, + +00:01:34.479 --> 00:01:40.880 +and I might, maybe, do some other emulation projects in the future. + +00:01:40.880 --> 00:01:43.360 +Now, for the section which is the longest: + +00:01:43.360 --> 00:01:45.439 +bunch of fun facts about chip8.el + +00:01:45.439 --> 00:01:49.200 +which I've learned during this project. + +00:01:49.200 --> 00:01:51.759 +So what the hell is chip8 anyway? + +00:01:51.759 --> 00:01:54.960 +First of all, unlike many other emulation game things, + +00:01:54.960 --> 00:01:56.799 +it's not a console, but a VM. + +00:01:56.799 --> 00:02:00.000 +It was designed for easy porting of home +computer games. + +00:02:00.000 --> 00:02:02.320 +It wasn't terribly successful, + +00:02:02.320 --> 00:02:05.439 +but there's still a small community of enthusiasts writing games for it, + +00:02:05.439 --> 00:02:09.119 +and there are even a few demos. + +00:02:09.119 --> 00:02:11.039 +This VM has system specs. + +00:02:11.039 --> 00:02:14.720 +It has a very, very simple 8-bit cpu with 16 registers, + +00:02:14.720 --> 00:02:17.280 +and 36 fixed-size instructions. + +00:02:17.280 --> 00:02:19.680 +You have a whole 4 kilobyte of RAM. + +00:02:19.680 --> 00:02:22.080 +You have a stack with 16 return addresses. + +00:02:22.080 --> 00:02:25.760 +The resolution is 64 by 32 black/white pixels. + +00:02:25.760 --> 00:02:28.000 +Rendering is done by drawing sprites. + +00:02:28.000 --> 00:02:29.200 +These are drawn in XOR mode, + +00:02:29.200 --> 00:02:31.840 +meaning that if you draw a sprite and set a bit, + +00:02:31.840 --> 00:02:35.040 +it just flips over from black to white or white to black. + +00:02:35.040 --> 00:02:39.360 +For sound, you have a monotone buzzer that can just beep at one frequency. + +00:02:39.360 --> 00:02:43.120 +Most unusually, there's a hexadecimal keypad as input, + +00:02:43.120 --> 00:02:48.480 +so the keys are basically zero to nine and a to f. + +00:02:48.480 --> 00:02:50.720 +So how does this whole thing work? + +00:02:50.720 --> 00:02:52.400 +It runs at an unspecified speed. + +00:02:52.400 --> 00:02:53.040 +You'll probably have to do some fine-tuning + +00:02:53.040 --> 00:02:56.080 +to find the speed you're happy with. + +00:02:56.080 --> 00:02:58.080 +Sound and delay timers exist. + +00:02:58.080 --> 00:03:01.120 +They count down at 60fps down to 0. + +00:03:01.120 --> 00:03:05.120 +This is done so that you can play a sound at some specific time. + +00:03:05.120 --> 00:03:07.840 +The game itself is loaded with a fixed offset into RAM. + +00:03:07.840 --> 00:03:10.480 +The program counter is set to exactly that offset, + +00:03:10.480 --> 00:03:11.920 +and from there it enters the game loop + +00:03:11.920 --> 00:03:13.280 +where it decodes an instruction, + +00:03:13.280 --> 00:03:14.800 +executes it for the side effects, + +00:03:14.800 --> 00:03:18.130 +and just loops and does this ad infinitum. + +00:03:19.599 --> 00:03:22.720 +So the game loop was the first thing where we ran into problems. + +00:03:22.720 --> 00:03:25.120 +The usual game approach is to do stuff, + +00:03:25.120 --> 00:03:26.640 +figure out how long to wait, + +00:03:26.640 --> 00:03:29.280 +wait for exactly that much, and repeat. + +00:03:29.280 --> 00:03:31.680 +This doesn't work well in Emacs at all, because, well, + +00:03:31.680 --> 00:03:34.959 +user input, basically. + +00:03:34.959 --> 00:03:37.760 +Emacs is designed to just do whatever it needs to do + +00:03:37.760 --> 00:03:39.040 +whenever you enter user input + +00:03:39.040 --> 00:03:42.319 +instead of doing things at one specific time. + +00:03:42.319 --> 00:03:46.640 +If you try to do interruptable sleep, well, you get unpredictable behavior. + +00:03:46.640 --> 00:03:50.480 +For example, it can be the timer doesn't run at all at the next time + +00:03:50.480 --> 00:03:52.560 +because you've accidentally cancelled it. + +00:03:52.560 --> 00:03:55.120 +If you do uninterruptable sleep, it freezes instead , + +00:03:55.120 --> 00:03:56.720 +which isn't what we want either. + +00:03:56.720 --> 00:04:00.560 +So I went for timers, which forced me to do inversion of control, + +00:04:00.560 --> 00:04:02.560 +meaning that I have to write code in the style + +00:04:02.560 --> 00:04:04.879 +where it just calls timer, + +00:04:04.879 --> 00:04:06.560 +and this allows this input to happen + +00:04:06.560 --> 00:04:11.040 +and for things to progress at roughly the speed I want to. + +00:04:11.040 --> 00:04:14.159 +So there's the timer function which is called at 60fps + +00:04:14.159 --> 00:04:17.359 +and I have to be very careful to not do too much in it. + +00:04:17.359 --> 00:04:21.305 +And, say, this function executes CPU cycles, + +00:04:21.305 --> 00:04:26.479 +decrement the sound/delay registers, and redraw the screen. + +00:04:26.479 --> 00:04:28.800 +So to map this whole system to Emacs Lisp, + +00:04:28.800 --> 00:04:31.199 +I've used just integers and vectors + +00:04:31.199 --> 00:04:33.120 +which contain even more integers. + +00:04:33.120 --> 00:04:35.040 +This is used for the RAM, registers, + +00:04:35.040 --> 00:04:37.040 +return stack, key state, screen, + +00:04:37.040 --> 00:04:38.508 +and so on and so forth. + +00:04:38.508 --> 00:04:41.520 +Basically, what you would do if you were writing C. + +00:04:41.520 --> 00:04:43.360 +All of this is stored in global variables. + +00:04:43.360 --> 00:04:45.600 +I'm not using any lists at all. + +00:04:45.600 --> 00:04:48.400 +As a side effect, there's no consing going on at all. + +00:04:48.400 --> 00:04:50.080 +There are no extra objects created + +00:04:50.080 --> 00:04:53.199 +which would trigger garbage collection processes. + +00:04:53.199 --> 00:04:55.600 +Getting this right was rather tricky, actually, + +00:04:55.600 --> 00:04:58.240 +and there were some hidden garbage collection problems + +00:04:58.240 --> 00:05:01.759 +which I had to resolve over time. + +00:05:01.759 --> 00:05:03.759 +So, decoding instructions. + +00:05:03.759 --> 00:05:06.800 +For this, you have to know that all instructions are two bytes long, + +00:05:06.800 --> 00:05:08.880 +and the arguments are encoded inside them. + +00:05:08.880 --> 00:05:11.440 +For example, the jump to address instruction + +00:05:11.440 --> 00:05:15.120 +is encoded as one and three hex digits. + +00:05:15.120 --> 00:05:18.400 +The type is extracted masking with #xF000 + +00:05:18.400 --> 00:05:20.400 +and then shifting it by 12 bits. + +00:05:20.400 --> 00:05:23.520 +Mask means you perform the binary AND. + +00:05:23.520 --> 00:05:28.400 +You can do the same with the argument by masking with #0xFFF and no shift. + +00:05:28.400 --> 00:05:30.560 +If you do this long enough, you'll find common patterns. + +00:05:30.560 --> 00:05:32.639 +For example, addresses are always encoded like this + +00:05:32.639 --> 00:05:34.880 +using the last three nibbles. + +00:05:34.880 --> 00:05:36.160 +In the code, you'll find a big cond + +00:05:36.160 --> 00:05:40.070 +which dispatches on the type and executes it for the side effects. + +00:05:41.440 --> 00:05:45.919 +For testing, I've initially just executed the ROM until I've hit C-g, + +00:05:45.919 --> 00:05:49.039 +and then use the debug command to render the screen to a buffer. + +00:05:49.039 --> 00:05:53.199 +Later on, I found tiny ROMs that just display a static test screen, + +00:05:53.199 --> 00:05:57.280 +for example, logo, and looked whether it looked right. + +00:05:57.280 --> 00:05:58.800 +I added instructions as needed + +00:05:58.800 --> 00:06:00.720 +and went through more and more and more ROMs. + +00:06:00.720 --> 00:06:04.000 +And later I wrote a unit test suite as a safety net. + +00:06:04.000 --> 00:06:07.840 +This unit test suite, it just sets up an empty emulator state, + +00:06:07.840 --> 00:06:09.199 +executes some instructions, + +00:06:09.199 --> 00:06:14.880 +and then looks whether the expected side effects have happened. + +00:06:14.880 --> 00:06:18.319 +For debugging, I usually use edebug, but this was super ineffective, + +00:06:18.319 --> 00:06:21.600 +because, well, you don't really want to step through big cons + +00:06:21.600 --> 00:06:23.680 +doing side effects for every single cycle, + +00:06:23.680 --> 00:06:26.880 +when it can take like 100 cycles for things to happen. + +00:06:26.880 --> 00:06:29.680 +Therefore I've set up logging. + +00:06:29.680 --> 00:06:32.639 +Whenever I logged something and couldn't figure out the error, + +00:06:32.639 --> 00:06:37.039 +I compared my log output with the instrumented version of another emulator, + +00:06:37.039 --> 00:06:40.479 +and if the logs diverge, then I have figured out where the bug lies + +00:06:40.479 --> 00:06:42.720 +and could look deeper into it. + +00:06:42.720 --> 00:06:44.960 +Future project idea might be a chip 8 debugger, + +00:06:44.960 --> 00:06:49.440 +but I doubt I'll ever go into it. + +00:06:49.440 --> 00:06:51.759 +For analysis, I initially wrote a disassembler, + +00:06:51.759 --> 00:06:54.400 +which is a very simple thing but super tedious, + +00:06:54.400 --> 00:06:56.639 +especially if you wanted to add advanced functionality, + +00:06:56.639 --> 00:06:58.720 +for example, analysis or thinking of what part is data, + +00:06:58.720 --> 00:07:00.000 +what part is code. + +00:07:00.000 --> 00:07:03.360 +I had this great idea for using the radare 2 framework + +00:07:03.360 --> 00:07:06.479 +and adding analysis and disassembly plug-in for it. + +00:07:06.479 --> 00:07:08.400 +So I looked into this. Found, okay, + +00:07:08.400 --> 00:07:10.319 +you can write plugins in C + +00:07:10.319 --> 00:07:12.639 +but also in Python, so I wrote one in Python, + +00:07:12.639 --> 00:07:14.720 +and then discovered there's actually an existing one in core, + +00:07:14.720 --> 00:07:18.400 +which you have to enable explicitly by passing an extra argument. + +00:07:18.400 --> 00:07:21.680 +I've tried it and found it's not exactly as good as my own one, + +00:07:21.680 --> 00:07:24.160 +so I improved this one and submitted pull requests + +00:07:24.160 --> 00:07:26.610 +until it was at the same level. + +00:07:28.080 --> 00:07:30.720 +Rendering was the trickiest part of this whole thing, + +00:07:30.720 --> 00:07:34.319 +because, well, I decided against using a library. + +00:07:34.319 --> 00:07:37.120 +Not like there would have been any usable library for this. + +00:07:37.120 --> 00:07:40.880 +My usual approach of creating SVG files was too expensive. + +00:07:40.880 --> 00:07:45.120 +It just created too much garbage and took too long time. + +00:07:45.120 --> 00:07:47.360 +I then tried creating mutating strings. + +00:07:47.360 --> 00:07:52.479 +This was either too expensive, just like SVGs, or too complicated. + +00:07:52.479 --> 00:07:57.280 +I tried changing SVG tiles, which created gaps between the lines. + +00:07:57.280 --> 00:08:00.720 +Then I tried to create an xpm file which was backed by a bool vector + +00:08:00.720 --> 00:08:02.400 +and mutating this bool vector, + +00:08:02.400 --> 00:08:04.000 +but the image caching effect + +00:08:04.000 --> 00:08:06.479 +made it just every nth frame to appear, + +00:08:06.479 --> 00:08:08.879 +which wasn't good either. + +00:08:08.879 --> 00:08:11.440 +Then I had the idea to just use plain text + +00:08:11.440 --> 00:08:13.120 +and paint the individual characters + +00:08:13.120 --> 00:08:14.800 +with a different background color. + +00:08:14.800 --> 00:08:17.120 +This had perfect, perfect performance. + +00:08:17.120 --> 00:08:19.280 +There were many optimization attempts until I got there, + +00:08:19.280 --> 00:08:21.199 +and it was very, very stressful. + +00:08:21.199 --> 00:08:26.160 +I wasn't sure whether I would ever get to accept the performance at all. + +00:08:26.160 --> 00:08:28.560 +For sound you only need to do a single beep, + +00:08:28.560 --> 00:08:31.280 +so technically, it shouldn't be difficult to emulate it. + +00:08:31.280 --> 00:08:33.039 +However, doing this is hard because + +00:08:33.039 --> 00:08:37.200 +Emacs officially only supports synchronous playback of sounds. + +00:08:37.200 --> 00:08:41.360 +But there's also Emacs process, which you can launch in asynchronous way. + +00:08:41.360 --> 00:08:44.720 +So I looked into it and found that mplayer has a slave mode + +00:08:44.720 --> 00:08:48.640 +and mpv supports listing on the fifo for commands. + +00:08:48.640 --> 00:08:53.760 +So I've created a pipe, started a paused MPV in loop mode, + +00:08:53.760 --> 00:08:56.560 +and always send in pause and unpause command to the FIFO, + +00:08:56.560 --> 00:08:58.000 +and that way I could control + +00:08:58.000 --> 00:09:02.640 +when to start beeping and stop beeping. + +00:09:02.640 --> 00:09:04.160 +So yeah, that's it so far. + +00:09:04.160 --> 00:09:07.200 +It was a very educational experience. + +00:09:07.200 --> 00:09:10.320 +I have tried out a bunch of games which were, + +00:09:10.320 --> 00:09:14.320 +well, I almost say the worst ports of classic games I've ever tried. + +00:09:14.320 --> 00:09:15.680 +It wasn't terribly fun to play them, + +00:09:15.680 --> 00:09:18.555 +but was fun to improve the emulator + +00:09:18.555 --> 00:09:21.760 +until, well, things worked good enough. + +00:09:21.760 --> 00:09:25.120 +I've learned a lot about how computers work at this level, + +00:09:25.120 --> 00:09:28.880 +so, maybe, maybe I'll in the future make another emulator, + +00:09:28.880 --> 00:09:34.000 +but I'm not sure whether anything more advanced, like an Intel 8080 emulator, + +00:09:34.000 --> 00:09:36.560 +will actually run in Emacs fast enough, + +00:09:36.560 --> 00:09:37.839 +but it's still an interesting idea, + +00:09:37.839 --> 00:09:40.800 +because then you could actually have an OS inside Emacs + +00:09:40.800 --> 00:09:43.120 +and fulfill that one specific meme. + +00:09:43.120 --> 00:09:45.440 +But if I try to do most serious stuff, + +00:09:45.440 --> 00:09:47.040 +I'll probably use Chicken Scheme, + +00:09:47.040 --> 00:09:49.920 +which is my preferred language for serious projects, + +00:09:49.920 --> 00:09:53.279 +and write a NES game emulator. + +00:09:53.279 --> 00:09:57.839 +And that's it. Thank you. -- cgit v1.2.3