rockbox/apps/plugins/xworld/sys.c
William Wilgus 3237ae4a4f LCD core move buf ptr and address look up function viewport struct
I'm currently running up against the limitations of the lcd_draw functions
I want these functions to be able to be used on any size buffer not
just buffers with a stride matching the underlying device

[DONE] allow the framebuffer to be decoupled from the device framebuffer
[DONE need examples] allow for some simple blit like transformations
[DONE] remove the device framebuffer from the plugin api
[DONE}ditto remote framebuffer
[DONE] remove _viewport_get_framebuffer you can call struct *vp = lcd_set_viewport(NULL) and vp->buffer->fb_ptr

while remote lcds may compile (and work in the sim) its not been tested on targets

[FIXED] backdrops need work to be screen agnostic

[FIXED] screen statusbar is not being combined into the main viewport correctly yet

[FIXED] screen elements are displayed incorrectly  after switch to void*

[FIXED] core didn't restore proper viewport on splash etc.

[NEEDS TESTING] remote lcd garbled data

[FIXED] osd lib garbled screen on bmp_part

[FIXED] grey_set_vp needs to return old viewport like lcd_set_viewport

[FIXED] Viewport update now handles viewports with differing buffers/strides by copying to the main buffer

[FIXED] splash on top of WPS leaves old framebuffer data (doesn't redraw)
[UPDATE] refined this a bit more to have clear_viewport set the clean bit and have skin_render do its own screen clear
scrolling viewports no longer trigger wps refresh
also fixed a bug where guisyncyesno was displaying and then disappearing

[ADDED!] New LCD macros that allow you to create properly size frame buffers in you desired size without wasting bytes
(LCD_ and LCD_REMOTE_)
LCD_STRIDE(w, h) same as STRIDE_MAIN
LCD_FBSTRIDE(w, h) returns target specific stride for a buffer W x H
LCD_NBELEMS(w, h) returns the number of fb_data sized elemenst needed for a buffer W x H
LCD_NATIVE_STRIDE(s) conversion between rockbox native vertical and lcd native stride (2bitH)
test_viewports.c has an example of usage

[FIXED!!] 2bit targets don't respect non-native strides
[FIXED] Few define snags

Change-Id: I0d04c3834e464eca84a5a715743a297a0cefd0af
2020-10-26 12:28:48 -04:00

1161 lines
34 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2014 Franklin Wei, Benjamin Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* TODO: */
/* vertical stride support (as of Dec. 2014, only the M:Robe 500 has a color,
vertical stride LCD) */
/* monochrome/grayscale support (many grayscale targets have vertical strides,
so get that working first!) */
#include "plugin.h"
#include "lib/display_text.h"
#include "lib/helper.h"
#include "lib/pluginlib_actions.h"
#include "lib/pluginlib_bmp.h"
#include "lib/pluginlib_exit.h"
#include "lib/keymaps.h"
#include "sys.h"
#include "parts.h"
#include "engine.h"
static struct System* save_sys;
static fb_data *lcd_fb = NULL;
static bool sys_save_settings(struct System* sys)
{
File f;
file_create(&f, false);
if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "wb"))
{
return false;
}
file_write(&f, &sys->settings, sizeof(sys->settings));
file_close(&f);
return true;
}
static void sys_rotate_keymap(struct System* sys)
{
switch(sys->settings.rotation_option)
{
case 0:
sys->keymap.up = BTN_UP;
sys->keymap.down = BTN_DOWN;
sys->keymap.left = BTN_LEFT;
sys->keymap.right = BTN_RIGHT;
#ifdef BTN_HAVE_DIAGONAL
sys->keymap.upleft = BTN_UP_LEFT;
sys->keymap.upright = BTN_UP_RIGHT;
sys->keymap.downleft = BTN_DOWN_RIGHT;
sys->keymap.downright = BTN_DOWN_LEFT;
#endif
break;
case 1:
sys->keymap.up = BTN_RIGHT;
sys->keymap.down = BTN_LEFT;
sys->keymap.left = BTN_UP;
sys->keymap.right = BTN_DOWN;
#ifdef BTN_HAVE_DIAGONAL
sys->keymap.upleft = BTN_UP_RIGHT;
sys->keymap.upright = BTN_DOWN_RIGHT;
sys->keymap.downleft = BTN_UP_LEFT;
sys->keymap.downright = BTN_DOWN_LEFT;
#endif
break;
case 2:
sys->keymap.up = BTN_LEFT;
sys->keymap.down = BTN_RIGHT;
sys->keymap.left = BTN_DOWN;
sys->keymap.right = BTN_UP;
#ifdef BTN_HAVE_DIAGONAL
sys->keymap.upleft = BTN_DOWN_LEFT;
sys->keymap.upright = BTN_UP_LEFT;
sys->keymap.downleft = BTN_DOWN_RIGHT;
sys->keymap.downright = BTN_DOWN_LEFT;
#endif
break;
default:
error("sys_rotate_keymap: fall-through!");
}
}
static bool sys_load_settings(struct System* sys)
{
File f;
file_create(&f, false);
if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "rb"))
{
return false;
}
file_read(&f, &sys->settings, sizeof(sys->settings));
file_close(&f);
sys_rotate_keymap(sys);
return true;
}
void exit_handler(void)
{
sys_save_settings(save_sys);
sys_stopAudio(save_sys);
rb->timer_unregister();
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
#ifdef HAVE_BACKLIGHT
backlight_use_settings();
#endif
}
static bool sys_do_help(void)
{
#ifdef HAVE_LCD_COLOR
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_set_background(LCD_BLACK);
#endif
rb->lcd_setfont(FONT_UI);
char *help_text[] = {
"XWorld", "",
"XWorld", "is", "a", "port", "of", "a", "bytecode", "interpreter", "for", "`Another", "World',", "a", "cinematic", "adventure", "game", "by", "Eric", "Chahi.",
"",
"",
"Level", "Codes:", "",
"Level", "1:", "LDKD", "",
"Level", "2:", "HTDC", "",
"Level", "3:", "CLLD", "",
"Level", "4:", "LBKG", "",
"Level", "5:", "XDDJ", "",
"Level", "6:", "FXLC", "",
"Level", "7:", "KRFK", "",
"Level", "8:", "KFLB", "",
"Level", "9:", "DDRX", "",
"Level", "10:", "BFLX", "",
"Level", "11:", "BRTD", "",
"Level", "12:", "TFBB", "",
"Level", "13:", "TXHF", "",
"Level", "14:", "CKJL", "",
"Level", "15:", "LFCK", "",
};
struct style_text style[] = {
{ 0, TEXT_CENTER | TEXT_UNDERLINE },
};
return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
}
static const struct opt_items scaling_settings[3] = {
{ "Disabled", -1 },
{ "Fast" , -1 },
#ifdef HAVE_LCD_COLOR
{ "Good" , -1 }
#endif
};
static const struct opt_items rotation_settings[3] = {
{ "Disabled" , -1 },
{ "Clockwise" , -1 },
{ "Counterclockwise", -1 }
};
static void do_video_settings(struct System* sys)
{
MENUITEM_STRINGLIST(menu, "Video Settings", NULL,
"Negative",
#ifdef SYS_MOTION_BLUR
"Motion Blur",
#endif
"Scaling",
"Rotation",
"Show FPS",
"Zoom on Code",
"Back");
int sel = 0;
while(1)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
rb->set_bool("Negative", &sys->settings.negative_enabled);
break;
#ifdef SYS_MOTION_BLUR
case 1:
rb->set_bool("Motion Blur", &sys->settings.blur);
break;
#endif
#ifndef SYS_MOTION_BLUR
case 1:
#else
case 2:
#endif
rb->set_option("Scaling", &sys->settings.scaling_quality, INT, scaling_settings,
#ifdef HAVE_LCD_COLOR
3
#else
2
#endif
, NULL);
if(sys->settings.scaling_quality &&
sys->settings.zoom)
{
rb->splash(HZ*2, "Zoom automatically disabled.");
sys->settings.zoom = false;
}
break;
#ifndef SYS_MOTION_BLUR
case 2:
#else
case 3:
#endif
rb->set_option("Rotation", &sys->settings.rotation_option, INT, rotation_settings, 3, NULL);
if(sys->settings.rotation_option &&
sys->settings.zoom)
{
rb->splash(HZ*2, "Zoom automatically disabled.");
sys->settings.zoom = false;
}
sys_rotate_keymap(sys);
break;
#ifndef SYS_MOTION_BLUR
case 3:
#else
case 4:
#endif
rb->set_bool("Show FPS", &sys->settings.showfps);
break;
#ifndef SYS_MOTION_BLUR
case 4:
#else
case 5:
#endif
rb->set_bool("Zoom on Code", &sys->settings.zoom);
/* zoom only works with scaling and rotation disabled */
if(sys->settings.zoom && (sys->settings.scaling_quality || sys->settings.rotation_option))
{
rb->splash(HZ*2, "Scaling and rotation automatically disabled.");
sys->settings.scaling_quality = 0;
sys->settings.rotation_option = 0;
}
break;
default:
sys_save_settings(sys);
return;
}
}
}
#define MAX_SOUNDBUF_SIZE 256
static void do_sound_settings(struct System* sys)
{
MENUITEM_STRINGLIST(menu, "Sound Settings", NULL,
"Enabled",
"Buffer Level",
"Volume",
"Back",
);
int sel = 0;
while(1)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
rb->set_bool("Enabled", &sys->settings.sound_enabled);
break;
case 1:
rb->set_int("Buffer Level", "samples", UNIT_INT, &sys->settings.sound_bufsize, NULL, 16, 16, MAX_SOUNDBUF_SIZE, NULL);
break;
case 2:
{
const struct settings_list* vol =
rb->find_setting(&rb->global_settings->volume, NULL);
rb->option_screen((struct settings_list*)vol, NULL, false, "Volume");
break;
}
case 3:
default:
sys_save_settings(sys);
return;
}
}
}
static void sys_reset_settings(struct System* sys)
{
sys->settings.negative_enabled = false;
sys->settings.rotation_option = 0; // off
sys->settings.scaling_quality = 1; // fast
sys->settings.sound_enabled = true;
sys->settings.sound_bufsize = 32; /* keep this low */
sys->settings.showfps = false;
sys->settings.zoom = false;
sys->settings.blur = false;
sys_rotate_keymap(sys);
}
static struct System* mainmenu_sysptr;
static int mainmenu_cb(int action,
const struct menu_item_ex *this_item,
struct gui_synclist *this_list)
{
(void)this_list;
int idx = ((intptr_t)this_item);
if(action == ACTION_REQUEST_MENUITEM && !mainmenu_sysptr->loaded && (idx == 0 || idx == 8 || idx == 10))
return ACTION_EXIT_MENUITEM;
return action;
}
static AudioCallback audio_callback = NULL;
static void get_more(const void** start, size_t* size);
static void* audio_param;
static struct System* audio_sys;
/************************************** MAIN MENU ***************************************/
/* called after game init */
void sys_menu(struct System* sys)
{
sys_stopAudio(sys);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
/* boost for load */
rb->cpu_boost(true);
#endif
rb->splash(0, "Loading...");
sys->loaded = engine_loadGameState(sys->e, 0);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
rb->lcd_update();
mainmenu_sysptr = sys;
MENUITEM_STRINGLIST(menu, "XWorld Menu", mainmenu_cb,
"Resume Game", /* 0 */
"Start New Game", /* 1 */
"Video Settings", /* 2 */
"Sound Settings", /* 3 */
"Fast Mode", /* 4 */
"Help", /* 5 */
"Reset Settings", /* 6 */
"Load", /* 7 */
"Save", /* 8 */
"Quit without Saving", /* 9 */
"Save and Quit"); /* 10 */
bool quit = false;
while(!quit)
{
switch(rb->do_menu(&menu, NULL, NULL, false))
{
case 0:
quit = true;
break;
case 1:
vm_initForPart(&sys->e->vm, GAME_PART_FIRST); // This game part is the protection screen
quit = true;
break;
case 2:
do_video_settings(sys);
break;
case 3:
do_sound_settings(sys);
break;
case 4:
rb->set_bool("Fast Mode", &sys->e->vm._fastMode);
sys_save_settings(sys);
break;
case 5:
sys_do_help();
break;
case 6:
sys_reset_settings(sys);
sys_save_settings(sys);
break;
case 7:
rb->splash(0, "Loading...");
sys->loaded = engine_loadGameState(sys->e, 0);
rb->lcd_update();
break;
case 8:
sys->e->_stateSlot = 0;
rb->splash(0, "Saving...");
engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave");
rb->lcd_update();
break;
case 9:
engine_deleteGameState(sys->e, 0);
exit(PLUGIN_OK);
break;
case 10:
/* saves are NOT deleted on loading */
exit(PLUGIN_OK);
break;
default:
break;
}
}
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
/* boost for game */
rb->cpu_boost(true);
#endif
sys_startAudio(sys, audio_callback, audio_param);
}
void sys_init(struct System* sys, const char* title)
{
(void) title;
#ifdef HAVE_BACKLIGHT
backlight_ignore_timeout();
#endif
rb_atexit(exit_handler);
save_sys = sys;
rb->memset(&sys->input, 0, sizeof(sys->input));
sys->mutex_bitfield = ~0;
if(!sys_load_settings(sys))
{
sys_reset_settings(sys);
}
struct viewport *vp_main = rb->lcd_set_viewport(NULL);
lcd_fb = vp_main->buffer->fb_ptr;
}
void sys_destroy(struct System* sys)
{
(void) sys;
rb->splash(HZ, "Exiting...");
}
void sys_setPalette(struct System* sys, uint8_t start, uint8_t n, const uint8_t *buf)
{
for(int i = start; i < start + n; ++i)
{
uint8_t c[3];
for (int j = 0; j < 3; j++) {
uint8_t col = buf[i * 3 + j];
c[j] = (col << 2) | (col & 3);
}
#if (LCD_DEPTH > 24)
sys->palette[i] = (fb_data) {
c[2], c[1], c[0], 255
};
#elif (LCD_DEPTH > 16) && (LCD_DEPTH <= 24)
sys->palette[i] = (fb_data) {
c[2], c[1], c[0]
};
#elif defined(HAVE_LCD_COLOR)
sys->palette[i] = FB_RGBPACK(c[0], c[1], c[2]);
#elif LCD_DEPTH > 1
sys->palette[i] = LCD_BRIGHTNESS((c[0] + c[1] + c[2]) / 3);
#endif
}
}
/****************************** THE MAIN DRAWING METHOD ********************************/
void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch)
{
static int last_timestamp = 1;
/* get the address of the temporary framebuffer that has been allocated in the audiobuf */
fb_data* framebuffer = (fb_data*) sys->e->res._memPtrStart + MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE);
buf += y * pitch + x;
/************************ BLIT THE TEMPORARY FRAMEBUFFER ***********************/
if(sys->settings.rotation_option)
{
/* clockwise */
if(sys->settings.rotation_option == 1)
{
while (h--) {
/* one byte gives two pixels */
for (int i = 0; i < w / 2; ++i) {
uint8_t pix1 = *(buf + i) >> 4;
uint8_t pix2 = *(buf + i) & 0xF;
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
framebuffer[( (h * 2) ) * 320 + i] = sys->palette[pix1];
framebuffer[( (h * 2) + 1) * 320 + i] = sys->palette[pix2];
#else
framebuffer[( (i * 2) ) * 200 + h] = sys->palette[pix1];
framebuffer[( (i * 2) + 1) * 200 + h] = sys->palette[pix2];
#endif
}
buf += pitch;
}
}
/* counterclockwise */
else
{
while (h--) {
/* one byte gives two pixels */
for (int i = 0; i < w / 2; ++i) {
uint8_t pix1 = *(buf + i) >> 4;
uint8_t pix2 = *(buf + i) & 0xF;
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
framebuffer[(200 - h * 2 ) * 320 + ( 320 - i )] = sys->palette[pix1];
framebuffer[(200 - h * 2 - 1) * 320 + ( 320 - i )] = sys->palette[pix2];
#else
framebuffer[(320 - i * 2 ) * 200 + ( 200 - h )] = sys->palette[pix1];
framebuffer[(320 - i * 2 - 1) * 200 + ( 200 - h )] = sys->palette[pix2];
#endif
}
buf += pitch;
}
}
}
/* no rotation */
else
{
int next = 0;
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
for(int x = 0; x < w / 2; ++x)
{
for(int y = 0; y < h; ++y)
{
uint8_t pix1 = buf[ y * w + x ] >> 4;
uint8_t pix2 = buf[ y * w + x ] & 0xF;
framebuffer[(x + 0)*h + y] = sys->palette[pix1];
framebuffer[(x + 1)*h + y] = sys->palette[pix2];
}
}
#else
while (h--) {
/* one byte gives two pixels */
for (int i = 0; i < w / 2; ++i) {
uint8_t pix1 = *(buf + i) >> 4;
uint8_t pix2 = *(buf + i) & 0xF;
framebuffer[next] = sys->palette[pix1];
++next;
framebuffer[next] = sys->palette[pix2];
++next;
}
buf += pitch;
}
#endif
}
/*************************** NOW SCALE IT! ***************************/
if(sys->settings.scaling_quality)
{
struct bitmap in_bmp;
if(sys->settings.rotation_option)
{
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
in_bmp.width = 320;
in_bmp.height = 200;
#else
in_bmp.width = 200;
in_bmp.height = 320;
#endif
}
else
{
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
in_bmp.width = 200;
in_bmp.height = 320;
#else
in_bmp.width = 320;
in_bmp.height = 200;
#endif
}
in_bmp.data = (unsigned char*) framebuffer;
struct bitmap out_bmp;
out_bmp.width = LCD_WIDTH;
out_bmp.height = LCD_HEIGHT;
out_bmp.data = (unsigned char*) lcd_fb;
#ifdef HAVE_LCD_COLOR
if(sys->settings.scaling_quality == 1)
#endif
simple_resize_bitmap(&in_bmp, &out_bmp);
#ifdef HAVE_LCD_COLOR
else
smooth_resize_bitmap(&in_bmp, &out_bmp);
#endif
}
else
{
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
for(int x = 0; x < 320; ++x)
{
for(int y = 0; y < 200; ++y)
{
rb->lcd_set_foreground(framebuffer[x * 200 + y]);
rb->lcd_drawpixel(x, y);
}
}
#else
if(sys->settings.zoom)
{
rb->lcd_bitmap_part(framebuffer, CODE_X, CODE_Y, STRIDE(SCREEN_MAIN, 320, 200), 0, 0, 320 - CODE_X, 200 - CODE_Y);
}
else
{
if(sys->settings.rotation_option)
rb->lcd_bitmap(framebuffer, 0, 0, 200, 320);
else
rb->lcd_bitmap(framebuffer, 0, 0, 320, 200);
}
#endif
}
/************************************* APPLY FILTERS ******************************************/
if(sys->settings.negative_enabled)
{
for(int y = 0; y < LCD_HEIGHT; ++y)
{
for(int x = 0; x < LCD_WIDTH; ++x)
{
#ifdef HAVE_LCD_COLOR
int r, g, b;
fb_data pix = lcd_fb[y * LCD_WIDTH + x];
#if (LCD_DEPTH > 24)
r = 0xff - pix.r;
g = 0xff - pix.g;
b = 0xff - pix.b;
lcd_fb[y * LCD_WIDTH + x] = (fb_data) { b, g, r, 255 };
#elif (LCD_DEPTH == 24)
r = 0xff - pix.r;
g = 0xff - pix.g;
b = 0xff - pix.b;
lcd_fb[y * LCD_WIDTH + x] = (fb_data) { b, g, r };
#else
r = RGB_UNPACK_RED (pix);
g = RGB_UNPACK_GREEN(pix);
b = RGB_UNPACK_BLUE (pix);
lcd_fb[y * LCD_WIDTH + x] = LCD_RGBPACK(0xff - r, 0xff - g, 0xff - b);
#endif
#else
lcd_fb[y * LCD_WIDTH + x] = LCD_BRIGHTNESS(0xff - lcd_fb[y * LCD_WIDTH + x]);
#endif
}
}
}
#ifdef SYS_MOTION_BLUR
if(sys->settings.blur)
{
static fb_data *prev_frames = NULL;
static fb_data *orig_fb = NULL;
static int prev_baseidx = 0; /* circular buffer */
if(!prev_frames)
{
prev_frames = sys_get_buffer(sys, sizeof(fb_data) * LCD_WIDTH * LCD_HEIGHT * BLUR_FRAMES);
orig_fb = sys_get_buffer(sys, sizeof(fb_data) * LCD_WIDTH * LCD_HEIGHT);
rb->memset(prev_frames, 0, sizeof(fb_data) * LCD_WIDTH * LCD_HEIGHT * BLUR_FRAMES);
}
if(prev_frames && orig_fb)
{
rb->memcpy(orig_fb, lcd_fb, sizeof(fb_data) * LCD_WIDTH * LCD_HEIGHT);
/* fancy useless slow motion blur */
for(int y = 0; y < LCD_HEIGHT; ++y)
{
for(int x = 0; x < LCD_WIDTH; ++x)
{
int r, g, b;
fb_data pix = lcd_fb[y * LCD_WIDTH + x];
r = RGB_UNPACK_RED (pix);
g = RGB_UNPACK_GREEN(pix);
b = RGB_UNPACK_BLUE (pix);
r *= BLUR_FRAMES + 1;
g *= BLUR_FRAMES + 1;
b *= BLUR_FRAMES + 1;
for(int i = 0; i < BLUR_FRAMES; ++i)
{
fb_data prev_pix = prev_frames[ (prev_baseidx + i * (LCD_WIDTH * LCD_HEIGHT)) % (LCD_WIDTH * LCD_HEIGHT * BLUR_FRAMES) + y * LCD_WIDTH + x];
r += (BLUR_FRAMES-i) * RGB_UNPACK_RED(prev_pix);
g += (BLUR_FRAMES-i) * RGB_UNPACK_GREEN(prev_pix);
b += (BLUR_FRAMES-i) * RGB_UNPACK_BLUE(prev_pix);
}
r /= (BLUR_FRAMES + 1) / 2 * (1 + BLUR_FRAMES + 1);
g /= (BLUR_FRAMES + 1) / 2 * (1 + BLUR_FRAMES + 1);
b /= (BLUR_FRAMES + 1) / 2 * (1 + BLUR_FRAMES + 1);
lcd_fb[y * LCD_WIDTH + x] = LCD_RGBPACK(r, g, b);
}
}
prev_baseidx -= LCD_WIDTH * LCD_HEIGHT;
if(prev_baseidx < 0)
prev_baseidx += BLUR_FRAMES * LCD_WIDTH * LCD_HEIGHT;
rb->memcpy(prev_frames + prev_baseidx, orig_fb, sizeof(fb_data) * LCD_WIDTH * LCD_HEIGHT);
}
}
#endif
/*********************** SHOW FPS *************************/
int current_time = sys_getTimeStamp(sys);
if(sys->settings.showfps)
{
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(LCD_WHITE);
/* use 1000 and not HZ here because getTimeStamp is in milliseconds */
rb->lcd_putsf(0, 0, "FPS: %d", 1000 / ((current_time - last_timestamp == 0) ? 1 : current_time - last_timestamp));
}
rb->lcd_update();
last_timestamp = sys_getTimeStamp(sys);
}
static void do_pause_menu(struct System* sys)
{
sys_stopAudio(sys);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
int sel = 0;
MENUITEM_STRINGLIST(menu, "XWorld Menu", NULL,
"Resume Game", /* 0 */
"Start New Game", /* 1 */
"Video Settings", /* 2 */
"Sound Settings", /* 3 */
"Fast Mode", /* 4 */
"Enter Code", /* 5 */
"Help", /* 6 */
"Reset Settings", /* 7 */
"Load", /* 8 */
"Save", /* 9 */
"Quit"); /* 10 */
bool quit = false;
while(!quit)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
quit = true;
break;
case 1:
vm_initForPart(&sys->e->vm, GAME_PART_FIRST);
quit = true;
break;
case 2:
do_video_settings(sys);
break;
case 3:
do_sound_settings(sys);
break;
case 4:
rb->set_bool("Fast Mode", &sys->e->vm._fastMode);
sys_save_settings(sys);
break;
case 5:
sys->input.code = true;
quit = true;
break;
case 6:
sys_do_help();
break;
case 7:
sys_reset_settings(sys);
sys_save_settings(sys);
break;
case 8:
rb->splash(0, "Loading...");
sys->loaded = engine_loadGameState(sys->e, 0);
rb->lcd_update();
quit = true;
break;
case 9:
sys->e->_stateSlot = 0;
rb->splash(0, "Saving...");
engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave");
rb->lcd_update();
break;
case 10:
exit(PLUGIN_OK);
break;
}
}
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
sys_startAudio(sys, audio_callback, audio_param);
}
void sys_processEvents(struct System* sys)
{
int btn = rb->button_status();
rb->button_clear_queue();
static int oldbuttonstate = 0;
debug(DBG_SYS, "button is 0x%08x", btn);
/* exit early if we can */
if(btn == oldbuttonstate)
{
return;
}
/* handle special keys first */
switch(btn)
{
case BTN_PAUSE:
do_pause_menu(sys);
sys->input.dirMask = 0;
sys->input.button = false;
/* exit early to avoid unwanted button presses being detected */
return;
default:
exit_on_usb(btn);
break;
}
/* Ignore some buttons that cause errant input */
btn &= ~BUTTON_REDRAW;
#if (CONFIG_KEYPAD == IPOD_4G_PAD) || \
(CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
if(btn & 0x80000000)
return;
#endif
#if (CONFIG_KEYPAD == SANSA_E200_PAD)
if(btn == (BUTTON_SCROLL_FWD || BUTTON_SCROLL_BACK))
return;
#endif
#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
if(btn == (BUTTON_SELECT))
return;
#endif
/* copied from doom which was copied from rockboy... */
unsigned released = ~btn & oldbuttonstate;
unsigned pressed = btn & ~oldbuttonstate;
oldbuttonstate = btn;
if(released)
{
if(released & BTN_FIRE)
sys->input.button = false;
if(released & sys->keymap.up)
sys->input.dirMask &= ~DIR_UP;
if(released & sys->keymap.down)
sys->input.dirMask &= ~DIR_DOWN;
if(released & sys->keymap.left)
sys->input.dirMask &= ~DIR_LEFT;
if(released & sys->keymap.right)
sys->input.dirMask &= ~DIR_RIGHT;
#ifdef BTN_DOWN_LEFT
if(released & sys->keymap.downleft)
sys->input.dirMask &= ~(DIR_DOWN | DIR_LEFT);
#endif
#ifdef BTN_DOWN_RIGHT
if(released & sys->keymap.downright)
sys->input.dirMask &= ~(DIR_DOWN | DIR_RIGHT);
#endif
#ifdef BTN_UP_LEFT
if(released & sys->keymap.upleft)
sys->input.dirMask &= ~(DIR_UP | DIR_LEFT);
#endif
#ifdef BTN_UP_RIGHT
if(released & sys->keymap.upright)
sys->input.dirMask &= ~(DIR_UP | DIR_RIGHT);
#endif
}
if(pressed)
{
if(pressed & BTN_FIRE)
sys->input.button = true;
if(pressed & sys->keymap.up)
sys->input.dirMask |= DIR_UP;
if(pressed & sys->keymap.down)
sys->input.dirMask |= DIR_DOWN;
if(pressed & sys->keymap.left)
sys->input.dirMask |= DIR_LEFT;
if(pressed & sys->keymap.right)
sys->input.dirMask |= DIR_RIGHT;
#ifdef BTN_DOWN_LEFT
if(pressed & sys->keymap.downleft)
sys->input.dirMask |= (DIR_DOWN | DIR_LEFT);
#endif
#ifdef BTN_DOWN_RIGHT
if(pressed & sys->keymap.downright)
sys->input.dirMask |= (DIR_DOWN | DIR_RIGHT);
#endif
#ifdef BTN_UP_LEFT
if(pressed & sys->keymap.upleft)
sys->input.dirMask |= (DIR_UP | DIR_LEFT);
#endif
#ifdef BTN_UP_RIGHT
if(pressed & sys->keymap.upright)
sys->input.dirMask |= (DIR_UP | DIR_RIGHT);
#endif
}
#if 0
/* handle releases */
if(btn & BUTTON_REL)
{
debug(DBG_SYS, "button_rel");
btn &= ~BUTTON_REL;
if(btn & BTN_FIRE)
sys->input.button = false;
if(btn & sys->keymap.up)
sys->input.dirMask &= ~DIR_UP;
if(btn & sys->keymap.down)
sys->input.dirMask &= ~DIR_DOWN;
if(btn & sys->keymap.left)
sys->input.dirMask &= ~DIR_LEFT;
if(btn & sys->keymap.right)
sys->input.dirMask &= ~DIR_RIGHT;
#ifdef BTN_DOWN_LEFT
if(btn & sys->keymap.downleft)
sys->input.dirMask &= ~(DIR_DOWN | DIR_LEFT);
#endif
#ifdef BTN_DOWN_RIGHT
if(btn & sys->keymap.downright)
sys->input.dirMask &= ~(DIR_DOWN | DIR_RIGHT);
#endif
#ifdef BTN_UP_LEFT
if(btn & sys->keymap.upleft)
sys->input.dirMask &= ~(DIR_UP | DIR_LEFT);
#endif
#ifdef BTN_UP_RIGHT
if(btn & sys->keymap.upright)
sys->input.dirMask &= ~(DIR_UP | DIR_RIGHT);
#endif
}
/* then handle presses */
else
{
if(btn & BTN_FIRE)
sys->input.button = true;
if(btn & sys->keymap.up)
sys->input.dirMask |= DIR_UP;
if(btn & sys->keymap.down)
sys->input.dirMask |= DIR_DOWN;
if(btn & sys->keymap.left)
sys->input.dirMask |= DIR_LEFT;
if(btn & sys->keymap.right)
sys->input.dirMask |= DIR_RIGHT;
#ifdef BTN_DOWN_LEFT
if(btn & sys->keymap.downleft)
sys->input.dirMask |= (DIR_DOWN | DIR_LEFT);
#endif
#ifdef BTN_DOWN_RIGHT
if(btn & sys->keymap.downright)
sys->input.dirMask |= (DIR_DOWN | DIR_RIGHT);
#endif
#ifdef BTN_UP_LEFT
if(btn & sys->keymap.upleft)
sys->input.dirMask |= (DIR_UP | DIR_LEFT);
#endif
#ifdef BTN_UP_RIGHT
if(btn & sys->keymap.upright)
sys->input.dirMask |= (DIR_UP | DIR_RIGHT);
#endif
}
debug(DBG_SYS, "dirMask is 0x%02x", sys->input.dirMask);
debug(DBG_SYS, "button is %s", sys->input.button == true ? "true" : "false");
#endif
}
void sys_sleep(struct System* sys, uint32_t duration)
{
(void) sys;
/* duration is in ms */
rb->sleep(duration / (1000/HZ));
}
uint32_t sys_getTimeStamp(struct System* sys)
{
(void) sys;
return (uint32_t) (*rb->current_tick * (1000/HZ));
}
/* game provides us mono samples, we need stereo */
static int16_t rb_soundbuf[MAX_SOUNDBUF_SIZE * 2];
static int8_t temp_soundbuf[MAX_SOUNDBUF_SIZE];
static void get_more(const void** start, size_t* size)
{
if(audio_sys->settings.sound_enabled && audio_callback)
{
audio_callback(audio_param, temp_soundbuf, audio_sys->settings.sound_bufsize);
/* convert xworld format (signed 8-bit) to rockbox format (stereo signed 16-bit) */
for(int i = 0; i < audio_sys->settings.sound_bufsize; ++i)
{
rb_soundbuf[2*i] = rb_soundbuf[2*i+1] = temp_soundbuf[i] * 256;
}
}
else
{
rb->memset(rb_soundbuf, 0, audio_sys->settings.sound_bufsize * 2 * sizeof(int16_t));
}
*start = rb_soundbuf;
*size = audio_sys->settings.sound_bufsize * 2 * sizeof(int16_t);
}
void sys_startAudio(struct System* sys, AudioCallback callback, void *param)
{
(void) sys;
audio_callback = callback;
audio_param = param;
audio_sys = sys;
rb->pcm_play_data(get_more, NULL, NULL, 0);
}
void sys_stopAudio(struct System* sys)
{
(void) sys;
rb->pcm_play_stop();
}
uint32_t sys_getOutputSampleRate(struct System* sys)
{
(void) sys;
return rb->mixer_get_frequency();
}
/* pretty non-reentrant here, but who cares? it's not like someone
would want to run two instances of the game on Rockbox! :D */
static uint32_t cur_delay;
static void* cur_param;
static TimerCallback user_callback;
static void timer_callback(void)
{
debug(DBG_SYS, "timer callback with delay of %d ms callback 0x%08x param 0x%08x", cur_delay, timer_callback, cur_param);
uint32_t ret = user_callback(cur_delay, cur_param);
if(ret != cur_delay)
{
cur_delay = ret;
rb->timer_register(1, NULL, TIMER_FREQ / 1000 * ret, timer_callback IF_COP(, CPU));
}
}
void *sys_addTimer(struct System* sys, uint32_t delay, TimerCallback callback, void *param)
{
(void) sys;
debug(DBG_SYS, "registering timer with delay of %d ms callback 0x%08x param 0x%08x", delay, callback, param);
user_callback = callback;
cur_delay = delay;
cur_param = param;
rb->timer_register(1, NULL, TIMER_FREQ / 1000 * delay, timer_callback IF_COP(, CPU));
return NULL;
}
void sys_removeTimer(struct System* sys, void *timerId)
{
(void) sys;
(void) timerId;
/* there's only one timer per game instance, so ignore both parameters */
rb->timer_unregister();
}
void *sys_createMutex(struct System* sys)
{
if(!sys)
error("sys is NULL!");
debug(DBG_SYS, "allocating mutex");
/* this bitfield works as follows: bit set = free, unset = in use */
for(int i = 0; i < MAX_MUTEXES; ++i)
{
/* check that the corresponding bit is 1 (free) */
if(sys->mutex_bitfield & (1 << i))
{
rb->mutex_init(sys->mutex_memory + i);
sys->mutex_bitfield &= ~(1 << i);
return sys->mutex_memory + i;
}
}
warning("Out of mutexes!");
return NULL;
}
void sys_destroyMutex(struct System* sys, void *mutex)
{
int mutex_number = ((char*)mutex - (char*)sys->mutex_memory) / sizeof(struct mutex); /* pointer arithmetic! check for bugs! */
sys->mutex_bitfield |= 1 << mutex_number;
}
void sys_lockMutex(struct System* sys, void *mutex)
{
(void) sys;
rb->mutex_lock((struct mutex*) mutex);
}
void sys_unlockMutex(struct System* sys, void *mutex)
{
(void) sys;
rb->mutex_unlock((struct mutex*) mutex);
}
uint8_t* getOffScreenFramebuffer(struct System* sys)
{
(void) sys;
return NULL;
}
void *sys_get_buffer(struct System* sys, size_t sz)
{
if((signed int)sys->bytes_left - (signed int)sz >= 0)
{
void* ret = sys->membuf;
rb->memset(ret, 0, sz);
sys->membuf = (char*)(sys->membuf) + sz;
sys->bytes_left -= sz;
return ret;
}
else
{
error("sys_get_buffer: out of memory!");
return NULL;
}
}
void MutexStack(struct MutexStack_t* s, struct System *stub, void *mutex)
{
s->sys = stub;
s->_mutex = mutex;
/* FW 2017-2-12: disabled; no blocking ops in IRQ context! */
/*sys_lockMutex(s->sys, s->_mutex);*/
}
void MutexStack_destroy(struct MutexStack_t* s)
{
(void) s;
/*sys_unlockMutex(s->sys, s->_mutex);*/
}