blob: a2b17d692a0cbd815da0746fb8445cbff3304360 [file] [log] [blame] [edit]
#include <stdio.h>
#include <random>
#include "hardware/clocks.h"
#include "hardware/structs/rosc.h"
#include "pico/binary_info.h"
#include "pico/stdlib.h"
#ifdef DISPLAY_SCANVIDEO
#include "pico/scanvideo.h"
#include "pico/scanvideo/composable_scanline.h"
#endif
#include "audio.hpp"
#include "config.h"
#include "file.hpp"
#include "input.hpp"
#include "led.hpp"
#include "st7789.hpp"
#include "usb.hpp"
#include "engine/api_private.hpp"
#include "engine/engine.hpp"
#include "graphics/surface.hpp"
using namespace blit;
#ifdef DISPLAY_ST7789
static const int lores_page_size = (ST7789_WIDTH / 2) * (ST7789_HEIGHT / 2) * 2;
#if ALLOW_HIRES
uint8_t screen_fb[ST7789_WIDTH * ST7789_HEIGHT * 2];
#else
uint8_t screen_fb[lores_page_size * 2]; // double-buffered
#endif
static Surface lores_screen(screen_fb, PixelFormat::RGB565, Size(ST7789_WIDTH / 2, ST7789_HEIGHT / 2));
static Surface hires_screen(screen_fb, PixelFormat::RGB565, Size(ST7789_WIDTH, ST7789_HEIGHT));
//static Surface hires_palette_screen(screen_fb, PixelFormat::P, Size(320, 240));
#elif defined(DISPLAY_SCANVIDEO)
uint8_t screen_fb[160 * 120 * 4];
static Surface lores_screen(screen_fb, PixelFormat::RGB565, Size(160, 120));
#endif
static blit::AudioChannel channels[CHANNEL_COUNT];
#ifdef DISPLAY_ST7789
#ifdef PIMORONI_PICOSYSTEM
// non-default pins
// TODO: clean this up?
pimoroni::ST7789 st7789(240, 240, (uint16_t *)screen_fb, 5, 9, 6, 7, 12, 8, 4);
#else
pimoroni::ST7789 st7789(ST7789_WIDTH, ST7789_HEIGHT, (uint16_t *)screen_fb);
#endif
static bool have_vsync = false;
#endif
ScreenMode cur_screen_mode = ScreenMode::lores;
// double buffering for lores
static volatile int buf_index = 0;
static volatile bool do_render = true;
static Surface &set_screen_mode(ScreenMode mode) {
switch(mode) {
case ScreenMode::lores:
screen = lores_screen;
// window
#ifdef DISPLAY_ST7789
if(have_vsync)
do_render = true; // prevent starting an update during switch
st7789.set_pixel_double(true);
#endif
break;
case ScreenMode::hires:
#if defined(DISPLAY_ST7789) && ALLOW_HIRES
if(have_vsync)
do_render = true;
screen = hires_screen;
st7789.frame_buffer = (uint16_t *)screen_fb;
st7789.set_pixel_double(false);
#else
return screen;
#endif
break;
//case ScreenMode::hires_palette:
// screen = hires_palette_screen;
// break;
}
cur_screen_mode = mode;
return blit::screen;
}
static uint32_t now() {
return to_ms_since_boot(get_absolute_time());
}
static uint32_t get_random_seed() {
uint32_t seed = 0;
// use the hardware random bit to seed
for(int i = 0; i < 32; i++) {
seed <<= 1;
seed |= rosc_hw->randombit & 1;
sleep_us(1); // don't read too fast
}
return seed;
}
static uint32_t random() {
static std::mt19937 random_generator(get_random_seed());
static std::uniform_int_distribution<uint32_t> random_distribution;
return random_distribution(random_generator);
}
static void debug(const char *message) {
fputs(message, stdout);
usb_debug(message);
}
static bool is_storage_available() {
return true; // TODO: optional storage?
}
static uint32_t get_us_timer() {
return to_us_since_boot(get_absolute_time());
}
static uint32_t get_max_us_timer() {
return 0xFFFFFFFF; // it's a 64bit timer...
}
const char *get_launch_path() {
return nullptr;
}
static GameMetadata get_metadata() {
GameMetadata ret;
// parse binary info
extern binary_info_t *__binary_info_start, *__binary_info_end;
for(auto tag_ptr = &__binary_info_start; tag_ptr != &__binary_info_end ; tag_ptr++) {
if((*tag_ptr)->type != BINARY_INFO_TYPE_ID_AND_STRING || (*tag_ptr)->tag != BINARY_INFO_TAG_RASPBERRY_PI)
continue;
auto id_str_tag = (binary_info_id_and_string_t *)*tag_ptr;
switch(id_str_tag->id) {
case BINARY_INFO_ID_RP_PROGRAM_NAME:
ret.title = id_str_tag->value;
break;
case BINARY_INFO_ID_RP_PROGRAM_VERSION_STRING:
ret.version = id_str_tag->value;
break;
case BINARY_INFO_ID_RP_PROGRAM_URL:
ret.url = id_str_tag->value;
break;
case BINARY_INFO_ID_RP_PROGRAM_DESCRIPTION:
ret.description = id_str_tag->value;
break;
}
}
return ret;
}
// user funcs
void init();
void render(uint32_t);
void update(uint32_t);
#ifdef DISPLAY_ST7789
void vsync_callback(uint gpio, uint32_t events) {
if(!do_render && !st7789.dma_is_busy()) {
st7789.update();
do_render = true;
}
}
#endif
#ifdef DISPLAY_SCANVIDEO
static void fill_scanline_buffer(struct scanvideo_scanline_buffer *buffer) {
static uint32_t postamble[] = {
0x0000u | (COMPOSABLE_EOL_ALIGN << 16)
};
int w = screen.bounds.w;
buffer->data[0] = 4;
buffer->data[1] = host_safe_hw_ptr(buffer->data + 8);
buffer->data[2] = (w - 4) / 2; // first four pixels are handled separately
uint16_t *pixels = ((uint16_t *)screen_fb) + buf_index * (160 * 120) + scanvideo_scanline_number(buffer->scanline_id) * w;
buffer->data[3] = host_safe_hw_ptr(pixels + 4);
buffer->data[4] = count_of(postamble);
buffer->data[5] = host_safe_hw_ptr(postamble);
buffer->data[6] = 0;
buffer->data[7] = 0;
buffer->data_used = 8;
// 3 pixel run followed by main run, consuming the first 4 pixels
buffer->data[8] = (pixels[0] << 16u) | COMPOSABLE_RAW_RUN;
buffer->data[9] = (pixels[1] << 16u) | 0;
buffer->data[10] = (COMPOSABLE_RAW_RUN << 16u) | pixels[2];
buffer->data[11] = (((w - 3) + 1 - 3) << 16u) | pixels[3]; // note we add one for the black pixel at the end
}
static int64_t timer_callback(alarm_id_t alarm_id, void *user_data) {
static int last_frame = 0;
struct scanvideo_scanline_buffer *buffer = scanvideo_begin_scanline_generation(false);
while (buffer) {
fill_scanline_buffer(buffer);
scanvideo_end_scanline_generation(buffer);
auto next_frame = scanvideo_frame_number(scanvideo_get_next_scanline_id());
if(next_frame != last_frame) {
//if(scanvideo_in_vblank() && !do_render) {
do_render = true;
last_frame = next_frame;
break;
}
buffer = scanvideo_begin_scanline_generation(false);
}
return 100;
}
#endif
int main() {
#if OVERCLOCK_250
set_sys_clock_khz(250000, false);
#endif
stdio_init_all();
api.channels = ::channels;
api.set_screen_mode = ::set_screen_mode;
// api.set_screen_palette = ::set_screen_palette;
api.now = ::now;
api.random = ::random;
// api.exit = ::exit;
// serial debug
api.debug = ::debug;
// files
api.open_file = ::open_file;
api.read_file = ::read_file;
api.write_file = ::write_file;
api.close_file = ::close_file;
api.get_file_length = ::get_file_length;
api.list_files = ::list_files;
api.file_exists = ::file_exists;
api.directory_exists = ::directory_exists;
api.create_directory = ::create_directory;
api.rename_file = ::rename_file;
api.remove_file = ::remove_file;
api.get_save_path = ::get_save_path;
api.is_storage_available = ::is_storage_available;
// profiler
// api.enable_us_timer = ::enable_us_timer;
api.get_us_timer = ::get_us_timer;
api.get_max_us_timer = ::get_max_us_timer;
// jpeg
// api.decode_jpeg_buffer = ::decode_jpeg_buffer;
// api.decode_jpeg_file = ::decode_jpeg_file;
// launcher
// api.launch = ::launch;
// api.erase_game = ::erase_game;
// api.get_type_handler_metadata = ::get_type_handler_metadata;
api.get_launch_path = ::get_launch_path;
// multiplayer
api.is_multiplayer_connected = ::is_multiplayer_connected;
api.set_multiplayer_enabled = ::set_multiplayer_enabled;
api.send_message = ::send_multiplayer_message;
// api.flash_to_tmp = ::flash_to_tmp;
// api.tmp_file_closed = ::tmp_file_closed;
api.get_metadata = ::get_metadata;
init_led();
#ifdef DISPLAY_ST7789
bool backlight_enabled = false;
st7789.init();
st7789.clear();
have_vsync = st7789.vsync_callback(vsync_callback);
#endif
#ifdef DISPLAY_SCANVIDEO
//scanvideo_setup(&vga_mode_320x240_60); // not quite
scanvideo_setup(&vga_mode_160x120_60);
scanvideo_timing_enable(true);
add_alarm_in_us(100, timer_callback, nullptr, true);
#endif
init_input();
init_fs();
init_usb();
::set_screen_mode(ScreenMode::lores);
blit::render = ::render;
blit::update = ::update;
init_audio();
// user init
::init();
uint32_t last_render = 0;
while(true) {
auto now = ::now();
#ifdef DISPLAY_ST7789
if((do_render || (!have_vsync && now - last_render >= 20)) && (cur_screen_mode == ScreenMode::lores || !st7789.dma_is_busy())) {
if(cur_screen_mode == ScreenMode::lores) {
buf_index ^= 1;
screen.data = screen_fb + (buf_index) * lores_page_size;
st7789.frame_buffer = (uint16_t *)screen.data;
}
::render(now);
if(!have_vsync)
st7789.update();
if(last_render && !backlight_enabled) {
// the first render should have made it to the screen at this point
st7789.set_backlight(255);
backlight_enabled = true;
}
last_render = now;
do_render = false;
}
#elif defined(DISPLAY_SCANVIDEO)
if(do_render) {
screen.data = screen_fb + (buf_index ^ 1) * (160 * 120 * 2); // only works because there's no "firmware" here
::render(now);
buf_index ^= 1;
do_render = false;
}
#endif
update_input();
int ms_to_next_update = tick(::now());
update_audio(now);
update_led();
update_usb();
if(ms_to_next_update > 1)
best_effort_wfe_or_timeout(make_timeout_time_ms(ms_to_next_update - 1));
}
return 0;
}