| #include <chrono> |
| #include <iostream> |
| #include <random> |
| #include "SDL.h" |
| |
| #include "File.hpp" |
| #include "System.hpp" |
| #include "32blit.hpp" |
| #include "UserCode.hpp" |
| #include "JPEG.hpp" |
| |
| #include "engine/api_private.hpp" |
| |
| // blit framebuffer memory |
| uint8_t framebuffer[320 * 240 * 3]; |
| blit::Surface __fb_hires((uint8_t *)framebuffer, blit::PixelFormat::RGB, blit::Size(320, 240)); |
| blit::Surface __fb_hires_pal((uint8_t *)framebuffer, blit::PixelFormat::P, blit::Size(320, 240)); |
| blit::Surface __fb_lores((uint8_t *)framebuffer, blit::PixelFormat::RGB, blit::Size(160, 120)); |
| |
| static blit::Pen palette[256]; |
| |
| // blit debug callback |
| void debug(std::string message) { |
| std::cout << message << std::endl; |
| } |
| |
| int blit_debugf(const char * psFormatString, va_list args) |
| { |
| return vprintf(psFormatString, args); |
| } |
| |
| // blit screenmode callback |
| blit::ScreenMode _mode = blit::ScreenMode::lores; |
| blit::Surface &set_screen_mode(blit::ScreenMode new_mode) { |
| _mode = new_mode; |
| switch(_mode) { |
| case blit::ScreenMode::lores: |
| blit::screen = __fb_lores; |
| break; |
| case blit::ScreenMode::hires: |
| blit::screen = __fb_hires; |
| break; |
| case blit::ScreenMode::hires_palette: |
| blit::screen = __fb_hires_pal; |
| break; |
| } |
| |
| return blit::screen; |
| } |
| |
| static void set_screen_palette(const blit::Pen *colours, int num_cols) { |
| memcpy(palette, colours, num_cols * sizeof(blit::Pen)); |
| } |
| |
| // blit timer callback |
| std::chrono::steady_clock::time_point start; |
| uint32_t now() { |
| auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start); |
| return (uint32_t)elapsed.count(); |
| } |
| |
| // blit random callback |
| #ifdef __MINGW32__ |
| // Windows/MinGW doesn't support a non-deterministic source of randomness, so we fall back upon the age-old time seed once more |
| // Without this, random_device() will always return the same number and thus our mersenne twister will always produce the same sequence. |
| std::mt19937 random_generator(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()); |
| #else |
| std::random_device random_device; |
| std::mt19937 random_generator(random_device()); |
| #endif |
| std::uniform_int_distribution<uint32_t> random_distribution; |
| uint32_t blit_random() { |
| return random_distribution(random_generator); |
| } |
| |
| |
| // us timer used by profiler |
| |
| void enable_us_timer() |
| { |
| // Enable/initialise timer |
| } |
| |
| uint32_t get_us_timer() |
| { |
| // get current time in us |
| uint64_t ticksPerUs = SDL_GetPerformanceFrequency() / 1000000; |
| return SDL_GetPerformanceCounter() / ticksPerUs; |
| } |
| |
| uint32_t get_max_us_timer() |
| { |
| // largest us value timer can produce for wrapping |
| return UINT32_MAX; |
| } |
| |
| // SDL events |
| const Uint32 System::timer_event = SDL_RegisterEvents(2); |
| const Uint32 System::loop_event = System::timer_event + 1; |
| |
| // Thread bouncers |
| static int system_timer_thread(void *ptr) { |
| // Bounce back in to the class. |
| System *sys = (System *)ptr; |
| return sys->timer_thread(); |
| } |
| |
| static int system_loop_thread(void *ptr) { |
| // Bounce back in to the class. |
| System *sys = (System *)ptr; |
| return sys->update_thread(); |
| } |
| |
| |
| System::System() { |
| m_input = SDL_CreateMutex(); |
| s_timer_stop = SDL_CreateSemaphore(0); |
| s_loop_update = SDL_CreateSemaphore(0); |
| s_loop_redraw = SDL_CreateSemaphore(0); |
| s_loop_ended = SDL_CreateSemaphore(0); |
| |
| __fb_hires_pal.palette = palette; |
| } |
| |
| System::~System() { |
| SDL_DestroyMutex(m_input); |
| SDL_DestroySemaphore(s_timer_stop); |
| SDL_DestroySemaphore(s_loop_update); |
| SDL_DestroySemaphore(s_loop_redraw); |
| SDL_DestroySemaphore(s_loop_ended); |
| } |
| |
| void System::run() { |
| running = true; |
| |
| start = std::chrono::steady_clock::now(); |
| |
| blit::api.now = ::now; |
| blit::api.random = ::blit_random; |
| blit::api.debug = ::debug; |
| blit::api.debugf = ::blit_debugf; |
| blit::api.set_screen_mode = ::set_screen_mode; |
| blit::api.set_screen_palette = ::set_screen_palette; |
| blit::update = ::update; |
| blit::render = ::render; |
| |
| setup_base_path(); |
| |
| blit::api.open_file = ::open_file; |
| blit::api.read_file = ::read_file; |
| blit::api.write_file = ::write_file; |
| blit::api.close_file = ::close_file; |
| blit::api.get_file_length = ::get_file_length; |
| blit::api.list_files = ::list_files; |
| blit::api.file_exists = ::file_exists; |
| blit::api.directory_exists = ::directory_exists; |
| blit::api.create_directory = ::create_directory; |
| blit::api.rename_file = ::rename_file; |
| blit::api.remove_file = ::remove_file; |
| |
| blit::api.enable_us_timer = ::enable_us_timer; |
| blit::api.get_us_timer = ::get_us_timer; |
| blit::api.get_max_us_timer = ::get_max_us_timer; |
| |
| blit::api.decode_jpeg_buffer = blit_decode_jpeg_buffer; |
| blit::api.decode_jpeg_file = blit_decode_jpeg_file; |
| |
| ::set_screen_mode(blit::lores); |
| |
| #ifdef __EMSCRIPTEN__ |
| ::init(); |
| #else |
| t_system_loop = SDL_CreateThread(system_loop_thread, "Loop", (void *)this); |
| t_system_timer = SDL_CreateThread(system_timer_thread, "Timer", (void *)this); |
| #endif |
| } |
| |
| int System::timer_thread() { |
| // Signal the system loop every 20 msec. |
| int dropped = 0; |
| SDL_Event event = {}; |
| event.type = timer_event; |
| |
| while (SDL_SemWaitTimeout(s_timer_stop, 20)) { |
| if (SDL_SemValue(s_loop_update)) { |
| dropped++; |
| if(dropped > 100) { |
| dropped = 100; |
| event.user.code = 2; |
| SDL_PushEvent(&event); |
| } else { |
| event.user.code = 1; |
| SDL_PushEvent(&event); |
| } |
| } else { |
| SDL_SemPost(s_loop_update); |
| dropped = 0; |
| event.user.code = 0; |
| SDL_PushEvent(&event); |
| } |
| } |
| return 0; |
| } |
| |
| int System::update_thread() { |
| // Run the blit user code once every time we are signalled. |
| SDL_Event event = {}; |
| event.type = loop_event; |
| |
| ::init(); // Run init here because the user can make it hang. |
| |
| while (true) { |
| SDL_SemWait(s_loop_update); |
| if(!running) break; |
| loop(); |
| if(!running) break; |
| SDL_PushEvent(&event); |
| SDL_SemWait(s_loop_redraw); |
| } |
| SDL_SemPost(s_loop_ended); |
| return 0; |
| } |
| |
| void System::loop() |
| { |
| SDL_LockMutex(m_input); |
| blit::buttons = shadow_buttons; |
| blit::tilt.x = shadow_tilt[0]; |
| blit::tilt.y = shadow_tilt[1]; |
| blit::tilt.z = shadow_tilt[2]; |
| blit::joystick.x = shadow_joystick[0]; |
| blit::joystick.y = shadow_joystick[1]; |
| SDL_UnlockMutex(m_input); |
| blit::tick(::now()); |
| } |
| |
| Uint32 System::mode() { |
| return _mode; |
| } |
| |
| void System::update_texture(SDL_Texture *texture) { |
| blit::render(::now()); |
| if (_mode == blit::ScreenMode::lores) { |
| SDL_UpdateTexture(texture, nullptr, __fb_lores.data, 160 * 3); |
| } |
| else if(_mode == blit::ScreenMode::hires) { |
| SDL_UpdateTexture(texture, nullptr, __fb_hires.data, 320 * 3); |
| } else { |
| uint8_t col_fb[320 * 240 * 3]; |
| |
| auto in = __fb_hires_pal.data, out = col_fb; |
| |
| for(int i = 0; i < 320 * 240; i++) { |
| uint8_t index = *(in++); |
| (*out++) = palette[index].r; |
| (*out++) = palette[index].g; |
| (*out++) = palette[index].b; |
| } |
| |
| SDL_UpdateTexture(texture, nullptr, col_fb, 320 * 3); |
| } |
| } |
| |
| void System::notify_redraw() { |
| SDL_SemPost(s_loop_redraw); |
| } |
| |
| void System::set_joystick(int axis, float value) { |
| if (axis < 2) { |
| SDL_LockMutex(m_input); |
| shadow_joystick[axis] = value; |
| SDL_UnlockMutex(m_input); |
| } |
| } |
| |
| void System::set_tilt(int axis, float value) { |
| if (axis < 3) { |
| SDL_LockMutex(m_input); |
| shadow_tilt[axis] = value; |
| SDL_UnlockMutex(m_input); |
| } |
| } |
| |
| void System::set_button(int button, bool state) { |
| SDL_LockMutex(m_input); |
| if (state) { |
| shadow_buttons |= button; |
| } else { |
| shadow_buttons &= ~button; |
| } |
| SDL_UnlockMutex(m_input); |
| } |
| |
| void System::stop() { |
| int returnValue; |
| running = false; |
| |
| if(SDL_SemWaitTimeout(s_loop_ended, 500)) { |
| fprintf(stderr, "User code appears to have frozen. Detaching thread.\n"); |
| SDL_DetachThread(t_system_loop); |
| } else { |
| SDL_WaitThread(t_system_loop, &returnValue); |
| } |
| |
| SDL_SemPost(s_timer_stop); |
| SDL_WaitThread(t_system_timer, &returnValue); |
| } |