mod app_clock;
mod core_audio;
mod event_handler;
pub use self::core_audio::CoreAudio;
use std::ffi::CStr;
use std::path::Path;
use std::fs::File;
use std::io::BufReader;
use sdl2::{self, VideoSubsystem};
use sdl2::video::{FullscreenType, GLProfile};
use sdl2::video::gl_attr::GLAttr;
use sdl2::image::LoadTexture;
use sdl2::mixer::{Sdl2MixerContext, INIT_OGG, DEFAULT_CHANNELS, AUDIO_S16LSB};
use sdl2::render::{Renderer as SdlRenderer};
use gl;
use gl::types::*;
use crate::{AppContext, App};
use crate::app_info::AppInfo;
use crate::renderer::Renderer;
use crate::renderer::core_renderer::CoreRenderer;
use crate::renderer::render_buffer::RenderBuffer;
use crate::renderer::atlas::Atlas;
use crate::asset_id::{AppAssetId, IdU16};
use self::app_clock::AppClock;
use self::event_handler::EventHandler;
use super::mark_app_created_flag;
#[macro_export]
macro_rules! gate_header {
() => {};
}
pub fn run<AS: AppAssetId, AP: App<AS>>(info: AppInfo, mut app: AP) {
mark_app_created_flag();
#[cfg(target_os = "windows")]
sdl2::hint::set("SDL_RENDER_DRIVER", "opengles2");
let sdl_context = sdl2::init().unwrap();
let video = sdl_context.video().unwrap();
let _sdl_audio = sdl_context.audio().unwrap();
let _mixer_context = mixer_init();
mixer_setup();
gl_hints(video.gl_attr());
let timer = sdl_context.timer().unwrap();
let mut event_handler = EventHandler::new(sdl_context.event_pump().unwrap());
let window = video.window(info.title, info.window_pixels.0, info.window_pixels.1)
.position_centered().opengl().resizable()
.build().unwrap();
let mut sdl_renderer = window.renderer()
.accelerated()
.build().unwrap();
init_gl(&video);
let mut renderer = build_renderer(&info, &sdl_renderer);
gl_error_check();
let mut ctx = AppContext::new(CoreAudio::new(AS::Sound::count()), renderer.app_dims(), renderer.native_px());
if info.print_gl_info { print_gl_info(); }
app.start(&mut ctx);
let mut clock = AppClock::new(timer, &info);
loop {
unsafe {
gl::ClearColor(0., 0., 0., 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
let screen_dims = sdl_renderer.window().unwrap().size();
if screen_dims.0 > 0 && screen_dims.1 > 0 {
renderer.set_screen_dims(screen_dims);
ctx.set_dims(renderer.app_dims(), renderer.native_px());
app.render(&mut renderer, &ctx);
renderer.flush();
}
sdl_renderer.present();
gl_error_check();
let elapsed = clock.step();
match (ctx.is_fullscreen(), ctx.desires_fullscreen()) {
(false, true) => {
let success = sdl_renderer.window_mut().unwrap().set_fullscreen(FullscreenType::Desktop).is_ok();
ctx.set_is_fullscreen(success);
},
(true, false) => {
let success = sdl_renderer.window_mut().unwrap().set_fullscreen(FullscreenType::Off).is_ok();
ctx.set_is_fullscreen(!success);
},
(false, false) | (true, true) => {},
}
let continuing = event_handler.process_events(&mut app, &mut ctx, &renderer);
if !continuing { break; }
app.advance(elapsed.min(crate::MAX_TIMESTEP), &mut ctx);
if ctx.take_close_request() { break; }
}
}
fn build_renderer<AS: AppAssetId>(info: &AppInfo, sdl_renderer: &SdlRenderer) -> Renderer<AS> {
let sprites_atlas = Atlas::new(BufReader::new(File::open("assets/sprites.atlas").unwrap())).unwrap();
let render_buffer = RenderBuffer::new(&info, info.window_pixels, sprites_atlas);
let mut sprites_tex = sdl_renderer.load_texture(Path::new("assets/sprites.png")).unwrap();
unsafe {
sprites_tex.gl_bind_texture();
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
sprites_tex.gl_unbind_texture();
}
let core_renderer = CoreRenderer::new(sprites_tex);
Renderer::<AS>::new(render_buffer, core_renderer)
}
fn mixer_init() -> Sdl2MixerContext {
match sdl2::mixer::init(INIT_OGG) {
Ok(ctx) => ctx,
Err(ref msg) if msg.as_str() == "OGG support not available" => Sdl2MixerContext,
Err(msg) => panic!("sdl2::mixer::init failed: {}", msg),
}
}
fn mixer_setup() {
sdl2::mixer::open_audio(44100, AUDIO_S16LSB, DEFAULT_CHANNELS, 1024).unwrap();
sdl2::mixer::allocate_channels(4);
}
fn gl_hints(gl_attr: GLAttr) {
gl_attr.set_context_profile(GLProfile::Core);
gl_attr.set_context_flags().debug().set();
gl_attr.set_context_version(3, 0);
}
fn init_gl(video: &VideoSubsystem) {
gl::load_with(|name| video.gl_get_proc_address(name) as *const _);
unsafe {
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
}
}
fn print_gl_info() {
println!("OpenGL version: {:?}", gl_get_string(gl::VERSION));
println!("GLSL version: {:?}", gl_get_string(gl::SHADING_LANGUAGE_VERSION));
println!("Vendor: {:?}", gl_get_string(gl::VENDOR));
println!("Renderer: {:?}", gl_get_string(gl::RENDERER));
}
fn gl_get_string<'a>(name: GLenum) -> &'a CStr {
unsafe {
CStr::from_ptr(gl::GetString(name) as *const i8)
}
}
fn gl_error_check() {
let error = unsafe { gl::GetError() };
assert!(error == gl::NO_ERROR, "unexpected OpenGL error, code {}", error);
}