Coder Social home page Coder Social logo

deltabeard / peanut-gb Goto Github PK

View Code? Open in Web Editor NEW
246.0 11.0 32.0 9.98 MB

A Game Boy (DMG) emulator single header library written in C99. Performance is prioritised over accuracy.

Home Page: https://projects.deltabeard.com/peanutgb/

C 99.94% Makefile 0.06%
gameboy-emulator gameboy c emulator-library dmg

peanut-gb's Introduction

Peanut-GB

Peanut-GB is a single file header Game Boy emulator library based off of this gameboy emulator. The aim is to make a high speed and portable Game Boy (DMG) emulator library that may be used for any platform that has a C99 compiler.

This emulator is very fast. So much so that it can run at full speed on the Raspberry Pi Pico! Check out BENCHMARK.md for benchmarks of Peanut-GB.

Only the original Game Boy (DMG) is supported at this time, but preliminary work has been completed to support Game Boy Color (see #50).

This emulator is a work in progress and can be inaccurate (although it does pass Blargg's CPU instructions and instruction timing tests). As such, some games may run incorrectly or not run at all. Please seek an alternative emulator if accuracy is important.

Features

  • Game Boy (DMG) Support
  • Very fast; fast enough to run on a RP2040 ARM Cortex M0+ microcontroller at full speed.
  • MBC1, MBC2, MBC3, and MBC5 support
  • Real Time Clock (RTC) support
  • Serial connection support
  • Can be used with or without a bootrom
  • Allows different palettes on background and sprites
  • Frame skip and interlacing modes (useful for slow LCDs)
  • Simple to use and comes with examples
  • LCD and sound can be disabled at compile time.
  • If sound is enabled, an external audio processing unit (APU) library is required. A fast audio processing unit (APU) library is included in this repository at https://github.com/deltabeard/Peanut-GB/tree/master/examples/sdl2/minigb_apu .

Caveats

  • The LCD rendering is performed line by line, so certain animations will not render properly (such as in Prehistorik Man).
  • Some games may not be playable due to emulation inaccuracy (see #31).
  • MiniGB APU runs in a separate thread, and so the timing is not accurate. If accurate APU timing and emulation is required, then Blargg's Gb_Snd_Emu library (or an alternative) can be used instead.

SDL2 Example

An example implementation is given in peanut_sdl.c, which uses SDL2 to draw the screen and take input. Run cmake or make in the ./examples/sdl2/ folder to compile it.

Execute in command line with peanut-sdl game.gb which will automatically create the save file game.sav for the game if one isn't found. Or run with peanut-sdl game.gb save.sav to specify a save file. Or even peanut-sdl to create a drop zone window that you can drag and drop a ROM file to.

Screenshot

Pokemon Blue - Main screen animation Legend of Zelda: Links Awakening - animation Megaman V

Shantae Dragon Ball Z

Note: Animated GIFs shown here are limited to 50fps, whilst the emulation was running at the native ~60fps. This is because popular GIF decoders limit the maximum FPS to 50.

Controls

Action Keyboard Joypad
A z A
B x B
Start Return START
Select Backspace BACK
D-Pad Arrow Keys DPAD
Normal Speed 1
Turbo x2 (Hold) Space
Turbo X2 (Toggle) 2
Turbo X3 (Toggle) 3
Turbo X4 (Toggle) 4
Reset r
Change Palette p
Reset Palette Shift + p
Fullscreen F11 / f
Frameskip (Toggle) o
Interlace (Toggle) i
Dump BMP (Toggle) b

Frameskip and Interlaced modes are both off by default. The Frameskip toggles between 60 FPS and 30 FPS.

Pressing 'b' will dump each frame as a 24-bit bitmap file in the current folder. See /screencaps/README.md for more information.

Getting Started

Documentation of function prototypes can be found at the bottom of peanut_gb.h.

Required Functions

The front-end implementation must provide a number of functions to the library. These functions are set when calling gb_init.

  • gb_rom_read
  • gb_cart_ram_read
  • gb_cart_ram_write
  • gb_error

Optional Functions

The following optional functions may be defined for further functionality.

lcd_draw_line

This function is required for LCD drawing. Set this function using gb_init_lcd and enable LCD functionality within Peanut-GB by defining ENABLE_LCD to 1 before including peanut_gb.h. ENABLE_LCD is set to 1 by default if it was not previously defined. If gb_init_lcd is not called or lcd_draw_line is set to NULL, then LCD drawing is disabled.

The pixel data sent to lcd_draw_line comes with both shade and layer data. The first two least significant bits are the shade data (black, dark, light, white). Bits 4 and 5 are layer data (OBJ0, OBJ1, BG), which can be used to add more colours to the game in the same way that the Game Boy Color does to older Game Boy games.

audio_read and audio_write

These functions are required for audio emulation and output. Peanut-GB does not include audio emulation, so an external library must be used. These functions must be defined and audio output must be enabled by defining ENABLE_SOUND to 1 before including peanut_gb.h.

gb_serial_tx and gb_serial_rx

These functions are required for serial communication. Set these functions using gb_init_serial. If these functions are not set, then the emulation will act as though no link cable is connected.

Useful Functions

These functions are provided by Peanut-GB.

gb_reset

This function resets the game being played, as though the console had been powered off and on. gb_reset is called by gb_init to initialise the CPU registers.

gb_get_save_size

This function returns the save size of the game being played. This function returns 0 if the game does not use any save data.

gb_run_frame

This function runs the CPU until a full frame is rendered to the LCD.

gb_color_hash

This function calculates a hash of the game title. This hash is calculated in the same way as the Game Boy Color to add colour to Game Boy games.

gb_get_rom_name

This function returns the name of the game.

gb_set_rtc

Set the time of the real time clock (RTC). Some games use this RTC data.

gb_tick_rtc

Deprecated: do not use. The RTC is ticked internally.

gb_set_bootrom

Execute a bootrom image on reset. A reset must be performed after calling gb_set_bootrom for these changes to take effect. This is because gb_init calls gb_reset, but gb_set_bootrom must be called after gb_init. The bootrom must be either a DMG or a MGB bootrom.

License

This project is licensed under the MIT License.

peanut-gb's People

Contributors

deltabeard avatar froggestspirit avatar robloach avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

peanut-gb's Issues

sdl2: enable recording of frames and audio into a video format

I think this will be better for making animations and videos than dumping every single frame in an uncompressed bitmap.

Requirements:

  • Video and audio in lossless format if only one format enabled for each.
  • Compile time option to enable or disable this bulky feature.
  • Start/Stop video record keyboard shortcut.

Optional:

  • Recording at correct frame rate.
  • Ability to pipe video and audio to an external program like ffmpeg, instead of using an encoder directly.

sdl2: only update LCD line on change

Add a feature in the SDL2 example to only refresh the lines of the screen that have changed since the previous frame. This can be used as a reference for a performance improvement on platforms where communicating with an LCD is time costly.

A hashing algorithm that is fast for 160 bytes of data should be selected.

In this case, multiple hash algorithms could be added, and one could be selected for use at runtime. Some platforms, like the RP2040, have integrated CRC32 hashing. I think it will be interesting to see how well something like CRC32 will do against a stronger hash algorithm like wyhash or xxHash.

Use of "_t" in struct names must be changed to "_s"

For a long time now, I have been incorrectly adding "_t" to the end of struct names, not realising that "_t" is typically for typdefs and that they are reserved anyway.

Therefore, all defined "_t" variables in this repository must be changed to "_s".

CGB Support

I added CGB support here:
https://github.com/froggestspirit/Peanut-GB

I only really tested this with Pokemon Crystal, but seemed to work fine. I wasn't sure if you might want to merge it, or copy parts of it over, I know there's some formatting differences, and also the way I handled sram.

I should also note, this might only be working in the SDL2 example for now, since some changes to the C file were necessary there for rendering the palettes.

ppu: Rewrite PPU

  • Make the code more readable.
  • Use as little memory as possible.
  • Send each line to front-end when ready (if this makes sense).
    • The idea is that each line may be sent to the LCD whilst the emulator continues running.
    • The front-end may implement a feature where only changed pixels are updated if communication with the LCD is slow.
    • This removes the need for the massize framebuffer array in the emulator context structure.
  • Add support for interlaced mode.
    • This may allow the front-end to compensate for slow LCDs and missed deadlines.
    • Should be switchable during emulation.

core: improve performance by calculating interrupt time in gb_run_frame()

An increase in performance may be realised by calculating the time until an interrupt is expected as shown in gregtour/gameboy#5 (comment) .

__gb_step_cpu() may be refactored into separate inlined functions in order to allow gb_run_frame() to wrap the main interpreter in a for loop, whilst keeping the current functionality of __gb_step_cpu() for debugging purposes.

Any change in performance should be recorded in BENCHMARK.md, Other optimisations mentioned in the comment above should also be considered.

Use `uint_fast#_t` types instead of `unsigned int`

Some systems are faster when they use specific integer types. For variables where the width does not matter, using uint_fast#_t instead since it should provide the most efficient code for the platform.

static const unsigned int TAC_CYCLES[4] = {1024, 16, 64, 256};

Additionally, uint32_t could become uint_fast32_t in the following function:

uint32_t gb_get_save_size(struct gb_t *gb)

A smaller width could be used where small size is preferable over performance, as in:

const uint8_t cart_mbc[] = {

Where the code is executed very infrequently.

Potential Speed Up [Enhancement]

I've been messing with this on a microcontroller, and was getting maybe around 80% speed (going by the crude audio I added). When the program hits a halt, the code has it run a bunch of filler nops until it hits the next interrupt. I've been testing a few things to skip past this, currently I changed the gb->counters to count down, so when it hits a nop, it can take the lowest counter and set the inst_cycles to the next multiple of 4 (4 is the minimum so it doesn't hang on the LCD timer being 0). This gives a small speedup.

serial: remove requirement for frontend function if unimplemented

If the front-end does not support serial, the serial transmit function currently required to be implemented in the front-end must still return 0xFF (which is what the Game Boy sees when no device is connected to the serial port).
If serial connection is supported by the front end, then a gb_serial_init() function may be called to imply support. Otherwise, an internal function to peanut-gb may emulate no device connected by default.

Add likely/unlikely macros for improved performance

Some checks, like the following NULL check, will either always be true or false depending on if the user application is implemented correctly. When using peanut-gb with LCD enabled, this NULL check should never be true, so it's a bit of a waste.

if(gb->display.lcd_draw_line == NULL)

The least we can do is use an unlikely(x) macro here, or we could wrap it in an assert.

Peanut-GB should be profiled, and unlikely/likely macros should be used throughout where there is likely to be significant performance gains.

core: game compatibility list

Please comment whether Peanut-GB was able to play a game in a playable state or not.
Any issues with playable games must be listed with a comment.
Unplayable games must have a comment describing why it isn't playable.

Playable:

  • Legend of Zelda: Links Awakening
  • Mega Man V
  • Pokémon Red/Blue/Yellow
  • Pokémon Gold/Silver
  • Tetris
  • Pipe Dream
  • Dr. Mario
  • Super Mario Land 2 - 6 Golden Coins
  • Metroid II
  • Kirby's Dream Land 2
  • Final Fantasy Legend

Unplayable:

  • Is That a Demo in Your Pocket? (#81)
  • Dragon Warrior III (#83)

core: lcd_mode enum type as signed int

On some compilers lcd_mode enum is assumed int (I dont know why?) and not unsigned int. Therefore the bitfield should be large enough to encompass the sign bit

Peanut-GB/peanut_gb.h

Lines 376 to 382 in 1ad02b5

enum
{
LCD_HBLANK = 0,
LCD_VBLANK = 1,
LCD_SEARCH_OAM = 2,
LCD_TRANSFER = 3
} lcd_mode : 2;

lcd_mode : 2 should be lcd_mode : 3?

Currently I get warnings like this
warning: implicit truncation from 'int' to bit-field changes value from 3 to -1 [-Wbitfield-constant-conversion] gb->lcd_mode = LCD_TRANSFER; and the screen is not rendered

core: Add function to change various core emulator settings on-the-fly

A new function should be written to allow the frontend to turn various settings on and off. These may include:

  • Ability to switch sound off and on. Useful if the frontend wants to fast-forward without sound. The frontend should handle this, not Peanut-GB which doesn't do APU emulation.
  • Ability to switch certain sound channels off and on. The APU should do this (not included in Peanut-GB).
  • Ability to change sound sampling rate. As mentioned in #4, if the frontend detects high load, reducing the sampling rate may help. Another item for an APU to implement.
  • Ability to switch interlaced mode off and on. Drawing half the screen can be skipped to reduce load.

core: add selectable, internal peripheral support

Implement peripherals that may be connected to the Game Boy serial port. Then allow the front-end to select which peripheral is connected.

Example below:

enum serial_peripherals {
SERIAL_NOT_CONNECTED, /* Emulate no serial device connected (Default). */
SERIAL_PRINTER, /* Emulate printer connected. */
SERIAL_EXTERNAL /* Send serial output to front-end (eg. for multiplayer). */
};

/* Must be set before switching to printer peripheral. */
int gb_serial_printer_init((void printer_output)(gb_t *gb, uint8_t *buffer));

/* To set the connected peripheral. Returns error if peripheral not initialised. */
int gb_serial_set_peripheral(enum serial_peripherals);

sdl2: allow button remap

Users use different keyboards, so the hard-coded button mappings make it difficult for them to test and play Peanut-SDL.

Serial: Use separate tx and rx frontend functions

The current implementation expects an immediate response from the frontend when a byte is transmitted over a serial connection. This will pause emulation until the frontend returns from the gb_serial_transfer() function.

Therefore, I propose a system when the emulator calls a gb_serial_tx() frontend function which transmits a byte, and then calls a gb_serial_rx() when the emulator expects a byte to be received on the serial connection.

Since the serial link on a DMG has a frequency of 8192Hz, the frontend function will have just under 1ms to transmit and receive a byte. A microcontroller with an SPI peripheral will be able to handle the TX/RX whilst the emulator is still running.

I'm still unsure whether doing this is a good idea. As having a single frontend function for this reduces complexity. However, I don't think having the emulator pause whilst the frontend sends and receives data is good.

core: freeze in Pokemon RBY with ENABLE_SOUND=0

Pokemon Red and Blue (possibly Yellow) will freeze when white out or Pokemon Center heal if the Peanut-GB SDL2 example is compiled without APU emulation (ENABLE_SOUND=0).

Peanut-GB should include barebones audio emulation, or require an APU emulation at build time.

no input possible on certain roms

Hi,

I've ported this emulator to a ESP32 based embedded system.
While the main functions work fine, I'm having some trouble with keypad inputs.

Super Mario Land: input works fine
Tetris: no input possible
this is where I got my roms: [Edit: Removed]

Black box when falling in hole in Libbet and the Magic Floor

If bit 7 of a sprite's attributes ($FE03, $FE07, $FE0B, ..., $FE9F) is true, the sprite's pixels are supposed to be drawn behind any background pixels that are not color 0. The comparison with color 0 is supposed to happen before BGP is applied.

To reproduce:

  1. Open Libbet and the Magic Floor v0.04
  2. Press Select to start the demo
  3. Wait for Libbet to roll to the dead end at the top right corner

Expect: Libbet smoothly falls into the hole

Actual: A black square appears on the white cell below Libbet, which would otherwise have been hidden behind the background.

apu: Add sound support

  • Process timers for various audio features: length counter, volume envelope, and frequency sweep.
  • Add support for the four channels, starting with the simplest.
  • Write a fast audio mixer.
  • Record an audio sample based on the given sampling frequency, into an array allocated by the front-end.
  • Queue the array of audio samples to the front-end. With SDL2, this will be done using the SDL_QueueAudio function.
  • Add defines around sound code to add/remove support at compile time. This will reduce binary size for systems with extremely small programming space (< 64KB for example).

Extra tasks:

  • Allow for the sample rate to be modified on the fly. *
  • Allow for the disabling of specific channels and all channels. *

* This may be useful for reducing load on embedded systems. For example, if LCD drawing is taking too long due to full screen refresh, the sampling rate of sound may be reduced for 100ms.

It would be great if floating point values wouldn't have to be used, as some embedded hardware do not have a floating point unit (FPU).

Samples should be in unsigned 16 bit format, as this is best supported on embedded hardware. On systems that use 12 bit registers for DAC or PWM (PWM may be used in replacement of a DAC if one isn't available), it will be easy to bit shift the 16 bit sample. I don't see why floating point samples would be any good here.

Obscure behaviour that is detrimental to performance may not be considered.

input: use various methods of updating button presses

Create a gb_init_input() function that allows the front-end to configure how to update the button presses.

  1. (Default) Calling a function already defined in the library at any time (eg. for an interrupt) to update the joypad registers.
  2. Calling a given front-end function whenever the game reads the joypad input registers.

apu: replace internal samples buffer with one given by SDL audio callback

SDL2 provides an allocated buffer with its length to the audio callback function. Therefore, peanut_apu does not require any internal buffer. The buffer and the size of which should be passed to the functions called by audio_update(), where the SDL2 buffer replaces "samples" and the length of the buffer replaces "nsamples".

I hope to remove all memory allocation functions and things like memmove() too.

core: serial code interfering with incompatible games

Hi,
after quite some debugging I found out that Tetris won't work as long as the serial support is enabled. If you remove the serial interrupt and transfer code, Tetris will fully work again.

However, I haven't figured out the exact cause.

all: fix code style

In some places, the code exceeds the 80-char limit massively. Some inconsistencies exist too.
I would like to use a code formatter like clang-format to automatically format my code. The amount of code modified should be kept minimal, so clang-format should be configured to use this current code style as much as possible. I don't want a massive thousand-line change commit for code style changes.
Things like case statements shouldn't be indented as they are currently, in order to increase the amount of space on a line.

Freeze on entering Pokemon Center in Pokemon Yellow

Bug started in commit c22ea85, where basic serial transmission was implemented.
This commit had fixed a crash when attempting to use the printer in the game, but now you can't enter a Pokemon Center.

Implementing correct timing for serial transmission may fix this issue.

First frame after reenabling LCD isn't white

When the program turns on rendering by changing bit 7 of LCDC ($FF40) from 0 to 1, the PPU goes straight to the start of rendering (scanline 0) without sending the vsync pulse to the LCD. This causes the LCD to display a white screen for one frame. (On monochrome systems, this is the same whiter-than-white shade displayed when rendering is off.) Some games, such as Pokémon Pinball, use this blank frame to prepare things before the first visible frame, such as the sprite display list to be DMA copied to OAM during the first vblank.

Though recent BGB emulates this quirk, mGBA, WasmBoy, and Peanut-GB do not. This causes 1 frame of corruption to appear in Pokémon Pinball and numerous other games after a screen transition. Many games on KiGB's compatibility list are there because of this quirk. Affected games may include A Boy and His Blob, Boxing by Tonkin House, Captain Knick-Knack, HyperDunk, Hyper Lode Runner, and Xiang Pu -- Dong Hai Dao Chang Suo.

I've attached a test ROM for this quirk, which repeatedly turns the LCD on for one frame before turning it off.

  • platform: Xubuntu 18.04 (amd64)
  • CPU: Pentium N3710
  • ROM used: The first frame is always white
  • expect: screen remains white (all but SGB) or rolling image (SGB)
  • actual: "Incorrect" message

Joypad interrupt does not fire

The Lawnmower Man for Game Boy reportedly will not start unless joypad interrupts are implemented.

To reproduce:

  1. Build Peanut-GB on Xubuntu 18.04
  2. Start the attached test ROM "Telling LYs?" tellinglys-0.02.zip
  3. Press any button

Expect: Display advances from title screen to conversation

Actual: Title screen does not disappear

(If you do issue joypad interrupts, but you don't vary the LY when they're issued, you'll get a different error message.)

sdl2: Use 8-bit surface for pixel data

Use SDL_GetRendererInfo() to find out what the native texture format is for the used driver, then convert the RGB555 values to whatever format is detected.
Using a texture format that is native to the system should improve performance on systems that do not natively support RGB555.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.