diff --git a/.appveyor.yml b/.appveyor.yml index a051de1..a88fa9f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,7 +1,5 @@ version: "0.1.0.{build}-{branch}" -os: Visual Studio 2019 - environment: global: PROJECT_NAME: doukutsu-rs @@ -10,37 +8,78 @@ environment: target: x86_64-pc-windows-msvc target_name: win64 job_name: windows-x64 + appveyor_build_worker_image: Visual Studio 2019 # - channel: stable # target: i686-pc-windows-msvc # target_name: win32 # job_name: windows-x32 + - channel: stable + target: x86_64-pc-windows-msvc + target_name: macos + job_name: mac-x64 + appveyor_build_worker_image: macos -install: - - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init -yv --default-toolchain %channel% --default-host %target% - - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - - rustup update - - rustup default %channel% - - rustc -vV - - cargo -vV +matrix: + fast_finish: true -cache: - - '%USERPROFILE%\.cache\sccache -> Cargo.toml' - - '%USERPROFILE%\.cargo -> Cargo.toml' - - 'target -> Cargo.toml' +for: + - + matrix: + only: + - appveyor_build_worker_image: Visual Studio 2019 -#test_script: -# - cargo build --verbose --all -# - cargo test --verbose --all --no-fail-fast + install: + - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init -yv --default-toolchain %channel% --default-host %target% + - set PATH=%PATH%;%USERPROFILE%\.cargo\bin + - rustup update + - rustup default %channel% + - rustc -vV + - cargo -vV -build_script: - - set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION% - - cargo build --release --bin doukutsu-rs - - mkdir release - - copy target\release\doukutsu-rs.exe release - - cd release - - appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip - - 7z x ../game-data.zip - - rename game-data-master data - - 7z a ../doukutsu-rs_%target_name%.zip * - - appveyor PushArtifact ../doukutsu-rs_%target_name%.zip + cache: + - '%USERPROFILE%\.cache\sccache -> Cargo.toml' + - '%USERPROFILE%\.cargo -> Cargo.toml' + - 'target -> Cargo.toml' + + build_script: + - set DRS_BUILD_VERSION_OVERRIDE=%APPVEYOR_BUILD_VERSION% + - cargo build --release --bin doukutsu-rs + - mkdir release + - copy target\release\doukutsu-rs.exe release + - cd release + - appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip + - 7z x ../game-data.zip + - rename game-data-master data + - 7z a ../doukutsu-rs_%target_name%.zip * + - appveyor PushArtifact ../doukutsu-rs_%target_name%.zip + + - + matrix: + only: + - appveyor_build_worker_image: macos + + install: + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + - export PATH=$PATH:$HOME/.cargo/bin + - rustup update + - rustup default $channel + - rustc -vV + - cargo -vV + + cache: + - '$HOME/.cache/sccache -> Cargo.toml' + - '$HOME/.cargo -> Cargo.toml' + - 'target -> Cargo.toml' + + build_script: + - export DRS_BUILD_VERSION_OVERRIDE=$APPVEYOR_BUILD_VERSION + - appveyor DownloadFile https://github.com/doukutsu-rs/game-data/archive/master.zip -FileName ../game-data.zip + - 7z x ../game-data.zip + - rename game-data-master data + - cargo bundle --release + - mkdir release + - cp -a target/release/bundle/osx/doukutsu-rs.app ./release/doukutsu-rs.app + - cd release + - 7z a ../doukutsu-rs_$target_name.zip * + - appveyor PushArtifact ../doukutsu-rs_$target_name.zip diff --git a/.gitignore b/.gitignore index d3ed3c9..08662c2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,48 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +# cave story saves Profile.dat* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ diff --git a/Cargo.toml b/Cargo.toml index 0435ed1..44790d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [package] +name = "doukutsu-rs" +description = "A re-implementation of Cave Story (Doukutsu Monogatari) engine" +version = "0.1.0" authors = ["Alula"] edition = "2018" -name = "doukutsu-rs" -version = "0.1.0" [lib] #crate-type = ["lib", "cdylib"] @@ -22,6 +23,15 @@ panic = 'abort' [profile.dev.package."*"] opt-level = 3 +[package.metadata.bundle] +name = "doukutsu-rs" +identifier = "io.github.doukutsu_rs" +version = "0.1.0" +resources = ["data"] +copyright = "Copyright (c) 2020-2021 Alula and contributors" +category = "Game" +osx_minimum_system_version = "10.12" + [features] default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe"] ogg-playback = ["lewton"] @@ -79,6 +89,9 @@ winit = { git = "https://github.com/alula/winit.git", rev = "6acf76ff192dd8270aa [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["winuser"] } +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2.7" + [target.'cfg(target_os = "android")'.dependencies] ndk = "0.3" ndk-glue = "0.3" diff --git a/build.rs b/build.rs index d26903c..40a8b62 100644 --- a/build.rs +++ b/build.rs @@ -19,6 +19,10 @@ fn main() { // .unwrap(); // } + if target.contains("macos") { + println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.12") + } + if is_android { println!("cargo:rustc-link-lib=dylib=GLESv2"); println!("cargo:rustc-link-lib=dylib=EGL"); diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index ba01bd9..221dbeb 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -13,6 +13,7 @@ use sdl2::mouse::{Cursor, SystemCursor}; use sdl2::pixels::PixelFormatEnum; use sdl2::render::{Texture, TextureCreator, WindowCanvas}; use sdl2::video::WindowContext; +use sdl2::video::GLProfile; use sdl2::{keyboard, pixels, EventPump, Sdl, VideoSubsystem}; use crate::common::{Color, Rect}; @@ -66,6 +67,11 @@ impl SDL2EventLoop { pub fn new(sdl: &Sdl) -> GameResult> { let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; let video = sdl.video().map_err(|e| GameError::WindowError(e))?; + let gl_attr = video.gl_attr(); + + gl_attr.set_context_profile(GLProfile::Core); + gl_attr.set_context_version(3, 0); + let mut window = video.window("Cave Story (doukutsu-rs)", 640, 480); window.position_centered(); window.resizable(); @@ -121,6 +127,29 @@ impl BackendEventLoop for SDL2EventLoop { } loop { + + #[cfg(target_os = "macos")] + unsafe { + use objc::*; + let mut winfo: sdl2_sys::SDL_SysWMinfo = mem::MaybeUninit::uninit().assume_init(); + winfo.version.major = sdl2_sys::SDL_MAJOR_VERSION as _; + winfo.version.minor = sdl2_sys::SDL_MINOR_VERSION as _; + winfo.version.patch = sdl2_sys::SDL_PATCHLEVEL as _; + + let mut whandle = self.refs.borrow().canvas.window().raw(); + + if sdl2_sys::SDL_GetWindowWMInfo(whandle, &mut winfo as *mut _) != sdl2_sys::SDL_bool::SDL_FALSE { + let window = winfo.info.x11.display as *mut objc::runtime::Object; + let _: () = msg_send![window, setTitlebarAppearsTransparent:1]; + let _: () = msg_send![window, setTitleVisibility:1]; // NSWindowTitleHidden + let mut style_mask: u32 = msg_send![window, styleMask]; + + style_mask |= 1 << 15; // NSWindowStyleMaskFullSizeContentView + + let _: () = msg_send![window, setStyleMask:style_mask]; + } + } + for event in self.event_pump.poll_iter() { imgui_sdl2.handle_event(imgui, &event); diff --git a/src/framework/render_opengl.rs b/src/framework/render_opengl.rs index 6934111..18c8afc 100644 --- a/src/framework/render_opengl.rs +++ b/src/framework/render_opengl.rs @@ -279,6 +279,52 @@ fn check_shader_compile_status(shader: u32, gl: &Gl) -> bool { } const VERTEX_SHADER_BASIC: &str = r" +#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); +} + +"; + +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 VERTEX_SHADER_BASIC_GLES: &str = r" #version 100 precision mediump float; @@ -299,7 +345,7 @@ void main() "; -const FRAGMENT_SHADER_TEXTURED: &str = r" +const FRAGMENT_SHADER_TEXTURED_GLES: &str = r" #version 100 precision mediump float; @@ -315,7 +361,7 @@ void main() "; -const FRAGMENT_SHADER_COLOR: &str = r" +const FRAGMENT_SHADER_COLOR_GLES: &str = r" #version 100 precision mediump float; @@ -366,15 +412,19 @@ impl ImguiData { } } - fn init(&mut self, imgui: &mut imgui::Context, gl: &Gl) { + fn init(&mut self, gles2_mode: bool, imgui: &mut imgui::Context, gl: &Gl) { self.initialized = true; - let vert_sources = [VERTEX_SHADER_BASIC.as_ptr() as *const GLchar]; - let frag_sources_tex = [FRAGMENT_SHADER_TEXTURED.as_ptr() as *const GLchar]; - let frag_sources_fill = [FRAGMENT_SHADER_COLOR.as_ptr() as *const GLchar]; - let vert_sources_len = [VERTEX_SHADER_BASIC.len() as GLint - 1]; - let frag_sources_tex_len = [FRAGMENT_SHADER_TEXTURED.len() as GLint - 1]; - let frag_sources_fill_len = [FRAGMENT_SHADER_COLOR.len() as GLint - 1]; + let vshdr_basic = if gles2_mode { VERTEX_SHADER_BASIC_GLES } else { VERTEX_SHADER_BASIC }; + let fshdr_tex = if gles2_mode { FRAGMENT_SHADER_TEXTURED_GLES } else { FRAGMENT_SHADER_TEXTURED }; + let fshdr_fill = if gles2_mode { FRAGMENT_SHADER_COLOR_GLES } else { FRAGMENT_SHADER_COLOR }; + + let vert_sources = [vshdr_basic.as_ptr() as *const GLchar]; + let frag_sources_tex = [fshdr_tex.as_ptr() as *const GLchar]; + let frag_sources_fill = [fshdr_fill.as_ptr() as *const GLchar]; + let vert_sources_len = [vshdr_basic.len() as GLint - 1]; + let frag_sources_tex_len = [fshdr_tex.len() as GLint - 1]; + let frag_sources_fill_len = [fshdr_fill.len() as GLint - 1]; unsafe { self.program_tex = gl.gl.CreateProgram(); @@ -520,10 +570,11 @@ impl OpenGLRenderer { fn get_context(&mut self) -> Option<(&mut GLContext, &'static Gl)> { let imgui = unsafe { &mut *self.imgui.get() }; + let gles2 = self.refs.gles2_mode; let gl = load_gl(&mut self.refs); if !self.imgui_data.initialized { - self.imgui_data.init(imgui, gl); + self.imgui_data.init(gles2, imgui, gl); } Some((&mut self.refs, gl)) diff --git a/src/lib.rs b/src/lib.rs index 8aea7ee..9bb1e30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,6 +200,22 @@ pub fn init() -> GameResult { if resource_dir.file_name().is_some() { let _ = resource_dir.pop(); } + + #[cfg(target_os = "macos")] + { + let mut bundle_dir = resource_dir.clone(); + let _ = bundle_dir.pop(); + let mut bundle_exec_dir = bundle_dir.clone(); + + bundle_exec_dir.push("MacOS"); + bundle_dir.push("Resources"); + + if bundle_exec_dir.is_dir() && bundle_dir.is_dir() { + log::info!("Running in macOS bundle mode"); + resource_dir = bundle_dir + } + } + resource_dir.push("data"); resource_dir };