Mostly-working Horizon port

This commit is contained in:
Alula 2022-12-01 14:30:59 +01:00
parent f91513edd2
commit 67979a03ea
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
30 changed files with 1838 additions and 231 deletions

3
.gitignore vendored
View File

@ -9,6 +9,9 @@
debug/ debug/
target/ target/
# Shader binary files
*.dksh
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock Cargo.lock

View File

@ -21,13 +21,14 @@ panic = "abort"
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3
overflow-checks = false
[package.metadata.bundle] [package.metadata.bundle]
name = "doukutsu-rs" name = "doukutsu-rs"
identifier = "io.github.doukutsu_rs" identifier = "io.github.doukutsu_rs"
version = "0.100.0" version = "0.100.0"
resources = ["data"] resources = ["data"]
copyright = "Copyright (c) 2020-2022 doukutsu-rs dev team" copyright = "Copyright (c) 2020-2022 doukutsu-rs contributors"
category = "Game" category = "Game"
osx_minimum_system_version = "10.12" osx_minimum_system_version = "10.12"
@ -37,6 +38,7 @@ default-base = ["ogg-playback"]
ogg-playback = ["lewton"] ogg-playback = ["lewton"]
backend-sdl = ["sdl2", "sdl2-sys"] backend-sdl = ["sdl2", "sdl2-sys"]
backend-glutin = ["winit", "glutin", "render-opengl"] backend-glutin = ["winit", "glutin", "render-opengl"]
backend-horizon = []
render-opengl = [] render-opengl = []
scripting-lua = ["lua-ffi"] scripting-lua = ["lua-ffi"]
netplay = ["serde_cbor"] netplay = ["serde_cbor"]
@ -50,10 +52,11 @@ android = []
#winit = { path = "./3rdparty/winit", optional = true, default_features = false, features = ["x11"] } #winit = { path = "./3rdparty/winit", optional = true, default_features = false, features = ["x11"] }
#sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } #sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
#sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] } #sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] }
cpal = { path = "./3rdparty/cpal" }
byteorder = "1.4" byteorder = "1.4"
case_insensitive_hashmap = "1.0.0" case_insensitive_hashmap = "1.0.0"
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
cpal = "0.14" #cpal = "0.14"
directories = "3" directories = "3"
downcast = "0.11" downcast = "0.11"
#glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] } #glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
@ -102,3 +105,5 @@ ndk-sys = "0.4"
jni = "0.20" jni = "0.20"
[target.'cfg(target_os = "horizon")'.dependencies] [target.'cfg(target_os = "horizon")'.dependencies]
#deko3d = { path = "./3rdparty/deko3d" }
deko3d = { git = "https://github.com/doukutsu-rs/deko3d-rs", branch = "master" }

View File

@ -4,7 +4,18 @@ description = "doukutsu-rs targeted for Nintendo Switch"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [profile.release]
opt-level = 3
#incremental = true
[profile.dev.package."*"]
opt-level = 3
overflow-checks = false
incremental = true
[profile.dev.package."doukutsu-rs"]
opt-level = 3
overflow-checks = false
incremental = true
[dependencies] [dependencies]
doukutsu-rs = { path = "../", default-features = false, features = ["default-base"] } doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-horizon"] }

View File

@ -8,6 +8,7 @@
"exe-suffix": ".elf", "exe-suffix": ".elf",
"features": "+a57,+strict-align,+crc,+crypto", "features": "+a57,+strict-align,+crc,+crypto",
"has-rpath": false, "has-rpath": false,
"has-thread-local": false,
"linker": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc", "linker": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc",
"linker-flavor": "gcc", "linker-flavor": "gcc",
"llvm-target": "aarch64-unknown-none", "llvm-target": "aarch64-unknown-none",
@ -26,14 +27,18 @@
"-L", "-L",
"/opt/devkitpro/portlibs/switch/lib", "/opt/devkitpro/portlibs/switch/lib",
"-L", "-L",
"/opt/devkitpro/libnx/lib" "/opt/devkitpro/libnx/lib",
"-I",
"/opt/devkitpro/libnx/include"
] ]
}, },
"relocation-model": "pic", "relocation-model": "pic",
"requires-uwtable": true, "requires-uwtable": true,
"target-c-int-width": "32", "target-c-int-width": "32",
"target-endian": "little", "target-endian": "little",
"target-family": ["unix"], "target-family": [
"unix"
],
"target-pointer-width": "64", "target-pointer-width": "64",
"trap-unreachable": true, "trap-unreachable": true,
"vendor": "nintendo" "vendor": "nintendo"

View File

@ -3,17 +3,31 @@
cd "$(dirname "$0")" || exit cd "$(dirname "$0")" || exit
set -e set -e
DARK_GRAY=$(tput setaf 8)
YELLOW=$(tput bold)$(tput setaf 3)
RESET=$(tput sgr0)
function message() {
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
}
message "Compiling shaders..."
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
message "Building crate..."
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json
rm -f target/aarch64-nintendo-switch/debug/drshorizon.nro rm -f target/aarch64-nintendo-switch/debug/drshorizon.nro
rm -f target/aarch64-nintendo-switch/debug/drshorizon.nacp rm -f target/aarch64-nintendo-switch/debug/drshorizon.nacp
echo "Creating NACP..." message "Creating NACP..."
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.100.0' target/aarch64-nintendo-switch/debug/drshorizon.nacp nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.100.0' target/aarch64-nintendo-switch/debug/drshorizon.nacp
echo "Running elf2nro..." message "Running elf2nro..."
elf2nro target/aarch64-nintendo-switch/debug/drshorizon.elf target/aarch64-nintendo-switch/debug/drshorizon.nro \ elf2nro target/aarch64-nintendo-switch/debug/drshorizon.elf target/aarch64-nintendo-switch/debug/drshorizon.nro \
--icon=../res/nx_icon.jpg \ --icon=../res/nx_icon.jpg \
--nacp=target/aarch64-nintendo-switch/debug/drshorizon.nacp --nacp=target/aarch64-nintendo-switch/debug/drshorizon.nacp
echo "done." message "done!"

33
drshorizon/build_release.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
set -e
DARK_GRAY=$(tput setaf 8)
YELLOW=$(tput bold)$(tput setaf 3)
RESET=$(tput sgr0)
function message() {
echo "${DARK_GRAY}----${RESET} ${YELLOW}$*${RESET}"
}
message "Compiling shaders..."
uam -s vert -o ../src/framework/shaders/deko3d/vertex_basic.dksh ../src/framework/shaders/deko3d/vertex_basic.glsl
uam -s frag -o ../src/framework/shaders/deko3d/fragment_textured.dksh ../src/framework/shaders/deko3d/fragment_textured.glsl
uam -s frag -o ../src/framework/shaders/deko3d/fragment_color.dksh ../src/framework/shaders/deko3d/fragment_color.glsl
message "Building crate..."
rustup run rust-switch cargo build -Z build-std=core,alloc,std,panic_abort --target aarch64-nintendo-switch.json --release
rm -f target/aarch64-nintendo-switch/release/drshorizon.nro
rm -f target/aarch64-nintendo-switch/release/drshorizon.nacp
message "Creating NACP..."
nacptool --create 'doukutsu-rs' 'doukutsu-rs contributors' '0.100.0' target/aarch64-nintendo-switch/release/drshorizon.nacp
message "Running elf2nro..."
elf2nro target/aarch64-nintendo-switch/release/drshorizon.elf target/aarch64-nintendo-switch/release/drshorizon.nro \
--icon=../res/nx_icon.jpg \
--nacp=target/aarch64-nintendo-switch/release/drshorizon.nacp
message "done!"

View File

@ -3,15 +3,36 @@
#[repr(C)] #[repr(C)]
pub struct PrintConsole {} pub struct PrintConsole {}
extern "C" { #[repr(C)]
pub fn consoleInit(unk: *mut PrintConsole) -> *mut PrintConsole; #[derive(Copy, Clone, Eq, PartialEq)]
pub enum ApmCpuBoostMode {
Normal = 0,
FastLoad = 1,
}
pub fn consoleUpdate(unk: *mut PrintConsole); extern "C" {
fn consoleInit(unk: *mut PrintConsole) -> *mut PrintConsole;
fn consoleUpdate(unk: *mut PrintConsole);
fn socketInitialize(unk: *const std::ffi::c_void) -> u32;
fn nxlinkConnectToHost(redir_stdout: bool, redir_stderr: bool) -> i32;
fn appletSetCpuBoostMode(mode: ApmCpuBoostMode) -> u32;
static __text_start: u32;
} }
fn main() { fn main() {
unsafe { unsafe {
consoleInit(std::ptr::null_mut()); if socketInitialize(std::ptr::null()) == 0 {
nxlinkConnectToHost(true, true);
}
appletSetCpuBoostMode(ApmCpuBoostMode::FastLoad);
std::env::set_var("RUST_BACKTRACE", "full");
println!("__text_start = {:#x}", (&__text_start) as *const _ as usize);
let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false }; let options = doukutsu_rs::game::LaunchOptions { server_mode: false, editor: false };
let result = doukutsu_rs::game::init(options); let result = doukutsu_rs::game::init(options);
@ -19,7 +40,6 @@ fn main() {
if let Err(e) = result { if let Err(e) = result {
println!("Initialization error: {}", e); println!("Initialization error: {}", e);
loop { loop {
consoleUpdate(std::ptr::null_mut());
std::thread::sleep(std::time::Duration::from_millis(100)); std::thread::sleep(std::time::Duration::from_millis(100));
} }
} }

51
drshorizon/symbolize.js Normal file
View File

@ -0,0 +1,51 @@
// <reference types="node" />
const readline = require('readline');
const childProcess = require('child_process');
const rl = readline.createInterface({
input: process.stdin,
terminal: false
});
let textStart = 0;
const textStartRegex = /__text_start = 0x([0-9a-f]+)/i;
let symbolize = false;
if (process.argv.length <= 2) {
console.error('Usage: node symbolize.js <path to ELF file>');
process.exit(1);
}
const elfPath = process.argv[2];
rl.on('line', (line) => {
if (textStart === 0) {
const match = textStartRegex.exec(line);
if (match) {
textStart = parseInt(match[1], 16);
}
}
if (line.includes("stack backtrace:")) {
symbolize = true;
}
if (symbolize) {
const match = /0x([0-9a-f]+) - \<unknown\>/.exec(line);
if (match) {
const addr = parseInt(match[1], 16);
const relative = addr - textStart;
// run addr2line on the address
const addr2line = childProcess.spawnSync('addr2line', ['-e', elfPath, '-j', '.text', '-f', '-C', '0x' + relative.toString(16)]);
if (addr2line.status === 0) {
const output = addr2line.stdout.toString();
const lines = output.split('\n');
const [func, file] = lines;
line = line.replace(match[0], `0x${addr.toString(16)} - ${func} (${file})`);
}
}
}
console.log(line);
});

View File

@ -1,4 +1,4 @@
edition = "2018" edition = "2021"
max_width = 120 max_width = 120
use_small_heuristics = "Max" use_small_heuristics = "Max"
newline_style = "Unix" newline_style = "Unix"

View File

@ -107,15 +107,20 @@ pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn
return crate::framework::backend_null::NullBackend::new(); return crate::framework::backend_null::NullBackend::new();
} }
#[cfg(all(feature = "backend-horizon"))]
{
return crate::framework::backend_horizon::HorizonBackend::new();
}
#[cfg(all(feature = "backend-glutin"))] #[cfg(all(feature = "backend-glutin"))]
{ {
return crate::framework::backend_glutin::GlutinBackend::new(); return crate::framework::backend_glutin::GlutinBackend::new();
} }
#[cfg(feature = "backend-sdl")] #[cfg(feature = "backend-sdl")]
{ {
return crate::framework::backend_sdl2::SDL2Backend::new(size_hint); return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
} }
log::warn!("No backend compiled in, using null backend instead."); log::warn!("No backend compiled in, using null backend instead.");
crate::framework::backend_null::NullBackend::new() crate::framework::backend_null::NullBackend::new()

File diff suppressed because it is too large Load Diff

View File

@ -27,23 +27,10 @@ impl Backend for NullBackend {
} }
} }
#[cfg(target_os = "horizon")]
#[repr(C)]
pub struct PrintConsole {}
#[cfg(target_os = "horizon")]
extern "C" { fn consoleUpdate(unk: *mut PrintConsole); }
pub struct NullEventLoop; pub struct NullEventLoop;
impl BackendEventLoop for NullEventLoop { impl BackendEventLoop for NullEventLoop {
fn run(&mut self, game: &mut Game, ctx: &mut Context) { fn run(&mut self, game: &mut Game, ctx: &mut Context) {
println!("BackendEventLoop::run");
#[cfg(target_os = "horizon")]
unsafe {
consoleUpdate(std::ptr::null_mut());
}
let state_ref = unsafe { &mut *game.state.get() }; let state_ref = unsafe { &mut *game.state.get() };
ctx.screen_size = (640.0, 480.0); ctx.screen_size = (640.0, 480.0);
@ -67,11 +54,6 @@ impl BackendEventLoop for NullEventLoop {
std::thread::sleep(std::time::Duration::from_millis(10)); std::thread::sleep(std::time::Duration::from_millis(10));
game.draw(ctx).unwrap(); game.draw(ctx).unwrap();
#[cfg(target_os = "horizon")]
unsafe {
consoleUpdate(std::ptr::null_mut());
}
} }
} }

View File

@ -7,10 +7,9 @@ use std::ptr::{null, null_mut};
use std::rc::Rc; use std::rc::Rc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui};
use imgui::internal::RawWrapper; use imgui::internal::RawWrapper;
use imgui::sys::{ImGuiKey_Backspace, ImGuiKey_Delete, ImGuiKey_Enter}; use imgui::sys::{ImGuiKey_Backspace, ImGuiKey_Delete, ImGuiKey_Enter};
use sdl2::{controller, EventPump, GameControllerSubsystem, keyboard, pixels, Sdl, VideoSubsystem}; use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui};
use sdl2::controller::GameController; use sdl2::controller::GameController;
use sdl2::event::{Event, WindowEvent}; use sdl2::event::{Event, WindowEvent};
use sdl2::keyboard::Scancode; use sdl2::keyboard::Scancode;
@ -20,9 +19,13 @@ use sdl2::render::{Texture, TextureCreator, TextureQuery, WindowCanvas};
use sdl2::video::GLProfile; use sdl2::video::GLProfile;
use sdl2::video::Window; use sdl2::video::Window;
use sdl2::video::WindowContext; use sdl2::video::WindowContext;
use sdl2::{controller, keyboard, pixels, EventPump, GameControllerSubsystem, Sdl, VideoSubsystem};
use crate::common::{Color, Rect}; use crate::common::{Color, Rect};
use crate::framework::backend::{Backend, BackendEventLoop, BackendGamepad, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData}; use crate::framework::backend::{
Backend, BackendEventLoop, BackendGamepad, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand,
VertexData,
};
use crate::framework::context::Context; use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult}; use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem; use crate::framework::filesystem;
@ -31,9 +34,9 @@ use crate::framework::graphics::BlendMode;
use crate::framework::keyboard::ScanCode; use crate::framework::keyboard::ScanCode;
use crate::framework::render_opengl::{GLContext, OpenGLRenderer}; use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
use crate::framework::ui::init_imgui; use crate::framework::ui::init_imgui;
use crate::game::shared_game_state::WindowMode;
use crate::game::Game; use crate::game::Game;
use crate::game::GAME_SUSPENDED; use crate::game::GAME_SUSPENDED;
use crate::game::shared_game_state::WindowMode;
pub struct SDL2Backend { pub struct SDL2Backend {
context: Sdl, context: Sdl,
@ -165,7 +168,7 @@ impl SDL2EventLoop {
window.resizable(); window.resizable();
#[cfg(feature = "render-opengl")] #[cfg(feature = "render-opengl")]
window.opengl(); window.opengl();
let window = window.build().map_err(|e| GameError::WindowError(e.to_string()))?; let window = window.build().map_err(|e| GameError::WindowError(e.to_string()))?;
let opengl_available = if let Ok(v) = std::env::var("CAVESTORY_NO_OPENGL") { v != "1" } else { true }; let opengl_available = if let Ok(v) = std::env::var("CAVESTORY_NO_OPENGL") { v != "1" } else { true };
@ -207,7 +210,7 @@ impl BackendEventLoop for SDL2EventLoop {
loop { loop {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { unsafe {
use objc::*; use objc::*;
// no UB: fields are initialized by SDL_GetWindowWMInfo // no UB: fields are initialized by SDL_GetWindowWMInfo
@ -321,7 +324,8 @@ impl BackendEventLoop for SDL2EventLoop {
ctx.gamepad_context.add_gamepad(SDL2Gamepad::new(controller), axis_sensitivity); ctx.gamepad_context.add_gamepad(SDL2Gamepad::new(controller), axis_sensitivity);
unsafe { unsafe {
let controller_type = get_game_controller_type(sdl2_sys::SDL_GameControllerTypeForIndex(id as _)); let controller_type =
get_game_controller_type(sdl2_sys::SDL_GameControllerTypeForIndex(id as _));
ctx.gamepad_context.set_gamepad_type(id, controller_type); ctx.gamepad_context.set_gamepad_type(id, controller_type);
} }
} }
@ -404,19 +408,19 @@ impl BackendEventLoop for SDL2EventLoop {
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> { fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
#[cfg(feature = "render-opengl")] #[cfg(feature = "render-opengl")]
{ {
let mut refs = self.refs.borrow_mut(); let mut refs = self.refs.borrow_mut();
match refs.window.window().gl_create_context() { match refs.window.window().gl_create_context() {
Ok(gl_ctx) => { Ok(gl_ctx) => {
refs.window.window().gl_make_current(&gl_ctx).map_err(|e| GameError::RenderError(e.to_string()))?; refs.window.window().gl_make_current(&gl_ctx).map_err(|e| GameError::RenderError(e.to_string()))?;
refs.gl_context = Some(gl_ctx); refs.gl_context = Some(gl_ctx);
} }
Err(err) => { Err(err) => {
*self.opengl_available.borrow_mut() = false; *self.opengl_available.borrow_mut() = false;
log::error!("Failed to initialize OpenGL context, falling back to SDL2 renderer: {}", err); log::error!("Failed to initialize OpenGL context, falling back to SDL2 renderer: {}", err);
}
} }
} }
}
#[cfg(feature = "render-opengl")] #[cfg(feature = "render-opengl")]
if *self.opengl_available.borrow() { if *self.opengl_available.borrow() {
@ -493,15 +497,14 @@ struct SDL2Gamepad {
} }
impl SDL2Gamepad { impl SDL2Gamepad {
pub fn new(inner: GameController) -> Box<dyn BackendGamepad> { fn new(inner: GameController) -> Box<dyn BackendGamepad> {
Box::new(SDL2Gamepad { inner }) Box::new(SDL2Gamepad { inner })
} }
} }
impl BackendGamepad for SDL2Gamepad { impl BackendGamepad for SDL2Gamepad {
fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult { fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult {
self.inner.set_rumble(low_freq, high_freq, duration_ms) self.inner.set_rumble(low_freq, high_freq, duration_ms).map_err(|e| GameError::GamepadError(e.to_string()))
.map_err(|e| GameError::GamepadError(e.to_string()))
} }
fn instance_id(&self) -> u32 { fn instance_id(&self) -> u32 {

View File

@ -3,6 +3,8 @@
pub mod backend; pub mod backend;
#[cfg(feature = "backend-glutin")] #[cfg(feature = "backend-glutin")]
pub mod backend_glutin; pub mod backend_glutin;
#[cfg(feature = "backend-horizon")]
pub mod backend_horizon;
pub mod backend_null; pub mod backend_null;
#[cfg(feature = "backend-sdl")] #[cfg(feature = "backend-sdl")]
pub mod backend_sdl2; pub mod backend_sdl2;

View File

@ -301,149 +301,14 @@ fn check_shader_compile_status(shader: u32, gl: &Gl) -> GameResult {
Ok(()) Ok(())
} }
const VERTEX_SHADER_BASIC: &str = r" const VERTEX_SHADER_BASIC: &str = include_str!("shaders/opengl/vertex_basic_110.glsl");
#version 110 const FRAGMENT_SHADER_TEXTURED: &str = include_str!("shaders/opengl/fragment_textured_110.glsl");
const FRAGMENT_SHADER_COLOR: &str = include_str!("shaders/opengl/fragment_color_110.glsl");
const FRAGMENT_SHADER_WATER: &str = include_str!("shaders/opengl/fragment_water_110.glsl");
uniform mat4 ProjMtx; const VERTEX_SHADER_BASIC_GLES: &str = include_str!("shaders/opengles/vertex_basic_100.glsl");
attribute vec2 Position; const FRAGMENT_SHADER_TEXTURED_GLES: &str = include_str!("shaders/opengles/fragment_textured_100.glsl");
attribute vec2 UV; const FRAGMENT_SHADER_COLOR_GLES: &str = include_str!("shaders/opengles/fragment_color_100.glsl");
attribute vec4 Color;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
}
";
const FRAGMENT_SHADER_TEXTURED: &str = r"
#version 110
uniform sampler2D Texture;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
}
";
const FRAGMENT_SHADER_COLOR: &str = r"
#version 110
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color;
}
";
const FRAGMENT_SHADER_WATER: &str = r"
#version 110
uniform mat4 ProjMtx;
uniform sampler2D Texture;
uniform float Time;
uniform float Scale;
uniform vec2 FrameOffset;
varying vec4 Frag_Color;
void main()
{
vec2 resolution_inv = vec2(ProjMtx[0][0], -ProjMtx[1][1]) * 0.5;
vec2 uv = gl_FragCoord.xy * resolution_inv;
uv.y += 1.0;
vec2 wave = uv;
wave.x += sin((-FrameOffset.y * resolution_inv.y + uv.x * 16.0) + Time / 20.0) * Scale * resolution_inv.x;
wave.y -= cos((-FrameOffset.x * resolution_inv.x + uv.y * 16.0) + Time / 5.0) * Scale * resolution_inv.y;
float off = 0.35 * Scale * resolution_inv.y;
float off2 = 2.0 * off;
vec3 color = texture2D(Texture, wave).rgb * 0.25;
color += texture2D(Texture, wave + vec2(0, off)).rgb * 0.125;
color += texture2D(Texture, wave + vec2(0, -off)).rgb * 0.125;
color.rg += texture2D(Texture, wave + vec2(-off, -off)).rg * 0.0625;
color.rg += texture2D(Texture, wave + vec2(-off, 0)).rg * 0.125;
color.rg += texture2D(Texture, wave + vec2(-off, off)).rg * 0.0625;
color.b += texture2D(Texture, wave + vec2(-off2, -off)).b * 0.0625;
color.b += texture2D(Texture, wave + vec2(-off2, 0)).b * 0.125;
color.b += texture2D(Texture, wave + vec2(-off2, off)).b * 0.0625;
color.rg += texture2D(Texture, wave + vec2(off, off)).gb * 0.0625;
color.rg += texture2D(Texture, wave + vec2(off, 0)).gb * 0.125;
color.rg += texture2D(Texture, wave + vec2(off, -off)).gb * 0.0625;
color.b += texture2D(Texture, wave + vec2(off2, off)).r * 0.0625;
color.b += texture2D(Texture, wave + vec2(off2, 0)).r * 0.125;
color.b += texture2D(Texture, wave + vec2(off2, -off)).r * 0.0625;
color *= (1.0 - Frag_Color.a);
color += Frag_Color.rgb * Frag_Color.a;
gl_FragColor = vec4(color, 1.0);
}
";
const VERTEX_SHADER_BASIC_GLES: &str = r"
#version 100
precision mediump float;
uniform mat4 ProjMtx;
attribute vec2 Position;
attribute vec2 UV;
attribute vec4 Color;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
}
";
const FRAGMENT_SHADER_TEXTURED_GLES: &str = r"
#version 100
precision mediump float;
uniform sampler2D Texture;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
}
";
const FRAGMENT_SHADER_COLOR_GLES: &str = r"
#version 100
precision mediump float;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color;
}
";
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct RenderShader { struct RenderShader {

View File

@ -0,0 +1,11 @@
#version 460
layout (location = 0) in vec2 Frag_UV;
layout (location = 1) in vec4 Frag_Color;
layout (location = 0) out vec4 outColor;
void main()
{
outColor = Frag_Color;
}

View File

@ -0,0 +1,13 @@
#version 460
layout (location = 0) in vec2 Frag_UV;
layout (location = 1) in vec4 Frag_Color;
layout (binding = 0) uniform sampler2D Texture;
layout (location = 0) out vec4 outColor;
void main()
{
outColor = Frag_Color * texture(Texture, Frag_UV);
}

View File

@ -0,0 +1,20 @@
#version 460
layout (location = 0) in vec2 Position;
layout (location = 1) in vec2 UV;
layout (location = 2) in vec4 Color;
layout (location = 0) out vec2 Frag_UV;
layout (location = 1) out vec4 Frag_Color;
layout (std140, binding = 0) uniform VertUBO
{
mat4 proj;
} ProjMtx;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx.proj * vec4(Position.xy, 0.0, 1.0);
}

View File

@ -0,0 +1,9 @@
#version 110
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color;
}

View File

@ -0,0 +1,10 @@
#version 110
uniform sampler2D Texture;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
}

View File

@ -0,0 +1,42 @@
#version 110
uniform mat4 ProjMtx;
uniform sampler2D Texture;
uniform float Time;
uniform float Scale;
uniform vec2 FrameOffset;
varying vec4 Frag_Color;
void main()
{
vec2 resolution_inv = vec2(ProjMtx[0][0], -ProjMtx[1][1]) * 0.5;
vec2 uv = gl_FragCoord.xy * resolution_inv;
uv.y += 1.0;
vec2 wave = uv;
wave.x += sin((-FrameOffset.y * resolution_inv.y + uv.x * 16.0) + Time / 20.0) * Scale * resolution_inv.x;
wave.y -= cos((-FrameOffset.x * resolution_inv.x + uv.y * 16.0) + Time / 5.0) * Scale * resolution_inv.y;
float off = 0.35 * Scale * resolution_inv.y;
float off2 = 2.0 * off;
vec3 color = texture2D(Texture, wave).rgb * 0.25;
color += texture2D(Texture, wave + vec2(0, off)).rgb * 0.125;
color += texture2D(Texture, wave + vec2(0, -off)).rgb * 0.125;
color.rg += texture2D(Texture, wave + vec2(-off, -off)).rg * 0.0625;
color.rg += texture2D(Texture, wave + vec2(-off, 0)).rg * 0.125;
color.rg += texture2D(Texture, wave + vec2(-off, off)).rg * 0.0625;
color.b += texture2D(Texture, wave + vec2(-off2, -off)).b * 0.0625;
color.b += texture2D(Texture, wave + vec2(-off2, 0)).b * 0.125;
color.b += texture2D(Texture, wave + vec2(-off2, off)).b * 0.0625;
color.rg += texture2D(Texture, wave + vec2(off, off)).gb * 0.0625;
color.rg += texture2D(Texture, wave + vec2(off, 0)).gb * 0.125;
color.rg += texture2D(Texture, wave + vec2(off, -off)).gb * 0.0625;
color.b += texture2D(Texture, wave + vec2(off2, off)).r * 0.0625;
color.b += texture2D(Texture, wave + vec2(off2, 0)).r * 0.125;
color.b += texture2D(Texture, wave + vec2(off2, -off)).r * 0.0625;
color *= (1.0 - Frag_Color.a);
color += Frag_Color.rgb * Frag_Color.a;
gl_FragColor = vec4(color, 1.0);
}

View File

@ -0,0 +1,15 @@
#version 110
uniform mat4 ProjMtx;
attribute vec2 Position;
attribute vec2 UV;
attribute vec4 Color;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
}

View File

@ -0,0 +1,11 @@
#version 100
precision mediump float;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color;
}

View File

@ -0,0 +1,12 @@
#version 100
precision mediump float;
uniform sampler2D Texture;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);
}

View File

@ -0,0 +1,17 @@
#version 100
precision mediump float;
uniform mat4 ProjMtx;
attribute vec2 Position;
attribute vec2 UV;
attribute vec4 Color;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0);
}

View File

@ -128,7 +128,7 @@ pub trait VFS: Debug {
fn metadata(&self, path: &Path) -> GameResult<Box<dyn VMetadata>>; fn metadata(&self, path: &Path) -> GameResult<Box<dyn VMetadata>>;
/// Retrieve all file and directory entries in the given directory. /// Retrieve all file and directory entries in the given directory.
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>>; fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>>;
/// Retrieve the actual location of the VFS root, if available. /// Retrieve the actual location of the VFS root, if available.
fn to_path_buf(&self) -> Option<PathBuf>; fn to_path_buf(&self) -> Option<PathBuf>;
@ -156,6 +156,7 @@ pub trait VMetadata {
pub struct PhysicalFS { pub struct PhysicalFS {
root: PathBuf, root: PathBuf,
readonly: bool, readonly: bool,
lowercase: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -214,7 +215,11 @@ fn sanitize_path(path: &path::Path) -> Option<PathBuf> {
impl PhysicalFS { impl PhysicalFS {
/// Creates a new PhysicalFS /// Creates a new PhysicalFS
pub fn new(root: &Path, readonly: bool) -> Self { pub fn new(root: &Path, readonly: bool) -> Self {
PhysicalFS { root: root.into(), readonly } PhysicalFS { root: root.into(), readonly, lowercase: false }
}
pub fn new_lowercase(root: &Path) -> Self {
PhysicalFS { root: root.into(), readonly: true, lowercase: true }
} }
/// Takes a given path (&str) and returns /// Takes a given path (&str) and returns
@ -222,7 +227,11 @@ impl PhysicalFS {
/// absolute path you get when appending it /// absolute path you get when appending it
/// to this filesystem's root. /// to this filesystem's root.
fn to_absolute(&self, p: &Path) -> GameResult<PathBuf> { fn to_absolute(&self, p: &Path) -> GameResult<PathBuf> {
if let Some(safe_path) = sanitize_path(p) { if let Some(mut safe_path) = sanitize_path(p) {
if self.lowercase {
safe_path = PathBuf::from(p.to_string_lossy().to_lowercase())
}
let mut root_path = self.root.clone(); let mut root_path = self.root.clone();
root_path.push(safe_path.clone()); root_path.push(safe_path.clone());
@ -376,7 +385,7 @@ impl VFS for PhysicalFS {
} }
/// Retrieve the path entries in this path /// Retrieve the path entries in this path
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> { fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
self.create_root()?; self.create_root()?;
let p = self.to_absolute(path)?; let p = self.to_absolute(path)?;
// This is inconvenient because path() returns the full absolute // This is inconvenient because path() returns the full absolute
@ -511,7 +520,7 @@ impl VFS for OverlayFS {
} }
/// Retrieve the path entries in this path /// Retrieve the path entries in this path
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> { fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
// This is tricky 'cause we have to actually merge iterators together... // This is tricky 'cause we have to actually merge iterators together...
// Doing it the simple and stupid way works though. // Doing it the simple and stupid way works though.
let mut v = Vec::new(); let mut v = Vec::new();

View File

@ -265,7 +265,7 @@ pub fn init(options: LaunchOptions) -> GameResult {
log::info!("Resource directory: {:?}", resource_dir); log::info!("Resource directory: {:?}", resource_dir);
log::info!("Initializing engine..."); log::info!("Initializing engine...");
let mut context = Context::new(); let mut context = Box::pin(Context::new());
#[cfg(not(any(target_os = "android", target_os = "horizon")))] #[cfg(not(any(target_os = "android", target_os = "horizon")))]
mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true))); mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true)));
@ -294,6 +294,23 @@ pub fn init(options: LaunchOptions) -> GameResult {
mount_vfs(&mut context, Box::new(PhysicalFS::new(&data_path, true))); mount_vfs(&mut context, Box::new(PhysicalFS::new(&data_path, true)));
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_path, false))); mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_path, false)));
} }
#[cfg(target_os = "horizon")]
{
let mut data_path = PathBuf::from("sdmc:/switch/doukutsu-rs/data");
let mut user_path = PathBuf::from("sdmc:/switch/doukutsu-rs/user");
let _ = std::fs::create_dir_all(&data_path);
let _ = std::fs::create_dir_all(&user_path);
log::info!("Mounting VFS");
mount_vfs(&mut context, Box::new(PhysicalFS::new(&data_path, true)));
if crate::framework::backend_horizon::mount_romfs() {
mount_vfs(&mut context, Box::new(PhysicalFS::new_lowercase(&PathBuf::from("romfs:/data"))));
}
log::info!("Mounting user VFS");
mount_user_vfs(&mut context, Box::new(PhysicalFS::new(&user_path, false)));
log::info!("ok");
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))] #[cfg(not(any(target_os = "android", target_os = "horizon")))]
{ {
@ -308,6 +325,7 @@ pub fn init(options: LaunchOptions) -> GameResult {
} }
} }
log::info!("Mounting built-in FS");
mount_vfs(&mut context, Box::new(BuiltinFS::new())); mount_vfs(&mut context, Box::new(BuiltinFS::new()));
if options.server_mode { if options.server_mode {
@ -315,15 +333,15 @@ pub fn init(options: LaunchOptions) -> GameResult {
context.headless = true; context.headless = true;
} }
let game = UnsafeCell::new(Game::new(&mut context)?); let mut game = Box::pin(Game::new(&mut context)?);
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };
#[cfg(feature = "scripting-lua")] #[cfg(feature = "scripting-lua")]
{ {
state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context); game.state.get().lua.update_refs(unsafe { &mut *game.state.get() }, &mut context as *mut Context);
} }
state_ref.next_scene = Some(Box::new(LoadingScene::new())); game.state.get_mut().next_scene = Some(Box::new(LoadingScene::new()));
context.run(unsafe { &mut *game.get() })?; log::info!("Starting main loop...");
context.run(game.as_mut().get_mut())?;
Ok(()) Ok(())
} }

View File

@ -39,9 +39,9 @@ pub struct Settings {
pub pause_on_focus_loss: bool, pub pause_on_focus_loss: bool,
#[serde(default = "default_interpolation")] #[serde(default = "default_interpolation")]
pub organya_interpolation: InterpolationMode, pub organya_interpolation: InterpolationMode,
#[serde(default = "default_controller_type")] #[serde(default = "default_p1_controller_type")]
pub player1_controller_type: ControllerType, pub player1_controller_type: ControllerType,
#[serde(default = "default_controller_type")] #[serde(default = "default_p2_controller_type")]
pub player2_controller_type: ControllerType, pub player2_controller_type: ControllerType,
#[serde(default = "p1_default_keymap")] #[serde(default = "p1_default_keymap")]
pub player1_key_map: PlayerKeyMap, pub player1_key_map: PlayerKeyMap,
@ -133,8 +133,21 @@ fn default_screen_shake_intensity() -> ScreenShakeIntensity {
} }
#[inline(always)] #[inline(always)]
fn default_controller_type() -> ControllerType { fn default_p1_controller_type() -> ControllerType {
ControllerType::Keyboard if cfg!(any(target_os = "horizon")) {
ControllerType::Gamepad(0)
} else {
ControllerType::Keyboard
}
}
#[inline(always)]
fn default_p2_controller_type() -> ControllerType {
if cfg!(any(target_os = "horizon")) {
ControllerType::Gamepad(1)
} else {
ControllerType::Keyboard
}
} }
#[inline(always)] #[inline(always)]
@ -216,8 +229,8 @@ impl Settings {
if self.version == 11 { if self.version == 11 {
self.version = 12; self.version = 12;
self.player1_controller_type = default_controller_type(); self.player1_controller_type = default_p1_controller_type();
self.player2_controller_type = default_controller_type(); self.player2_controller_type = default_p2_controller_type();
self.player1_controller_button_map = player_default_controller_button_map(); self.player1_controller_button_map = player_default_controller_button_map();
self.player2_controller_button_map = player_default_controller_button_map(); self.player2_controller_button_map = player_default_controller_button_map();
self.player1_controller_axis_sensitivity = default_controller_axis_sensitivity(); self.player1_controller_axis_sensitivity = default_controller_axis_sensitivity();
@ -396,8 +409,8 @@ impl Default for Settings {
timing_mode: default_timing(), timing_mode: default_timing(),
pause_on_focus_loss: default_pause_on_focus_loss(), pause_on_focus_loss: default_pause_on_focus_loss(),
organya_interpolation: InterpolationMode::Linear, organya_interpolation: InterpolationMode::Linear,
player1_controller_type: default_controller_type(), player1_controller_type: default_p1_controller_type(),
player2_controller_type: default_controller_type(), player2_controller_type: default_p2_controller_type(),
player1_key_map: p1_default_keymap(), player1_key_map: p1_default_keymap(),
player2_key_map: p2_default_keymap(), player2_key_map: p2_default_keymap(),
player1_controller_button_map: player_default_controller_button_map(), player1_controller_button_map: player_default_controller_button_map(),

View File

@ -1,5 +1,7 @@
use crate::common::Color;
use crate::framework::context::Context; use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult}; use crate::framework::error::{GameError, GameResult};
use crate::framework::graphics;
use crate::game::shared_game_state::SharedGameState; use crate::game::shared_game_state::SharedGameState;
use crate::graphics::font::Font; use crate::graphics::font::Font;
use crate::scene::Scene; use crate::scene::Scene;
@ -49,6 +51,8 @@ impl Scene for NoDataScene {
} }
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, Color::from_rgb(30, 0, 0));
state.font.builder().center(state.canvas_size.0).y(10.0).color((255, 100, 100, 255)).draw( state.font.builder().center(state.canvas_size.0).y(10.0).color((255, 100, 100, 255)).draw(
"doukutsu-rs internal error", "doukutsu-rs internal error",
ctx, ctx,
@ -63,36 +67,57 @@ impl Scene for NoDataScene {
&mut state.texture_set, &mut state.texture_set,
)?; )?;
let mut y = 60.0;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
let yellow = (255, 255, 0, 255); let yellow = (255, 255, 0, 255);
state.font.builder().center(state.canvas_size.0).y(60.0).color(yellow).draw( state.font.builder().center(state.canvas_size.0).y(y).color(yellow).draw(
"It's likely that you haven't extracted the game data properly.", "It's likely that you haven't extracted the game data properly.",
ctx, ctx,
&state.constants, &state.constants,
&mut state.texture_set, &mut state.texture_set,
)?; )?;
state.font.builder().center(state.canvas_size.0).y(80.0).color(yellow).draw( y += 20.0;
state.font.builder().center(state.canvas_size.0).y(y).color(yellow).draw(
"Click here to open the guide.", "Click here to open the guide.",
ctx, ctx,
&state.constants, &state.constants,
&mut state.texture_set, &mut state.texture_set,
)?; )?;
state.font.builder().center(state.canvas_size.0).y(100.0).color(yellow).draw( y += 20.0;
state.font.builder().center(state.canvas_size.0).y(y).color(yellow).draw(
REL_URL, REL_URL,
ctx, ctx,
&state.constants, &state.constants,
&mut state.texture_set, &mut state.texture_set,
)?; )?;
y += 20.0;
} }
{ {
state.font.builder().center(state.canvas_size.0).y(140.0).draw( // put max 80 chars per line
&self.err, let mut lines = Vec::new();
ctx, let mut line = String::new();
&state.constants,
&mut state.texture_set, for word in self.err.split(' ') {
)?; if line.len() + word.len() > 80 {
lines.push(line);
line = String::new();
}
line.push_str(word);
line.push(' ');
}
lines.push(line);
for line in lines {
state.font.builder().center(state.canvas_size.0).y(y).draw(
&line,
ctx,
&state.constants,
&mut state.texture_set,
)?;
y += 20.0;
}
} }
Ok(()) Ok(())

View File

@ -2,7 +2,12 @@
pub use webbrowser::open; pub use webbrowser::open;
// stub for platforms webbrowser doesn't support, such as Horizon OS // stub for platforms webbrowser doesn't support, such as Horizon OS
#[cfg(not(feature = "webbrowser"))] #[cfg(not(any(feature = "webbrowser", target_os = "horizon")))]
pub fn open(_url: &str) -> std::io::Result<()> { pub fn open(_url: &str) -> std::io::Result<()> {
Ok(()) Ok(())
} }
#[cfg(target_os = "horizon")]
pub fn open(url: &str) -> std::io::Result<()> {
crate::framework::backend_horizon::web_open(url)
}