Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
boytacean
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
João Magalhães
boytacean
Merge requests
!29
Support for Libretro
Code
Review changes
Check out branch
Download
Patches
Plain diff
Merged
Support for Libretro
joamag/libretro
into
master
Overview
2
Commits
42
Pipelines
16
Changes
14
Merged
João Magalhães
requested to merge
joamag/libretro
into
master
1 year ago
Overview
2
Commits
42
Pipelines
16
Changes
1
Expand
Related to
#14 (closed)
1
0
Merge request reports
Compare
version 12
version 15
40a7d70e
1 year ago
version 14
706af2b1
1 year ago
version 13
eb81366f
1 year ago
version 12
ba1ad37f
1 year ago
version 11
e5b15376
1 year ago
version 10
54f9545c
1 year ago
version 9
e749a2ea
1 year ago
version 8
46004bf8
1 year ago
version 7
762debc5
1 year ago
version 6
8ee09c56
1 year ago
version 5
3d0c0ab4
1 year ago
version 4
2faa3e75
1 year ago
version 3
90f38b2c
1 year ago
version 2
dc2ddce9
1 year ago
version 1
2838bdd4
1 year ago
master (base)
and
version 13
latest version
3359ef33
42 commits,
1 year ago
version 15
40a7d70e
41 commits,
1 year ago
version 14
706af2b1
40 commits,
1 year ago
version 13
eb81366f
39 commits,
1 year ago
version 12
ba1ad37f
38 commits,
1 year ago
version 11
e5b15376
37 commits,
1 year ago
version 10
54f9545c
36 commits,
1 year ago
version 9
e749a2ea
35 commits,
1 year ago
version 8
46004bf8
34 commits,
1 year ago
version 7
762debc5
32 commits,
1 year ago
version 6
8ee09c56
31 commits,
1 year ago
version 5
3d0c0ab4
30 commits,
1 year ago
version 4
2faa3e75
29 commits,
1 year ago
version 3
90f38b2c
28 commits,
1 year ago
version 2
dc2ddce9
27 commits,
1 year ago
version 1
2838bdd4
26 commits,
1 year ago
Show latest version
1 file
+
26
−
26
Inline
Compare changes
Side-by-side
Inline
Show whitespace changes
Show one file at a time
frontends/libretro/src/lib.rs
0 → 100644
+
425
−
0
Options
#![allow(clippy::uninlined_format_args)]
pub
mod
consts
;
use
std
::{
collections
::
HashMap
,
fmt
::{
self
,
Display
,
Formatter
},
os
::
raw
::{
c_char
,
c_float
,
c_uint
,
c_void
},
slice
::
from_raw_parts
,
};
use
boytacean
::{
debugln
,
gb
::{
AudioProvider
,
GameBoy
},
pad
::
PadKey
,
ppu
::{
DISPLAY_HEIGHT
,
DISPLAY_WIDTH
,
FRAME_BUFFER_RGB155_SIZE
,
RGB1555_SIZE
},
rom
::
Cartridge
,
};
use
consts
::{
RETRO_DEVICE_ID_JOYPAD_A
,
RETRO_DEVICE_ID_JOYPAD_B
,
RETRO_DEVICE_ID_JOYPAD_DOWN
,
RETRO_DEVICE_ID_JOYPAD_L
,
RETRO_DEVICE_ID_JOYPAD_L2
,
RETRO_DEVICE_ID_JOYPAD_L3
,
RETRO_DEVICE_ID_JOYPAD_LEFT
,
RETRO_DEVICE_ID_JOYPAD_R
,
RETRO_DEVICE_ID_JOYPAD_R2
,
RETRO_DEVICE_ID_JOYPAD_R3
,
RETRO_DEVICE_ID_JOYPAD_RIGHT
,
RETRO_DEVICE_ID_JOYPAD_SELECT
,
RETRO_DEVICE_ID_JOYPAD_START
,
RETRO_DEVICE_ID_JOYPAD_UP
,
RETRO_DEVICE_ID_JOYPAD_X
,
RETRO_DEVICE_ID_JOYPAD_Y
,
RETRO_DEVICE_JOYPAD
,
};
use
crate
::
consts
::{
REGION_NTSC
,
RETRO_API_VERSION
};
static
mut
EMULATOR
:
Option
<
GameBoy
>
=
None
;
static
mut
KEY_STATES
:
Option
<
HashMap
<
RetroJoypad
,
bool
>>
=
None
;
static
mut
FRAME_BUFFER
:
[
u8
;
FRAME_BUFFER_RGB155_SIZE
]
=
[
0x00
;
FRAME_BUFFER_RGB155_SIZE
];
static
mut
PENDING_CYCLES
:
u32
=
0_u32
;
static
mut
ENVIRONMENT_CALLBACK
:
Option
<
extern
"C"
fn
(
u32
,
*
const
c_void
)
->
bool
>
=
None
;
static
mut
VIDEO_REFRESH_CALLBACK
:
Option
<
extern
"C"
fn
(
*
const
u8
,
c_uint
,
c_uint
,
usize
)
>
=
None
;
static
mut
AUDIO_SAMPLE_CALLBACK
:
Option
<
extern
"C"
fn
(
i16
,
i16
)
>
=
None
;
static
mut
AUDIO_SAMPLE_BATCH_CALLBACK
:
Option
<
extern
"C"
fn
(
*
const
i16
,
usize
)
>
=
None
;
static
mut
INPUT_POLL_CALLBACK
:
Option
<
extern
"C"
fn
()
>
=
None
;
static
mut
INPUT_STATE_CALLBACK
:
Option
<
extern
"C"
fn
(
port
:
u32
,
device
:
u32
,
index
:
u32
,
id
:
u32
)
->
i16
,
>
=
None
;
const
KEYS
:
[
RetroJoypad
;
8
]
=
[
RetroJoypad
::
RetroDeviceIdJoypadUp
,
RetroJoypad
::
RetroDeviceIdJoypadDown
,
RetroJoypad
::
RetroDeviceIdJoypadLeft
,
RetroJoypad
::
RetroDeviceIdJoypadRight
,
RetroJoypad
::
RetroDeviceIdJoypadStart
,
RetroJoypad
::
RetroDeviceIdJoypadSelect
,
RetroJoypad
::
RetroDeviceIdJoypadA
,
RetroJoypad
::
RetroDeviceIdJoypadB
,
];
#[derive(Clone,
Copy,
PartialEq,
Eq,
Hash)]
pub
enum
RetroJoypad
{
RetroDeviceIdJoypadB
=
RETRO_DEVICE_ID_JOYPAD_B
,
RetroDeviceIdJoypadY
=
RETRO_DEVICE_ID_JOYPAD_Y
,
RetroDeviceIdJoypadSelect
=
RETRO_DEVICE_ID_JOYPAD_SELECT
,
RetroDeviceIdJoypadStart
=
RETRO_DEVICE_ID_JOYPAD_START
,
RetroDeviceIdJoypadUp
=
RETRO_DEVICE_ID_JOYPAD_UP
,
RetroDeviceIdJoypadDown
=
RETRO_DEVICE_ID_JOYPAD_DOWN
,
RetroDeviceIdJoypadLeft
=
RETRO_DEVICE_ID_JOYPAD_LEFT
,
RetroDeviceIdJoypadRight
=
RETRO_DEVICE_ID_JOYPAD_RIGHT
,
RetroDeviceIdJoypadA
=
RETRO_DEVICE_ID_JOYPAD_A
,
RetroDeviceIdJoypadX
=
RETRO_DEVICE_ID_JOYPAD_X
,
RetroDeviceIdJoypadL
=
RETRO_DEVICE_ID_JOYPAD_L
,
RetroDeviceIdJoypadR
=
RETRO_DEVICE_ID_JOYPAD_R
,
RetroDeviceIdJoypadL2
=
RETRO_DEVICE_ID_JOYPAD_L2
,
RetroDeviceIdJoypadR2
=
RETRO_DEVICE_ID_JOYPAD_R2
,
RetroDeviceIdJoypadL3
=
RETRO_DEVICE_ID_JOYPAD_L3
,
RetroDeviceIdJoypadR3
=
RETRO_DEVICE_ID_JOYPAD_R3
,
}
impl
RetroJoypad
{
pub
fn
description
(
&
self
)
->
&
'static
str
{
match
self
{
RetroJoypad
::
RetroDeviceIdJoypadY
=>
"Y"
,
RetroJoypad
::
RetroDeviceIdJoypadB
=>
"B"
,
RetroJoypad
::
RetroDeviceIdJoypadSelect
=>
"Select"
,
RetroJoypad
::
RetroDeviceIdJoypadStart
=>
"Start"
,
RetroJoypad
::
RetroDeviceIdJoypadUp
=>
"Up"
,
RetroJoypad
::
RetroDeviceIdJoypadDown
=>
"Down"
,
RetroJoypad
::
RetroDeviceIdJoypadLeft
=>
"Left"
,
RetroJoypad
::
RetroDeviceIdJoypadRight
=>
"Right"
,
RetroJoypad
::
RetroDeviceIdJoypadA
=>
"A"
,
RetroJoypad
::
RetroDeviceIdJoypadX
=>
"X"
,
RetroJoypad
::
RetroDeviceIdJoypadL
=>
"L"
,
RetroJoypad
::
RetroDeviceIdJoypadR
=>
"R"
,
RetroJoypad
::
RetroDeviceIdJoypadL2
=>
"L2"
,
RetroJoypad
::
RetroDeviceIdJoypadR2
=>
"R2"
,
RetroJoypad
::
RetroDeviceIdJoypadL3
=>
"L3"
,
RetroJoypad
::
RetroDeviceIdJoypadR3
=>
"R3"
,
}
}
}
impl
Display
for
RetroJoypad
{
fn
fmt
(
&
self
,
f
:
&
mut
Formatter
<
'_
>
)
->
fmt
::
Result
{
write!
(
f
,
"{}"
,
self
.description
())
}
}
#[repr(C)]
pub
struct
RetroGameInfo
{
pub
path
:
*
const
c_char
,
pub
data
:
*
const
c_void
,
pub
size
:
usize
,
pub
meta
:
*
const
c_char
,
}
#[repr(C)]
pub
struct
RetroSystemInfo
{
pub
library_name
:
*
const
c_char
,
pub
library_version
:
*
const
c_char
,
pub
valid_extensions
:
*
const
c_char
,
pub
need_fullpath
:
bool
,
pub
block_extract
:
bool
,
}
#[repr(C)]
pub
struct
RetroGameGeometry
{
pub
base_width
:
c_uint
,
pub
base_height
:
c_uint
,
pub
max_width
:
c_uint
,
pub
max_height
:
c_uint
,
pub
aspect_ratio
:
c_float
,
}
#[repr(C)]
pub
struct
RetroSystemAvInfo
{
geometry
:
RetroGameGeometry
,
timing
:
RetroSystemTiming
,
}
#[repr(C)]
pub
struct
RetroSystemTiming
{
fps
:
f64
,
sample_rate
:
f64
,
}
#[no_mangle]
pub
extern
"C"
fn
retro_api_version
()
->
c_uint
{
debugln!
(
"retro_api_version()"
);
RETRO_API_VERSION
}
#[no_mangle]
pub
extern
"C"
fn
retro_init
()
{
debugln!
(
"retro_init()"
);
unsafe
{
EMULATOR
=
Some
(
GameBoy
::
new
(
None
));
KEY_STATES
=
Some
(
HashMap
::
new
());
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_deinit
()
{
debugln!
(
"retro_deinit()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_reset
()
{
debugln!
(
"retro_reset()"
);
let
emulator
=
unsafe
{
EMULATOR
.as_mut
()
.unwrap
()
};
emulator
.reload
();
}
/// # Safety
///
/// This function should not be called only within Lib Retro context.
#[no_mangle]
pub
unsafe
extern
"C"
fn
retro_get_system_info
(
info
:
*
mut
RetroSystemInfo
)
{
debugln!
(
"retro_get_system_info()"
);
(
*
info
)
.library_name
=
"Boytacean
\0
"
.as_ptr
()
as
*
const
c_char
;
(
*
info
)
.library_version
=
"v0.9.6
\0
"
.as_ptr
()
as
*
const
c_char
;
(
*
info
)
.valid_extensions
=
"gb|gbc
\0
"
.as_ptr
()
as
*
const
c_char
;
(
*
info
)
.need_fullpath
=
false
;
(
*
info
)
.block_extract
=
false
;
}
/// # Safety
///
/// This function should not be called only within Lib Retro context.
#[no_mangle]
pub
unsafe
extern
"C"
fn
retro_get_system_av_info
(
info
:
*
mut
RetroSystemAvInfo
)
{
debugln!
(
"retro_get_system_av_info()"
);
(
*
info
)
.geometry.base_width
=
DISPLAY_WIDTH
as
u32
;
(
*
info
)
.geometry.base_height
=
DISPLAY_HEIGHT
as
u32
;
(
*
info
)
.geometry.max_width
=
DISPLAY_WIDTH
as
u32
*
64
;
(
*
info
)
.geometry.max_height
=
DISPLAY_HEIGHT
as
u32
*
64
;
(
*
info
)
.geometry.aspect_ratio
=
DISPLAY_WIDTH
as
f32
/
DISPLAY_HEIGHT
as
f32
;
(
*
info
)
.timing.fps
=
GameBoy
::
VISUAL_FREQ
as
f64
;
(
*
info
)
.timing.sample_rate
=
EMULATOR
.as_ref
()
.unwrap
()
.audio_sampling_rate
()
as
f64
;
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_environment
(
callback
:
Option
<
extern
"C"
fn
(
u32
,
*
const
c_void
)
->
bool
>
,
)
{
debugln!
(
"retro_set_environment()"
);
unsafe
{
ENVIRONMENT_CALLBACK
=
callback
;
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_controller_port_device
()
{
debugln!
(
"retro_set_controller_port_device()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_run
()
{
let
emulator
=
unsafe
{
EMULATOR
.as_mut
()
.unwrap
()
};
let
video_refresh_cb
=
unsafe
{
VIDEO_REFRESH_CALLBACK
.as_ref
()
.unwrap
()
};
let
sample_batch_cb
=
unsafe
{
AUDIO_SAMPLE_BATCH_CALLBACK
.as_ref
()
.unwrap
()
};
let
input_poll_cb
=
unsafe
{
INPUT_POLL_CALLBACK
.as_ref
()
.unwrap
()
};
let
input_state_cb
=
unsafe
{
INPUT_STATE_CALLBACK
.as_ref
()
.unwrap
()
};
let
key_states
=
unsafe
{
KEY_STATES
.as_mut
()
.unwrap
()
};
let
channels
=
emulator
.audio_channels
();
let
mut
last_frame
=
emulator
.ppu_frame
();
let
mut
counter_cycles
=
unsafe
{
PENDING_CYCLES
};
let
cycle_limit
=
(
GameBoy
::
CPU_FREQ
as
f32
*
emulator
.multiplier
()
as
f32
/
GameBoy
::
VISUAL_FREQ
)
.round
()
as
u32
;
loop
{
// limits the number of ticks to the typical number
// of cycles expected for the current logic cycle
if
counter_cycles
>=
cycle_limit
{
unsafe
{
PENDING_CYCLES
=
counter_cycles
-
cycle_limit
};
break
;
}
// runs the Game Boy clock, this operation should
// include the advance of both the CPU, PPU, APU
// and any other frequency based component of the system
counter_cycles
+=
emulator
.clock
()
as
u32
;
// in case a new frame is available in the emulator
// then the frame must be pushed into display
if
emulator
.ppu_frame
()
!=
last_frame
{
let
frame_buffer
=
emulator
.frame_buffer_rgb1555
();
unsafe
{
FRAME_BUFFER
.copy_from_slice
(
&
frame_buffer
);
video_refresh_cb
(
FRAME_BUFFER
.as_ptr
(),
DISPLAY_WIDTH
as
u32
,
DISPLAY_HEIGHT
as
u32
,
DISPLAY_WIDTH
*
RGB1555_SIZE
,
);
}
// obtains the index of the current PPU frame, this value
// is going to be used to detect for new frame presence
last_frame
=
emulator
.ppu_frame
();
}
// obtains the audio buffer reference and queues it
// in a batch manner using the audio callback at the
// the end of the operation clears the buffer
let
audio_buffer
=
emulator
.audio_buffer
()
.iter
()
.map
(|
v
|
*
v
as
i16
*
256
)
.collect
::
<
Vec
<
i16
>>
();
sample_batch_cb
(
audio_buffer
.as_ptr
(),
audio_buffer
.len
()
/
channels
as
usize
,
);
emulator
.clear_audio_buffer
();
}
input_poll_cb
();
for
key
in
KEYS
{
let
key_pad
=
retro_key_to_pad
(
key
)
.unwrap
();
let
current
=
input_state_cb
(
0
,
RETRO_DEVICE_JOYPAD
as
u32
,
0
,
key
as
u32
)
>
0
;
let
previous
=
key_states
.get
(
&
key
)
.unwrap_or
(
&
false
);
if
current
!=
*
previous
{
if
current
{
emulator
.key_press
(
key_pad
);
}
else
{
emulator
.key_lift
(
key_pad
);
}
}
key_states
.insert
(
key
,
current
);
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_get_region
()
->
u32
{
debugln!
(
"retro_get_region()"
);
REGION_NTSC
}
/// # Safety
///
/// This function should not be called only within Lib Retro context.
#[no_mangle]
pub
unsafe
extern
"C"
fn
retro_load_game
(
game
:
*
const
RetroGameInfo
)
->
bool
{
debugln!
(
"retro_load_game()"
);
let
instance
=
EMULATOR
.as_mut
()
.unwrap
();
let
data_buffer
=
from_raw_parts
((
*
game
)
.data
as
*
const
u8
,
(
*
game
)
.size
);
let
rom
=
Cartridge
::
from_data
(
data_buffer
);
let
mode
=
rom
.gb_mode
();
instance
.set_mode
(
mode
);
instance
.reset
();
instance
.load
(
true
);
instance
.load_cartridge
(
rom
);
true
}
#[no_mangle]
pub
extern
"C"
fn
retro_load_game_special
(
_system
:
u32
,
_info
:
*
const
RetroGameInfo
,
_num_info
:
usize
,
)
->
bool
{
debugln!
(
"retro_load_game_special()"
);
false
}
#[no_mangle]
pub
extern
"C"
fn
retro_unload_game
()
{
debugln!
(
"retro_unload_game()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_get_memory_data
(
_memory_id
:
u32
)
->
*
mut
c_void
{
debugln!
(
"retro_get_memory_data()"
);
std
::
ptr
::
null_mut
()
}
#[no_mangle]
pub
extern
"C"
fn
retro_get_memory_size
(
_memory_id
:
u32
)
->
usize
{
debugln!
(
"retro_get_memory_size()"
);
0
}
#[no_mangle]
pub
extern
"C"
fn
retro_serialize_size
()
{
debugln!
(
"retro_serialize_size()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_serialize
()
{
debugln!
(
"retro_serialize()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_unserialize
()
{
debugln!
(
"retro_unserialize()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_cheat_reset
()
{
debugln!
(
"retro_cheat_reset()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_cheat_set
()
{
debugln!
(
"retro_cheat_set()"
);
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_video_refresh
(
callback
:
Option
<
extern
"C"
fn
(
*
const
u8
,
c_uint
,
c_uint
,
usize
)
>
,
)
{
debugln!
(
"retro_set_video_refresh()"
);
unsafe
{
VIDEO_REFRESH_CALLBACK
=
callback
;
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_audio_sample
(
callback
:
Option
<
extern
"C"
fn
(
i16
,
i16
)
>
)
{
debugln!
(
"retro_set_audio_sample()"
);
unsafe
{
AUDIO_SAMPLE_CALLBACK
=
callback
;
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_audio_sample_batch
(
callback
:
Option
<
extern
"C"
fn
(
*
const
i16
,
usize
)
>
)
{
debugln!
(
"retro_set_audio_sample_batch()"
);
unsafe
{
AUDIO_SAMPLE_BATCH_CALLBACK
=
callback
;
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_input_poll
(
callback
:
Option
<
extern
"C"
fn
()
>
)
{
debugln!
(
"retro_set_input_poll()"
);
unsafe
{
INPUT_POLL_CALLBACK
=
callback
;
}
}
#[no_mangle]
pub
extern
"C"
fn
retro_set_input_state
(
callback
:
Option
<
extern
"C"
fn
(
port
:
u32
,
device
:
u32
,
index
:
u32
,
id
:
u32
)
->
i16
>
,
)
{
debugln!
(
"retro_set_input_state()"
);
unsafe
{
INPUT_STATE_CALLBACK
=
callback
;
}
}
fn
retro_key_to_pad
(
retro_key
:
RetroJoypad
)
->
Option
<
PadKey
>
{
match
retro_key
{
RetroJoypad
::
RetroDeviceIdJoypadUp
=>
Some
(
PadKey
::
Up
),
RetroJoypad
::
RetroDeviceIdJoypadDown
=>
Some
(
PadKey
::
Down
),
RetroJoypad
::
RetroDeviceIdJoypadLeft
=>
Some
(
PadKey
::
Left
),
RetroJoypad
::
RetroDeviceIdJoypadRight
=>
Some
(
PadKey
::
Right
),
RetroJoypad
::
RetroDeviceIdJoypadStart
=>
Some
(
PadKey
::
Start
),
RetroJoypad
::
RetroDeviceIdJoypadSelect
=>
Some
(
PadKey
::
Select
),
RetroJoypad
::
RetroDeviceIdJoypadA
=>
Some
(
PadKey
::
A
),
RetroJoypad
::
RetroDeviceIdJoypadB
=>
Some
(
PadKey
::
B
),
_
=>
None
,
}
}
Loading