Compare commits

..

No commits in common. "main" and "v1.0.13" have entirely different histories.

35 changed files with 720 additions and 820 deletions

View file

@ -1,18 +0,0 @@
name: Security audit
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
audit:
permissions:
issues: write
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}

443
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "1.0.15"
version = "1.0.13"
edition = "2024"
rust-version = "1.85"
@ -8,8 +8,8 @@ rust-version = "1.85"
[dependencies]
dirs = "6.0"
zip = "2.5"
governor = "0.10"
zip = "2.2"
governor = "0.8"
sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
@ -17,7 +17,7 @@ serde_json = "1.0"
serde-big-array = "0.5"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "glow"] }
sha2 = "0.10"
ab_glyph = "0.2"
rusttype = "0.9"
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
@ -35,7 +35,7 @@ clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
[build-dependencies]
winresource = "0.1"
winres = "0.1"
bindgen = "0.71"
cc = { version = "1.2", features = ["parallel"] }

View file

@ -15,17 +15,17 @@ https://discord.gg/9RGXq8W8JQ
Keys are mapped according to [these defaults](https://github.com/gopher64/gopher64/wiki/Default-Keyboard-Setup). Xbox-style controllers also have a [default mapping applied](https://github.com/gopher64/gopher64/wiki/Default-Gamepad-Setup).
You can create your mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
To use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
## netplay
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion or contact me on Discord). You can also run the server (https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion, or contact me on Discord). You can also run the server (https://github.com/simple64/simple64-netplay-server) yourself on a LAN.
## portable mode
If you would like to keep all the game data in the same folder as the executable, you just need to create a file called "portable.txt" in the same directory as the executable.
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
## flatpak
@ -39,7 +39,7 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
1. Performance. I want to be able to use this emulator on my laptop.
2. Easy to use.
3. Easy to work on. Dynamic recompilers perform well, but they are very hard to read and understand. This emulator will only have interpreters for the CPU and RSP. Additionally, it is completely written in Rust (besides Parallel-RDP), a modern programming language with a growing user base. I've tried to avoid the use of macros, which can reduce some repetitiveness in the code but also reduce readability.
3. Easy to work on. Dynamic recompilers perform well, but they are very hard to read and understand. This emulator will only have interpreters for the CPU and RSP. Additionally, it is completely written in Rust (besides Parallel-RDP), a modern programming language with a growing user base. I've tried to avoid the use of macros, which can reduce some repetitiveness in the code, but also reduce readability.
## building and usage
@ -52,7 +52,7 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
## contributing
I am very open to contributions! Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
I am very open to contributions! Please reach out to me via a GitHub issue, or via discord (loganmc10) before doing substantial work on a PR.
## license

View file

@ -59,16 +59,9 @@ fn main() {
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
if os == "windows" {
if arch == "x86_64" {
if env == "msvc" {
build.flag("/arch:AVX2");
} else if env == "gnu" {
build.flag("-march=x86-64-v3");
} else {
panic!("unknown env")
}
build.flag("/arch:AVX2");
} else if arch == "aarch64" {
panic!("unsupported platform")
} else {
@ -76,7 +69,7 @@ fn main() {
}
build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
winresource::WindowsResource::new()
winres::WindowsResource::new()
.set_icon("data/icon.ico")
.compile()
.unwrap();
@ -109,9 +102,7 @@ fn main() {
.allowlist_function("rdp_check_callback")
.allowlist_function("rdp_new_processor")
.allowlist_function("rdp_check_framebuffers")
.allowlist_function("rdp_state_size")
.allowlist_function("rdp_save_state")
.allowlist_function("rdp_load_state")
.allowlist_function("rdp_full_sync")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");

View file

@ -52,41 +52,31 @@ enum vi_registers
VI_REGS_COUNT
};
typedef struct
{
uint32_t depthbuffer_address;
uint32_t framebuffer_address;
uint32_t texture_address;
uint32_t framebuffer_pixel_size;
uint32_t framebuffer_width;
uint32_t texture_pixel_size;
uint32_t texture_width;
uint32_t framebuffer_height;
} FrameBufferInfo;
typedef struct
{
uint32_t cmd_data[0x00040000 >> 2];
int cmd_cur;
int cmd_ptr;
uint32_t region;
FrameBufferInfo frame_buffer_info;
} RDP_DEVICE;
static SDL_Window *window;
static RDP::CommandProcessor *processor;
static SDL_WSIPlatform *wsi_platform;
static WSI *wsi;
static RDP_DEVICE rdp_device;
static bool crop_letterbox;
static uint32_t cmd_data[0x00040000 >> 2];
static int cmd_cur;
static int cmd_ptr;
static CALL_BACK callback;
static GFX_INFO gfx_info;
static uint32_t region;
static bool crop_letterbox;
static const uint32_t *fragment_spirv;
static size_t fragment_size;
std::vector<bool> rdram_dirty;
uint64_t sync_signal;
typedef struct
{
uint32_t depthbuffer_address;
uint32_t framebuffer_address;
uint32_t framebuffer_pixel_size;
uint32_t framebuffer_width;
uint32_t framebuffer_height;
uint32_t framebuffer_size;
uint32_t depthbuffer_size;
uint8_t depthbuffer_enabled;
} FrameBufferInfo;
typedef struct
{
@ -94,6 +84,10 @@ typedef struct
float OutputSize[4];
} Push;
static std::vector<bool> rdram_dirty;
static uint64_t sync_signal;
static FrameBufferInfo frame_buffer_info;
static const unsigned cmd_len_lut[64] = {
1,
1,
@ -201,12 +195,6 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
case SDL_SCANCODE_F7:
callback.load_state = true;
break;
case SDL_SCANCODE_LEFTBRACKET:
callback.lower_volume = true;
break;
case SDL_SCANCODE_RIGHTBRACKET:
callback.raise_volume = true;
break;
default:
break;
}
@ -217,14 +205,11 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
void rdp_new_processor(GFX_INFO _gfx_info)
{
gfx_info = _gfx_info;
memset(&frame_buffer_info, 0, sizeof(FrameBufferInfo));
sync_signal = 0;
rdram_dirty.assign(gfx_info.RDRAM_SIZE / 8, false);
rdp_device.frame_buffer_info.framebuffer_address = rdram_dirty.size();
rdp_device.frame_buffer_info.depthbuffer_address = rdram_dirty.size();
rdp_device.frame_buffer_info.texture_address = rdram_dirty.size();
gfx_info = _gfx_info;
if (processor)
{
delete processor;
@ -464,8 +449,6 @@ CALL_BACK rdp_check_callback()
CALL_BACK return_value = callback;
callback.save_state = false;
callback.load_state = false;
callback.lower_volume = false;
callback.raise_volume = false;
return return_value;
}
@ -479,61 +462,29 @@ void rdp_check_framebuffers(uint32_t address)
}
}
size_t rdp_state_size()
{
return sizeof(RDP_DEVICE);
}
void rdp_save_state(uint8_t *state)
void rdp_full_sync()
{
processor->wait_for_timeline(processor->signal_timeline());
memcpy(state, &rdp_device, sizeof(RDP_DEVICE));
}
void rdp_load_state(const uint8_t *state)
void calculate_buffer_size()
{
memcpy(&rdp_device, state, sizeof(RDP_DEVICE));
}
uint32_t texture_size(uint32_t area)
{
switch (rdp_device.frame_buffer_info.texture_pixel_size)
switch (frame_buffer_info.framebuffer_pixel_size)
{
case 0:
return (area / 2) >> 3;
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height / 2) >> 3;
break;
case 1:
return (area) >> 3;
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height) >> 3;
break;
case 2:
return (area * 2) >> 3;
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
break;
case 3:
return (area * 4) >> 3;
default:
printf("Invalid texture pixel size: %u\n", rdp_device.frame_buffer_info.texture_pixel_size);
return 0;
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 4) >> 3;
break;
}
}
uint32_t framebuffer_size()
{
switch (rdp_device.frame_buffer_info.framebuffer_pixel_size)
{
case 0:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height / 2) >> 3;
case 1:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) >> 3;
case 2:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height * 2) >> 3;
case 3:
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height * 4) >> 3;
default:
printf("Invalid framebuffer pixel size: %u\n", rdp_device.frame_buffer_info.framebuffer_pixel_size);
return 0;
}
}
uint32_t depthbuffer_size()
{
return (rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height * 2) >> 3;
frame_buffer_info.depthbuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
}
uint64_t rdp_process_commands()
@ -547,7 +498,7 @@ uint64_t rdp_process_commands()
return interrupt_timer;
length = unsigned(length) >> 3;
if ((rdp_device.cmd_ptr + length) & ~(0x0003FFFF >> 3))
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
return interrupt_timer;
uint32_t offset = DP_CURRENT;
@ -556,10 +507,10 @@ uint64_t rdp_process_commands()
do
{
offset &= 0xFF8;
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
cmd_data[2 * cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
cmd_data[2 * cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
offset += sizeof(uint64_t);
rdp_device.cmd_ptr++;
cmd_ptr++;
} while (--length > 0);
}
else
@ -573,118 +524,83 @@ uint64_t rdp_process_commands()
do
{
offset &= 0xFFFFF8;
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset);
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset + 4);
cmd_data[2 * cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset);
cmd_data[2 * cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset + 4);
offset += sizeof(uint64_t);
rdp_device.cmd_ptr++;
cmd_ptr++;
} while (--length > 0);
}
}
while (rdp_device.cmd_cur - rdp_device.cmd_ptr < 0)
while (cmd_cur - cmd_ptr < 0)
{
uint32_t w1 = rdp_device.cmd_data[2 * rdp_device.cmd_cur];
uint32_t w2 = rdp_device.cmd_data[2 * rdp_device.cmd_cur + 1];
uint32_t w1 = cmd_data[2 * cmd_cur];
uint32_t w2 = cmd_data[2 * cmd_cur + 1];
uint32_t command = (w1 >> 24) & 63;
int cmd_length = cmd_len_lut[command];
if (rdp_device.cmd_ptr - rdp_device.cmd_cur - cmd_length < 0)
if (cmd_ptr - cmd_cur - cmd_length < 0)
{
*gfx_info.DPC_START_REG = *gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
return interrupt_timer;
}
if (command >= 8)
processor->enqueue_command(cmd_length * 2, &rdp_device.cmd_data[2 * rdp_device.cmd_cur]);
processor->enqueue_command(cmd_length * 2, &cmd_data[2 * cmd_cur]);
switch (RDP::Op(command))
if ((RDP::Op(command) >= RDP::Op::FillTriangle && RDP::Op(command) <= RDP::Op::ShadeTextureZBufferTriangle) ||
RDP::Op(command) == RDP::Op::TextureRectangle ||
RDP::Op(command) == RDP::Op::TextureRectangleFlip ||
RDP::Op(command) == RDP::Op::FillRectangle)
{
case RDP::Op::FillTriangle:
case RDP::Op::FillZBufferTriangle:
case RDP::Op::TextureTriangle:
case RDP::Op::TextureZBufferTriangle:
case RDP::Op::ShadeTriangle:
case RDP::Op::ShadeZBufferTriangle:
case RDP::Op::ShadeTextureTriangle:
case RDP::Op::ShadeTextureZBufferTriangle:
case RDP::Op::TextureRectangle:
case RDP::Op::TextureRectangleFlip:
case RDP::Op::FillRectangle:
if (!rdram_dirty[rdp_device.frame_buffer_info.framebuffer_address])
if (!rdram_dirty[frame_buffer_info.framebuffer_address])
{
std::fill_n(rdram_dirty.begin() + rdp_device.frame_buffer_info.framebuffer_address, framebuffer_size(), true);
std::fill_n(rdram_dirty.begin() + frame_buffer_info.framebuffer_address, frame_buffer_info.framebuffer_size, true);
}
if (rdp_device.frame_buffer_info.depthbuffer_address < rdram_dirty.size() && !rdram_dirty[rdp_device.frame_buffer_info.depthbuffer_address])
if (frame_buffer_info.depthbuffer_enabled && !rdram_dirty[frame_buffer_info.depthbuffer_address])
{
std::fill_n(rdram_dirty.begin() + rdp_device.frame_buffer_info.depthbuffer_address, depthbuffer_size(), true);
std::fill_n(rdram_dirty.begin() + frame_buffer_info.depthbuffer_address, frame_buffer_info.depthbuffer_size, true);
}
break;
case RDP::Op::LoadTLut:
case RDP::Op::LoadTile:
if (!rdram_dirty[rdp_device.frame_buffer_info.texture_address])
{
uint32_t lower_right_t = (w2 & 0xFFF) >> 2;
std::fill_n(rdram_dirty.begin() +
rdp_device.frame_buffer_info.texture_address,
texture_size(rdp_device.frame_buffer_info.texture_width * lower_right_t), true);
}
break;
case RDP::Op::LoadBlock:
{
uint32_t upper_left_s = ((w1 >> 12) & 0xFFF) >> 2;
uint32_t offset_address = rdp_device.frame_buffer_info.texture_address + texture_size(upper_left_s);
if (!rdram_dirty[offset_address])
{
uint32_t lower_right_s = ((w2 >> 12) & 0xFFF) >> 2;
std::fill_n(rdram_dirty.begin() + offset_address, texture_size(lower_right_s - upper_left_s), true);
}
break;
}
case RDP::Op::SetColorImage:
rdp_device.frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF) >> 3;
rdp_device.frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
rdp_device.frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
break;
case RDP::Op::SetMaskImage:
rdp_device.frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF) >> 3;
break;
case RDP::Op::SetTextureImage:
rdp_device.frame_buffer_info.texture_address = (w2 & 0x00FFFFFF) >> 3;
rdp_device.frame_buffer_info.texture_pixel_size = (w1 >> 19) & 0x3;
rdp_device.frame_buffer_info.texture_width = (w1 & 0x3FF) + 1;
break;
case RDP::Op::SetScissor:
else if (RDP::Op(command) == RDP::Op::SetOtherModes)
{
frame_buffer_info.depthbuffer_enabled = (w2 >> 5) & 1;
}
else if (RDP::Op(command) == RDP::Op::SetColorImage)
{
frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF) >> 3;
frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
calculate_buffer_size();
}
else if (RDP::Op(command) == RDP::Op::SetMaskImage)
{
frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF) >> 3;
}
else if (RDP::Op(command) == RDP::Op::SetScissor)
{
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
uint32_t upper_left_y = (w1 & 0xFFF) >> 2;
uint32_t lower_right_x = ((w2 >> 12) & 0xFFF) >> 2;
uint32_t lower_right_y = (w2 & 0xFFF) >> 2;
if (lower_right_x > upper_left_x && lower_right_y > upper_left_y)
{
rdp_device.region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
}
else
{
rdp_device.region = 0;
}
rdp_device.frame_buffer_info.framebuffer_height = lower_right_y;
break;
region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
frame_buffer_info.framebuffer_height = lower_right_y;
calculate_buffer_size();
}
case RDP::Op::SyncFull:
else if (RDP::Op(command) == RDP::Op::SyncFull)
{
sync_signal = processor->signal_timeline();
interrupt_timer = rdp_device.region;
interrupt_timer = region;
if (interrupt_timer == 0)
interrupt_timer = 5000;
break;
}
rdp_device.cmd_cur += cmd_length;
cmd_cur += cmd_length;
}
rdp_device.cmd_ptr = 0;
rdp_device.cmd_cur = 0;
cmd_ptr = 0;
cmd_cur = 0;
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
return interrupt_timer;

View file

@ -2,7 +2,6 @@
#ifdef __cplusplus
#include <cstdint>
#include <stddef.h>
extern "C"
{
@ -31,8 +30,6 @@ extern "C"
bool save_state;
bool load_state;
bool enable_speedlimiter;
bool lower_volume;
bool raise_volume;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info);
@ -43,9 +40,7 @@ extern "C"
uint64_t rdp_process_commands();
void rdp_new_processor(GFX_INFO _gfx_info);
void rdp_check_framebuffers(uint32_t address);
size_t rdp_state_size();
void rdp_save_state(uint8_t *state);
void rdp_load_state(const uint8_t *state);
void rdp_full_sync();
#ifdef __cplusplus
}

View file

@ -28,7 +28,7 @@ std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
}
std::vector<const char *> extensionNames;
for (unsigned int i = 0; i < extensionCount; ++i)
for (int i = 0; i < extensionCount; ++i)
{
extensionNames.push_back(extensions[i]);
}

View file

@ -1,2 +0,0 @@
[toolchain]
channel = "1.85.1"

View file

@ -99,11 +99,11 @@ fn init_rng(device: &mut Device) {
device.rng = rand_chacha::ChaCha8Rng::seed_from_u64(rng_seed);
}
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
if test == 0x80371240 {
// z64
Some(contents)
contents
} else if test == 0x37804012 {
// v64
let mut data: Vec<u8> = vec![0; contents.len()];
@ -111,7 +111,7 @@ fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
}
Some(data)
return data;
} else if test == 0x40123780 {
// n64
let mut data: Vec<u8> = vec![0; contents.len()];
@ -119,13 +119,14 @@ fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
}
Some(data)
return data;
} else {
None
let data: Vec<u8> = vec![];
data
}
}
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
let mut contents = vec![];
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
let zip_file = fs::File::open(file_path).unwrap();
@ -172,11 +173,7 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
contents = fs::read(file_path).expect("Should have been able to read the file");
}
if contents.is_empty() {
None
} else {
swap_rom(contents)
}
swap_rom(contents)
}
#[derive(serde::Serialize, serde::Deserialize)]
@ -351,7 +348,7 @@ impl Device {
},
},
memory: memory::Memory {
fast_read: [unmapped::read_mem_fast; 0x2000],
fast_read: [unmapped::read_mem; 0x2000],
memory_map_read: [unmapped::read_mem; 0x2000],
memory_map_write: [unmapped::write_mem; 0x2000],
icache: [cache::ICache {

View file

@ -27,7 +27,7 @@ pub struct AiDma {
pub duration: u64,
}
fn get_remaining_dma_length(device: &device::Device) -> u64 {
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
if device.ai.fifo[0].duration == 0 {
return 0;
}

View file

@ -30,14 +30,54 @@ pub fn icache_writeback(device: &mut device::Device, line_index: usize) {
let cache_address = ((device.memory.icache[line_index].tag
| (device.memory.icache[line_index].index) as u32)
& 0x1ffffffc) as u64;
for i in 0..8 {
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device.memory.icache[line_index].words[i as usize],
0xFFFFFFFF,
);
}
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address,
device.memory.icache[line_index].words[0],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device.memory.icache[line_index].words[1],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device.memory.icache[line_index].words[2],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device.memory.icache[line_index].words[3],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x10,
device.memory.icache[line_index].words[4],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x14,
device.memory.icache[line_index].words[5],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x18,
device.memory.icache[line_index].words[6],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x1C,
device.memory.icache[line_index].words[7],
0xFFFFFFFF,
);
}
pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
@ -48,17 +88,71 @@ pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address:
let cache_address = ((device.memory.icache[line_index].tag
| (device.memory.icache[line_index].index) as u32)
& 0x1ffffffc) as u64;
for i in 0..8 {
device.memory.icache[line_index].words[i as usize] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[0] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[1] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[2] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[3] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[4] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x10,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[5] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x14,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[6] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x18,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[7] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x1C,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].instruction[i as usize] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i as usize]);
}
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
}
pub fn icache_fetch(device: &mut device::Device, phys_address: u64) {
@ -86,15 +180,30 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
let cache_address = ((device.memory.dcache[line_index].tag
| (device.memory.dcache[line_index].index) as u32)
& 0x1ffffffc) as u64;
for i in 0..4 {
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device.memory.dcache[line_index].words[i as usize],
0xFFFFFFFF,
);
}
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address,
device.memory.dcache[line_index].words[0],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device.memory.dcache[line_index].words[1],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device.memory.dcache[line_index].words[2],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device.memory.dcache[line_index].words[3],
0xFFFFFFFF,
);
}
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
@ -107,15 +216,30 @@ fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64
let cache_address = ((device.memory.dcache[line_index].tag
| (device.memory.dcache[line_index].index) as u32)
& 0x1ffffffc) as u64;
for i in 0..4 {
device.memory.dcache[line_index].words[i as usize] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device::memory::AccessSize::Dcache,
);
}
device.memory.dcache[line_index].words[0] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[1] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[2] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[3] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device::memory::AccessSize::Dcache,
);
}
pub fn dcache_read(device: &mut device::Device, phys_address: u64) -> u32 {

View file

@ -4,7 +4,7 @@ use sha2::{Digest, Sha256};
const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = [0; 4];
for i in 0..4 {
if let Some(value) = device
@ -24,7 +24,7 @@ fn read_cart_word(device: &device::Device, address: usize) -> u32 {
}
pub fn read_mem_fast(
device: &device::Device,
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@ -186,7 +186,7 @@ fn set_cic(device: &mut device::Device) {
}
_ => {
device.cart.cic_seed = 0x3F; // CicNus6102
//println!("unknown IPL3 {}", hash)
println!("unknown IPL3 {}", hash)
}
}
}

View file

@ -57,10 +57,10 @@ pub fn process(device: &mut device::Device, channel: usize) {
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
let input = if device.netplay.is_none() {
ui::input::get(&device.ui, channel)
ui::input::get(&mut device.ui, channel)
} else {
if device.netplay.as_ref().unwrap().player_number as usize == channel {
let local_input = ui::input::get(&device.ui, 0);
let local_input = ui::input::get(&mut device.ui, 0);
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
}
@ -163,9 +163,9 @@ pub fn pak_switch_event(device: &mut device::Device) {
if channel.change_pak != PakType::None {
//stop rumble if it is on
if device.netplay.is_none() {
device::ui::input::set_rumble(&device.ui, i, 0);
device::ui::input::set_rumble(&mut device.ui, i, 0);
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
device::ui::input::set_rumble(&device.ui, 0, 0);
device::ui::input::set_rumble(&mut device.ui, 0, 0);
}
let new_pak_type = match channel.change_pak {
@ -202,7 +202,7 @@ pub fn pak_switch_event(device: &mut device::Device) {
pak_type: new_pak_type,
});
}
ui::audio::play_pak_switch(&device.ui, new_pak_type);
ui::audio::play_pak_switch(&mut device.ui, new_pak_type);
channel.change_pak = PakType::None;
}
}

View file

@ -29,7 +29,7 @@ pub enum CartType {
}
fn write_mbc1(
pif_ram: &[u8],
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
@ -66,7 +66,7 @@ fn write_mbc1(
fn read_mbc1(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@ -100,7 +100,7 @@ fn read_mbc1(
}
fn write_mbc3(
pif_ram: &[u8],
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
@ -161,7 +161,7 @@ fn write_mbc3(
fn read_mbc3(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@ -213,7 +213,7 @@ fn read_mbc3(
}
fn write_mbc5(
pif_ram: &[u8],
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
@ -247,7 +247,7 @@ fn write_mbc5(
fn read_mbc5(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@ -278,7 +278,7 @@ fn read_mbc5(
pub fn read(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@ -299,7 +299,7 @@ pub fn read(
}
pub fn write(
pif_ram: &[u8],
pif_ram: &mut [u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,

View file

@ -16,9 +16,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
if address == 0xc000 {
let rumble = device.pif.ram[data + size - 1];
if device.netplay.is_none() {
device::ui::input::set_rumble(&device.ui, channel, rumble);
device::ui::input::set_rumble(&mut device.ui, channel, rumble);
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
device::ui::input::set_rumble(&device.ui, 0, rumble);
device::ui::input::set_rumble(&mut device.ui, 0, rumble);
}
}
}

View file

@ -54,7 +54,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
0xC..=0xF => {
device::controller::gbcart::read(
&mut device.pif.ram,
&pak.cart,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
@ -119,7 +119,7 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
}
0xC..=0xF => {
device::controller::gbcart::write(
&device.pif.ram,
&mut device.pif.ram,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,

View file

@ -36,9 +36,9 @@ pub fn create_event_at(device: &mut device::Device, name: usize, when: u64) {
set_next_event(device);
}
pub fn get_event(device: &device::Device, name: usize) -> Option<&Event> {
pub fn get_event(device: &mut device::Device, name: usize) -> Option<&mut Event> {
if device.cpu.events[name].enabled {
return Some(&device.cpu.events[name]);
return Some(&mut device.cpu.events[name]);
}
None
}

View file

@ -40,8 +40,8 @@ pub enum AccessSize {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Memory {
#[serde(skip, default = "savestates::default_memory_read_fast")]
pub fast_read: [fn(&device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
#[serde(skip, default = "savestates::default_memory_read")]
pub fast_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
#[serde(skip, default = "savestates::default_memory_read")]
pub memory_map_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000],
#[serde(skip, default = "savestates::default_memory_write")]

View file

@ -232,23 +232,6 @@ pub fn connect_pif_channels(device: &mut device::Device) {
device.pif.channels[4].process = Some(device::cart::process)
}
fn get_default_handler(device: &device::Device) -> device::controller::PakHandler {
if device.ui.game_id == "NCT" {
// Chameleon Twist does not support the mempak
device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
}
} else {
device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
}
}
}
pub fn init(device: &mut device::Device) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
@ -258,7 +241,11 @@ pub fn init(device: &mut device::Device) {
device.pif.ram[0x26] = device.cart.cic_seed;
device.pif.ram[0x27] = device.cart.cic_seed;
let default_handler = get_default_handler(device);
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
@ -271,13 +258,13 @@ pub fn init(device: &mut device::Device) {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(default_handler);
device.pif.channels[i].pak_handler = Some(mempak_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(default_handler);
device.pif.channels[i].pak_handler = Some(mempak_handler);
}
}
if device.ui.config.input.emulate_vru {

View file

@ -24,7 +24,7 @@ pub struct Rdram {
}
pub fn read_mem_fast(
device: &device::Device,
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {

View file

@ -89,7 +89,7 @@ pub struct Rsp {
}
pub fn read_mem_fast(
device: &device::Device,
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {

View file

@ -317,9 +317,10 @@ pub fn lw(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 4];
for (i, item) in w.iter_mut().enumerate() {
*item = device.rsp.mem[(address as usize + i) & 0xFFF];
}
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
device.rsp.cpu.gpr[rt(opcode) as usize] =
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
@ -348,9 +349,10 @@ pub fn lwu(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 4];
for (i, item) in w.iter_mut().enumerate() {
*item = device.rsp.mem[(address as usize + i) & 0xFFF];
}
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
device.rsp.cpu.gpr[rt(opcode) as usize] =
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
@ -376,10 +378,14 @@ pub fn sw(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
for i in 0..4 {
device.rsp.mem[(address as usize + i) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> ((3 - i) * 8)) as u8;
}
device.rsp.mem[address as usize & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 24) as u8;
device.rsp.mem[(address as usize + 1) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 16) as u8;
device.rsp.mem[(address as usize + 2) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
device.rsp.mem[(address as usize + 3) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
}
pub fn sll(device: &mut device::Device, opcode: u32) {
@ -600,19 +606,19 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
pub fn cfc2(device: &mut device::Device, opcode: u32) {
let hi;
let lo;
let zero = unsafe { _mm_setzero_si128() };
let mut zero = unsafe { _mm_setzero_si128() };
match rd(opcode) & 3 {
0x00 => {
hi = &device.rsp.cpu.vcoh;
lo = &device.rsp.cpu.vcol;
hi = &mut device.rsp.cpu.vcoh;
lo = &mut device.rsp.cpu.vcol;
}
0x01 => {
hi = &device.rsp.cpu.vcch;
lo = &device.rsp.cpu.vccl;
hi = &mut device.rsp.cpu.vcch;
lo = &mut device.rsp.cpu.vccl;
}
0x02 | 0x03 => {
hi = &zero;
lo = &device.rsp.cpu.vce;
hi = &mut zero;
lo = &mut device.rsp.cpu.vce;
}
_ => {
panic!("unknown cfc2")

View file

@ -146,7 +146,7 @@ pub fn probe(device: &mut device::Device) {
}
fn tlb_unmap(device: &mut device::Device, index: u64) {
let e = &device.cpu.cop0.tlb_entries[index as usize];
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0 {
let mut i = e.start_even;
@ -184,7 +184,7 @@ fn tlb_unmap(device: &mut device::Device, index: u64) {
}
fn tlb_map(device: &mut device::Device, index: u64) {
let e = &device.cpu.cop0.tlb_entries[index as usize];
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0
&& e.start_even < e.end_even

View file

@ -1,7 +1,7 @@
use crate::device;
pub fn read_mem_fast(
_device: &device::Device,
pub fn read_mem(
_device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@ -9,12 +9,4 @@ pub fn read_mem_fast(
value | (value << 16)
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
access_size: device::memory::AccessSize,
) -> u32 {
read_mem_fast(device, address, access_size)
}
pub fn write_mem(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {}

View file

@ -151,22 +151,22 @@ async fn main() {
}
} else if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
if let Some(rom_contents) = device::get_rom_contents(file_path) {
let handle = std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let mut device = device::Device::new();
let overclock = device.ui.config.emulation.overclock;
device::run_game(&mut device, rom_contents, args.fullscreen, overclock);
})
.unwrap();
handle.join().unwrap();
} else {
let rom_contents = device::get_rom_contents(file_path);
if rom_contents.is_empty() {
println!("Could not read rom file");
return;
}
let handle = std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let mut device = device::Device::new();
let overclock = device.ui.config.emulation.overclock;
device::run_game(&mut device, rom_contents, args.fullscreen, overclock);
})
.unwrap();
handle.join().unwrap();
} else {
let options = eframe::NativeOptions {
viewport: eframe::egui::ViewportBuilder::default()

View file

@ -144,7 +144,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu
let timeout = std::time::Instant::now() + std::time::Duration::from_secs(10);
let mut request_timer = std::time::Instant::now() - std::time::Duration::from_millis(5);
while input.is_none() {
process_incoming(netplay, &device.ui); // we execute process_incoming before request_input so that we send an accurate buffer count
process_incoming(netplay, &mut device.ui); // we execute process_incoming before request_input so that we send an accurate buffer count
if std::time::Instant::now() > request_timer {
// sends a request packet every 5ms
request_input(netplay, channel);
@ -155,7 +155,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu
.remove(&netplay.player_data[channel].count);
if std::time::Instant::now() > timeout {
ui::audio::play_netplay_audio(&device.ui, NETPLAY_ERROR_LOST_CONNECTION);
ui::audio::play_netplay_audio(&mut device.ui, NETPLAY_ERROR_LOST_CONNECTION);
input = Some(InputEvent {
input: 0,
plugin: 0,
@ -186,7 +186,7 @@ fn request_input(netplay: &Netplay, channel: usize) {
netplay.udp_socket.send(&request).unwrap();
}
fn process_incoming(netplay: &mut Netplay, ui: &ui::Ui) {
fn process_incoming(netplay: &mut Netplay, ui: &mut ui::Ui) {
let mut buf: [u8; 1024] = [0; 1024];
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
match buf[0] {

View file

@ -70,8 +70,7 @@ where
}
pub fn create_savestate(device: &device::Device) {
let mut rdp_state: Vec<u8> = vec![0; ui::video::state_size()];
ui::video::save_state(rdp_state.as_mut_ptr());
ui::video::full_sync();
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
@ -79,7 +78,6 @@ pub fn create_savestate(device: &device::Device) {
&postcard::to_stdvec(&device.ui.storage.saves).unwrap(),
"saves",
),
(&rdp_state, "rdp_state"),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(
@ -90,109 +88,116 @@ pub fn create_savestate(device: &device::Device) {
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path);
let savestate = std::fs::read(&mut device.ui.storage.paths.savestate_file_path);
if savestate.is_ok() {
let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device");
let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves");
let rdp_state = ui::storage::decompress_file(savestate.as_ref().unwrap(), "rdp_state");
if let Ok(state) = postcard::from_bytes::<device::Device>(&device_bytes) {
device.ui.storage.saves = postcard::from_bytes(&save_bytes).unwrap();
let state: device::Device = postcard::from_bytes(&device_bytes).unwrap();
device.cpu = state.cpu;
device.pif = state.pif;
device.ui.storage.saves = postcard::from_bytes(&save_bytes).unwrap();
let rom = device.cart.rom.clone();
device.cart = state.cart;
device.cart.rom = rom;
device.cpu = state.cpu;
device.pif = state.pif;
device.memory = state.memory;
device.rsp = state.rsp;
device.rdp = state.rdp;
device.rdram = state.rdram;
device.mi = state.mi;
device.pi = state.pi;
device.vi = state.vi;
device.ai = state.ai;
device.si = state.si;
device.ri = state.ri;
device.vru = state.vru;
let rom = device.cart.rom.clone();
device.cart = state.cart;
device.cart.rom = rom;
let mut tpak_rom = [vec![], vec![], vec![], vec![]];
for (i, item) in tpak_rom.iter_mut().enumerate() {
*item = device.transferpaks[i].cart.rom.clone();
}
device.transferpaks = state.transferpaks;
for (i, item) in tpak_rom.iter().enumerate() {
device.transferpaks[i].cart.rom = item.clone();
}
device.memory = state.memory;
device.rsp = state.rsp;
device.rdp = state.rdp;
device.rdram = state.rdram;
device.mi = state.mi;
device.pi = state.pi;
device.vi = state.vi;
device.ai = state.ai;
device.si = state.si;
device.ri = state.ri;
device.vru = state.vru;
device::memory::init(device);
device::vi::set_expected_refresh_rate(device);
device::cpu::map_instructions(device);
device::cop0::map_instructions(device);
device::cop1::map_instructions(device);
device::cop2::map_instructions(device);
device::rsp_cpu::map_instructions(device);
let mut mem_addr = 0x1000;
while mem_addr < 0x2000 {
let data =
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
device::rsp_cpu::decode_opcode(device, data);
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
mem_addr += 4;
}
for line_index in 0..512 {
for i in 0..8 {
device.memory.icache[line_index].instruction[i] = device::cpu::decode_opcode(
device,
device.memory.icache[line_index].words[i],
);
}
}
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::TransferPak
{
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: device::controller::PakType::TransferPak,
};
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
}
}
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device, rdp_state.as_ptr());
} else {
println!("Failed to load savestate");
let mut tpak_rom = [vec![], vec![], vec![], vec![]];
for (i, item) in tpak_rom.iter_mut().enumerate() {
*item = device.transferpaks[i].cart.rom.clone();
}
device.transferpaks = state.transferpaks;
for (i, item) in tpak_rom.iter().enumerate() {
device.transferpaks[i].cart.rom = item.clone();
}
device::memory::init(device);
device::vi::set_expected_refresh_rate(device);
device::cpu::map_instructions(device);
device::cop0::map_instructions(device);
device::cop1::map_instructions(device);
device::cop2::map_instructions(device);
device::rsp_cpu::map_instructions(device);
let mut mem_addr = 0x1000;
while mem_addr < 0x2000 {
let data =
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
device::rsp_cpu::decode_opcode(device, data);
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
mem_addr += 4;
}
for line_index in 0..512 {
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
}
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::TransferPak
{
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: device::controller::PakType::TransferPak,
};
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
}
}
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device);
}
}
@ -211,16 +216,11 @@ where
[device::cop0::reserved; N]
}
pub fn default_memory_read_fast()
-> [fn(&device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::unmapped::read_mem_fast; 0x2000]
}
pub fn default_memory_read()
-> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::unmapped::read_mem; 0x2000]
[device::rdram::read_mem; 0x2000]
}
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
[device::unmapped::write_mem; 0x2000]
[device::rdram::write_mem; 0x2000]
}

View file

@ -17,7 +17,6 @@ pub struct Audio {
pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_device: u32,
pub event_audio: audio::EventAudio,
pub gain: f32,
}
pub struct Input {
@ -189,7 +188,6 @@ impl Ui {
audio_stream: std::ptr::null_mut(),
event_audio_stream: std::ptr::null_mut(),
audio_device: 0,
gain: 1.0,
},
video: Video {
window: std::ptr::null_mut(),

View file

@ -51,8 +51,7 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
return;
}
if !unsafe {
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain)
&& sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.audio_stream)
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.audio_stream)
} {
panic!("Could not bind audio stream");
}
@ -66,11 +65,7 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
ui.audio.event_audio_stream =
unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
if !unsafe {
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain)
&& sdl3_sys::audio::SDL_BindAudioStream(
ui.audio.audio_device,
ui.audio.event_audio_stream,
)
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.event_audio_stream)
} {
panic!("Could not bind audio stream");
}
@ -91,29 +86,7 @@ pub fn close(ui: &mut ui::Ui) {
}
}
pub fn lower_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) - 0.05;
if ui.audio.gain < 0.0 {
ui.audio.gain = 0.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain);
}
}
pub fn raise_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) + 0.05;
if ui.audio.gain > 2.0 {
ui.audio.gain = 2.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain);
}
}
pub fn play_netplay_audio(ui: &ui::Ui, error: u32) {
pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
if ui.audio.event_audio_stream.is_null() {
return;
}
@ -145,7 +118,7 @@ pub fn play_netplay_audio(ui: &ui::Ui, error: u32) {
}
}
pub fn play_pak_switch(ui: &ui::Ui, pak: device::controller::PakType) {
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
if ui.audio.event_audio_stream.is_null() {
return;
}
@ -167,7 +140,7 @@ pub fn play_pak_switch(ui: &ui::Ui, pak: device::controller::PakType) {
}
}
pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
if device.ui.audio.audio_stream.is_null() {
return;
}

View file

@ -193,33 +193,37 @@ impl Drop for GopherEguiApp {
}
fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
egui::Window::new("Configure Input Profile").show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name:");
ui.text_edit_singleline(&mut app.profile_name)
.labelled_by(name_label.id);
});
ui.checkbox(&mut app.dinput, "Use DirectInput");
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = app.profile_name.clone();
let dinput = app.dinput;
std::thread::spawn(move || {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
});
app.configure_profile = false;
if !app.profile_name.is_empty() && !app.input_profiles.contains(&app.profile_name) {
app.input_profiles.push(app.profile_name.clone())
}
};
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
app.configure_profile = false
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name:");
ui.text_edit_singleline(&mut app.profile_name)
.labelled_by(name_label.id);
});
ui.checkbox(&mut app.dinput, "Use DirectInput");
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = app.profile_name.clone();
let dinput = app.dinput;
std::thread::spawn(move || {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
});
app.configure_profile = false;
if !app.profile_name.is_empty()
&& !app.input_profiles.contains(&app.profile_name)
{
app.input_profiles.push(app.profile_name.clone())
}
};
})
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
app.configure_profile = false
};
})
});
});
});
}
fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
@ -445,15 +449,16 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context, enable_overclock:
device.vru_window.gui_ctx = Some(gui_ctx);
}
if let Some(rom_contents) = device::get_rom_contents(file.unwrap().path()) {
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(
&mut device,
rom_contents,
fullscreen,
enable_overclock,
);
} else {
println!("Could not read rom file");
}
}
let result = std::fs::remove_file(running_file);

View file

@ -334,7 +334,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
protected: None,
port: None,
features: Some(features),
buffer_target: None,
buffer_target: Some(2),
}),
};
let (mut socket, _response) =
@ -430,7 +430,8 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
if let Some(rom_contents) = device::get_rom_contents(file.path()) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);

View file

@ -276,7 +276,7 @@ fn set_buttons_from_controller(
}
}
pub fn set_rumble(ui: &ui::Ui, channel: usize, rumble: u8) {
pub fn set_rumble(ui: &mut ui::Ui, channel: usize, rumble: u8) {
if !ui.input.controllers[channel].rumble {
return;
}
@ -334,7 +334,7 @@ fn change_paks(
pressed
}
pub fn get(ui: &ui::Ui, channel: usize) -> InputData {
pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
let profile_name = ui.config.input.input_profile_binding[channel].clone();
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
let mut keys = 0;
@ -482,6 +482,8 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
if !unsafe { sdl3_sys::video::SDL_ShowWindow(window) } {
panic!("Could not show window")
}
let font =
rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
let key_labels: [(&str, usize); PROFILE_SIZE] = [
("A", A_BUTTON),
@ -538,10 +540,6 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
id: 0,
axis: 0,
};
let font =
ab_glyph::FontRef::try_from_slice(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
for (key, value) in key_labels.iter() {
let mut event: sdl3_sys::events::SDL_Event = Default::default();
while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {} // clear events

View file

@ -196,27 +196,27 @@ pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
let eep = std::fs::read(&ui.storage.paths.eep_file_path);
let eep = std::fs::read(&mut ui.storage.paths.eep_file_path);
if eep.is_ok() {
ui.storage.saves.eeprom.data = eep.unwrap();
}
let sra = std::fs::read(&ui.storage.paths.sra_file_path);
let sra = std::fs::read(&mut ui.storage.paths.sra_file_path);
if sra.is_ok() {
ui.storage.saves.sram.data = sra.unwrap();
}
let fla = std::fs::read(&ui.storage.paths.fla_file_path);
let fla = std::fs::read(&mut ui.storage.paths.fla_file_path);
if fla.is_ok() {
ui.storage.saves.flash.data = fla.unwrap();
}
let mempak = std::fs::read(&ui.storage.paths.pak_file_path);
let mempak = std::fs::read(&mut ui.storage.paths.pak_file_path);
if mempak.is_ok() {
ui.storage.saves.mempak.data = mempak.unwrap();
}
let sdcard = std::fs::read(&ui.storage.paths.sdcard_file_path);
let sdcard = std::fs::read(&mut ui.storage.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.storage.saves.sdcard.data = sdcard.unwrap();
}
let romsave = std::fs::read(&ui.storage.paths.romsave_file_path);
let romsave = std::fs::read(&mut ui.storage.paths.romsave_file_path);
if romsave.is_ok() {
ui.storage.saves.romsave.data =
postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();

View file

@ -1,7 +1,6 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
use crate::{device, ui};
use ab_glyph::{Font, ScaleFont};
pub fn init(device: &mut device::Device) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
@ -42,13 +41,7 @@ pub fn init(device: &mut device::Device) {
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.video.window) } {
panic!("Could not show window");
}
unsafe {
sdl3_sys::everything::SDL_HideCursor();
sdl3_sys::everything::SDL_SetHint(
sdl3_sys::everything::SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
std::ffi::CString::new("1").unwrap().as_ptr(),
);
}
unsafe { sdl3_sys::everything::SDL_HideCursor() };
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
@ -85,15 +78,11 @@ pub fn check_framebuffers(address: u32) {
unsafe { rdp_check_framebuffers(address) }
}
pub fn state_size() -> usize {
unsafe { rdp_state_size() }
pub fn full_sync() {
unsafe { rdp_full_sync() }
}
pub fn save_state(rdp_state: *mut u8) {
unsafe { rdp_save_state(rdp_state) }
}
pub fn load_state(device: &mut device::Device, rdp_state: *const u8) {
pub fn load_state(device: &mut device::Device) {
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
DMEM: device.rsp.mem.as_mut_ptr(),
@ -111,10 +100,6 @@ pub fn load_state(device: &mut device::Device, rdp_state: *const u8) {
};
unsafe {
rdp_new_processor(gfx_info);
rdp_load_state(rdp_state);
for reg in 0..device::vi::VI_REGS_COUNT {
rdp_set_vi_register(reg, device.vi.regs[reg as usize])
}
}
}
@ -129,12 +114,6 @@ pub fn check_callback(device: &mut device::Device) {
}
device.vi.enable_speed_limiter = callback.enable_speedlimiter;
}
if callback.lower_volume {
ui::audio::lower_audio_volume(&mut device.ui);
} else if callback.raise_volume {
ui::audio::raise_audio_volume(&mut device.ui);
}
}
pub fn set_register(reg: u32, value: u32) {
@ -147,11 +126,12 @@ pub fn process_rdp_list() -> u64 {
unsafe { rdp_process_commands() }
}
pub fn draw_text(
text: &str,
renderer: *mut sdl3_sys::render::SDL_Renderer,
font: &ab_glyph::FontRef,
) {
pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font: &rusttype::Font) {
let text_size = 32;
let scale = rusttype::Scale::uniform(text_size as f32);
let v_metrics = font.v_metrics(scale);
let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent);
// Clear the canvas
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
@ -164,20 +144,12 @@ pub fn draw_text(
sdl3_sys::render::SDL_RenderClear(renderer);
};
let text_size = 40.0;
let (mut w, mut h) = (0, 0);
unsafe { sdl3_sys::render::SDL_GetRenderOutputSize(renderer, &mut w, &mut h) };
let x_start = 20.0;
let y_start = (h / 2) as f32;
let mut x_offset = 0.0;
for c in text.chars() {
let q_glyph_id = font.glyph_id(c);
let q_glyph = q_glyph_id.with_scale(text_size);
if let Some(q) = font.outline_glyph(q_glyph) {
q.draw(|x, y, c| {
if c > 0.5 {
for glyph in font.layout(text, scale, offset) {
if let Some(bb) = glyph.pixel_bounding_box() {
glyph.draw(|x, y, v| {
let x = x as i32 + bb.min.x;
let y = y as i32 + bb.min.y + (240 - text_size);
if v > 0.5 {
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
renderer,
@ -186,17 +158,11 @@ pub fn draw_text(
255,
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
);
sdl3_sys::render::SDL_RenderPoint(
renderer,
x_start + x_offset + x as f32 - q.px_bounds().width()
+ q.px_bounds().max.x,
y_start + y as f32 - q.px_bounds().height() + q.px_bounds().max.y,
);
sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32);
};
}
});
}
x_offset += font.as_scaled(text_size).h_advance(q_glyph_id);
}
// Present the canvas