From 82699f3d07cbda15a79c1763f3ff1365795bc2d5 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Wed, 1 Dec 2021 15:02:45 +0100 Subject: [PATCH] Implement an SDL main loop inside the Main Thread --- meson.build | 2 + openrtx/include/chan.h | 50 +++ openrtx/src/chan.c | 140 ++++++++ openrtx/src/main.c | 13 + openrtx/src/threads.c | 8 - platform/drivers/display/display_libSDL.c | 240 +++---------- platform/targets/linux/emulator/emulator.c | 123 +------ platform/targets/linux/emulator/sdl_engine.c | 335 +++++++++++++++++++ platform/targets/linux/emulator/sdl_engine.h | 51 +++ 9 files changed, 654 insertions(+), 308 deletions(-) create mode 100644 openrtx/include/chan.h create mode 100644 openrtx/src/chan.c create mode 100644 platform/targets/linux/emulator/sdl_engine.c create mode 100644 platform/targets/linux/emulator/sdl_engine.h diff --git a/meson.build b/meson.build index 876eca31..2972adc0 100644 --- a/meson.build +++ b/meson.build @@ -30,6 +30,7 @@ openrtx_src = ['openrtx/src/state.c', 'openrtx/src/input.c', 'openrtx/src/calibUtils.c', 'openrtx/src/queue.c', + 'openrtx/src/chan.c', 'openrtx/src/rtx/rtx.cpp', 'openrtx/src/rtx/OpMode_FM.cpp', 'openrtx/src/rtx/OpMode_M17.cpp', @@ -214,6 +215,7 @@ mk22fn512_def = {'_POSIX_PRIORITY_SCHEDULING':''} ## Linux ## linux_platform_src = ['platform/targets/linux/emulator/emulator.c', + 'platform/targets/linux/emulator/sdl_engine.c', 'platform/drivers/display/display_libSDL.c', 'platform/drivers/keyboard/keyboard_linux.c', 'platform/drivers/NVM/nvmem_linux.c', diff --git a/openrtx/include/chan.h b/openrtx/include/chan.h new file mode 100644 index 00000000..54d78968 --- /dev/null +++ b/openrtx/include/chan.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2021 by Alessio Caiazza IU5BON * + * * + * 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 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +#ifndef CHAN_H +#define CHAN_H + +#include +#include + +/* + * chan_t is an unbuffered synchronization channel. + * Both reader and writer are blocked untill the data is exchanged. + */ +typedef struct chan_t +{ + pthread_mutex_t m_meta; + pthread_mutex_t m_read; + pthread_mutex_t m_write; + pthread_cond_t c_reader; + pthread_cond_t c_writer; + + void *data; + bool closed; + bool reader; + bool writer; +} +chan_t; + +void chan_init(chan_t *c); +void chan_send(chan_t *c, void *data); +void chan_recv(chan_t *c, void **data); +bool chan_can_recv(chan_t *c); +bool chan_can_send(chan_t *c); +void chan_close(chan_t *c); +void chan_terminate(chan_t *c); + +#endif diff --git a/openrtx/src/chan.c b/openrtx/src/chan.c new file mode 100644 index 00000000..0c3e21c3 --- /dev/null +++ b/openrtx/src/chan.c @@ -0,0 +1,140 @@ +/*************************************************************************** + * Copyright (C) 2021 by Alessio Caiazza IU5BON * + * * + * 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 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +#include "chan.h" +#include + +void chan_init(chan_t *c) +{ + if(c == NULL) return; + + pthread_mutex_init(&c->m_meta, NULL); + pthread_mutex_init(&c->m_read, NULL); + pthread_mutex_init(&c->m_write, NULL); + + pthread_cond_init(&c->c_reader, NULL); + pthread_cond_init(&c->c_writer, NULL); + + c->reader = false; + c->writer = false; + c->closed = false; +} + +void chan_send(chan_t *c, void *data) +{ + pthread_mutex_lock(&c->m_write); + pthread_mutex_lock(&c->m_meta); + + if(c->closed) + { + pthread_mutex_unlock(&c->m_meta); + pthread_mutex_unlock(&c->m_write); + + return; + } + + c->data = data; + c->writer = true; + + // notify the waiting reader that data is ready + if (c->reader) + { + pthread_cond_signal(&c->c_writer); + } + + // wait until data is consumed + pthread_cond_wait(&c->c_reader, &c->m_meta); + c->writer = false; + + pthread_mutex_unlock(&c->m_meta); + pthread_mutex_unlock(&c->m_write); +} + +void chan_recv(chan_t *c, void **data) +{ + pthread_mutex_lock(&c->m_read); + pthread_mutex_lock(&c->m_meta); + + // wait for a writer + while(!c->closed && !c->writer) + { + c->reader = true; + pthread_cond_wait(&c->c_writer, &c->m_meta); + c->reader = false; + } + + if(c->closed) + { + pthread_mutex_unlock(&c->m_meta); + pthread_mutex_unlock(&c->m_read); + + return; + } + + if (data != NULL) + { + *data = c->data; + } + + // notify the waiting writer that the reader consumed the data + pthread_cond_signal(&c->c_reader); + + pthread_mutex_unlock(&c->m_meta); + pthread_mutex_unlock(&c->m_read); +} + + +bool chan_can_recv(chan_t *c) +{ + pthread_mutex_lock(&c->m_meta); + bool can_receive = c->writer; + pthread_mutex_unlock(&c->m_meta); + + return can_receive; +} + +bool chan_can_send(chan_t *c) +{ + pthread_mutex_lock(&c->m_meta); + bool can_send = c->reader; + pthread_mutex_unlock(&c->m_meta); + + return can_send; +} + +void chan_close(chan_t *c) +{ + pthread_mutex_lock(&c->m_meta); + if (!c->closed) + { + c->closed = true; + pthread_cond_broadcast(&c->c_reader); + pthread_cond_broadcast(&c->c_writer); + } + pthread_mutex_unlock(&c->m_meta); +} + +void chan_terminate(chan_t *c) +{ + chan_close(c); + + pthread_mutex_destroy(&c->m_write); + pthread_mutex_destroy(&c->m_read); + pthread_mutex_destroy(&c->m_meta); + + pthread_cond_destroy(&c->c_writer); + pthread_cond_destroy(&c->c_reader); +} diff --git a/openrtx/src/main.c b/openrtx/src/main.c index 470026de..aff3ca73 100644 --- a/openrtx/src/main.c +++ b/openrtx/src/main.c @@ -27,6 +27,9 @@ #include #include #include +#ifdef PLATFORM_LINUX +#include +#endif extern void *ui_task(void *arg); @@ -74,6 +77,16 @@ int main(void) // Create OpenRTX threads create_threads(); +#ifndef PLATFORM_LINUX // Jump to the UI task ui_task(NULL); +#else + // macOS requires SDL main loop to run on the main thread. + // Here we create a new thread for ui_task and utilize the masin thread for + // the SDL main loop. + pthread_t ui_thread; + pthread_create(&ui_thread, NULL, ui_task, NULL); + + sdl_task(); +#endif } diff --git a/openrtx/src/threads.c b/openrtx/src/threads.c index 0eeaa9ba..8e9a27a3 100644 --- a/openrtx/src/threads.c +++ b/openrtx/src/threads.c @@ -38,10 +38,6 @@ #include #include #endif -#ifdef PLATFORM_LINUX -#include -#endif - /* Mutex for concurrent access to state variable */ pthread_mutex_t state_mutex; @@ -77,10 +73,6 @@ void *ui_task(void *arg) while(1) { - #ifdef PLATFORM_LINUX - emulator_process_sdl_events(); - #endif - // Read from the keyboard queue (returns 0 if no message is present) // Copy keyboard_t keys from received void * pointer msg event_t event; diff --git a/platform/drivers/display/display_libSDL.c b/platform/drivers/display/display_libSDL.c index 95837ce2..4c0653e3 100644 --- a/platform/drivers/display/display_libSDL.c +++ b/platform/drivers/display/display_libSDL.c @@ -25,139 +25,20 @@ */ #include +#include +#include #include #include #include -#undef main /* necessary to avoid conflicts with SDL_main */ - -/* - * Screen dimensions, adjust basing on the size of the screen you need to - * emulate - */ -#ifndef SCREEN_WIDTH -#define SCREEN_WIDTH 160 -#endif - -#ifndef SCREEN_HEIGHT -#define SCREEN_HEIGHT 128 -#endif - -#ifdef PIX_FMT_RGB565 -#define PIXEL_FORMAT SDL_PIXELFORMAT_RGB565 -#define PIXEL_SIZE uint16_t -#else -#define PIXEL_FORMAT SDL_PIXELFORMAT_ARGB8888 -#define PIXEL_SIZE uint32_t -#endif - -SDL_Renderer *renderer; /* SDL renderer */ -SDL_Window *window; /* SDL window */ -SDL_Texture *displayTexture; /* SDL rendering surface */ void *frameBuffer; /* Pointer to framebuffer */ bool inProgress; /* Flag to signal when rendering is in progress */ - -int screenshot_display(const char *filename) -{ - //https://stackoverflow.com/a/48176678 - //user1902824 - //modified to keep renderer and display texture references in the body rather than as a parameter - SDL_Renderer * ren = renderer; - SDL_Texture * tex = displayTexture; - int err = 0; - - - SDL_Texture *ren_tex; - SDL_Surface *surf; - int st; - int w; - int h; - int format; - void *pixels; - - pixels = NULL; - surf = NULL; - ren_tex = NULL; - format = SDL_PIXELFORMAT_RGBA32; - - /* Get information about texture we want to save */ - st = SDL_QueryTexture(tex, NULL, NULL, &w, &h); - if (st != 0) - { - SDL_Log("Failed querying texture: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - - ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h); - if (!ren_tex) - { - SDL_Log("Failed creating render texture: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - - /* - * Initialize our canvas, then copy texture to a target whose pixel data we - * can access - */ - st = SDL_SetRenderTarget(ren, ren_tex); - if (st != 0) - { - SDL_Log("Failed setting render target: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00); - SDL_RenderClear(ren); - st = SDL_RenderCopy(ren, tex, NULL, NULL); - if (st != 0) - { - SDL_Log("Failed copying texture data: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - /* Create buffer to hold texture data and load it */ - pixels = malloc(w * h * SDL_BYTESPERPIXEL(format)); - if (!pixels) - { - SDL_Log("Failed allocating memory\n"); - err++; - goto cleanup; - } - st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format)); - if (st != 0) - { - SDL_Log("Failed reading pixel data: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - /* Copy pixel data over to surface */ - surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format); - if (!surf) - { - SDL_Log("Failed creating new surface: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - /* Save result to an image */ - st = SDL_SaveBMP(surf, filename); - if (st != 0) - { - SDL_Log("Failed saving image: %s\n", SDL_GetError()); - err++; - goto cleanup; - } - SDL_Log("Saved texture as BMP to \"%s\"\n", filename); - -cleanup: - SDL_FreeSurface(surf); - free(pixels); - SDL_DestroyTexture(ren_tex); - return err; -} - +/* + * SDL main loop syncronization + */ +bool sdl_ready = false; /* Flag to signal the sdl main loop is running */ +extern chan_t fb_sync; /* Shared channel to send a frame buffer update */ /** * @internal @@ -197,60 +78,36 @@ uint32_t fetchPixelFromFb(unsigned int x, unsigned int y) void display_init() { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) - { - printf("SDL video init error!!\n"); - - } - else - { - - window = SDL_CreateWindow("OpenRTX", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3, - SDL_WINDOW_SHOWN ); - //removed RESIZABLE flag so automatic screen recording is a little easier - - renderer = SDL_CreateRenderer(window, -1, 0); - SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); - displayTexture = SDL_CreateTexture(renderer, PIXEL_FORMAT, SDL_TEXTUREACCESS_STREAMING, - SCREEN_WIDTH, SCREEN_HEIGHT); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, displayTexture, NULL, NULL); - SDL_RenderPresent(renderer); - - /* - * Black and white pixel format: framebuffer type is uint8_t where each - * bit represents a pixel. We have to allocate - * (SCREEN_HEIGHT * SCREEN_WIDTH)/8 elements - */ + /* + * Black and white pixel format: framebuffer type is uint8_t where each + * bit represents a pixel. We have to allocate + * (SCREEN_HEIGHT * SCREEN_WIDTH)/8 elements + */ #ifdef PIX_FMT_BW - unsigned int fbSize = (SCREEN_HEIGHT * SCREEN_WIDTH)/8; - if((fbSize * 8) < (SCREEN_HEIGHT * SCREEN_WIDTH)) fbSize += 1; /* Compensate for eventual truncation error in division */ - fbSize *= sizeof(uint8_t); + unsigned int fbSize = (SCREEN_HEIGHT * SCREEN_WIDTH)/8; + if((fbSize * 8) < (SCREEN_HEIGHT * SCREEN_WIDTH)) fbSize += 1; /* Compensate for eventual truncation error in division */ + fbSize *= sizeof(uint8_t); #endif - /* - * Grayscale pixel format: framebuffer type is uint8_t where each element - * controls one pixel - */ + /* + * Grayscale pixel format: framebuffer type is uint8_t where each element + * controls one pixel + */ #ifdef PIX_FMT_GRAYSC - unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint8_t); + unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint8_t); #endif - /* - * RGB565 pixel format: framebuffer type is uint16_t where each element - * controls one pixel - */ + /* + * RGB565 pixel format: framebuffer type is uint16_t where each element + * controls one pixel + */ #ifdef PIX_FMT_RGB565 - unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint16_t); + unsigned int fbSize = SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint16_t); #endif - frameBuffer = malloc(fbSize); - memset(frameBuffer, 0xFFFF, fbSize); - inProgress = false; - } + frameBuffer = malloc(fbSize); + memset(frameBuffer, 0xFFFF, fbSize); + inProgress = false; } void display_terminate() @@ -259,36 +116,36 @@ void display_terminate() {} /* Wait until current render finishes */ printf("Terminating SDL display emulator, goodbye!\n"); free(frameBuffer); - SDL_DestroyWindow(window); - SDL_Quit(); } void display_renderRows(uint8_t startRow, uint8_t endRow) { (void) startRow; (void) endRow; - PIXEL_SIZE *pixels; - int pitch = 0; - if (SDL_LockTexture(displayTexture, NULL, (void **) &pixels, &pitch) < 0) - { - printf("SDL_lock failed: %s\n", SDL_GetError()); - } inProgress = true; -#ifdef PIX_FMT_RGB565 - uint16_t *fb = (uint16_t *) (frameBuffer); - memcpy(pixels, fb, sizeof(uint16_t) * SCREEN_HEIGHT * SCREEN_WIDTH); -#else - for (unsigned int x = 0; x < SCREEN_WIDTH; x++) + if(!sdl_ready) { - for (unsigned int y = startRow; y < endRow; y++) - { - pixels[x + y * SCREEN_WIDTH] = fetchPixelFromFb(x, y); - } + sdl_ready = sdl_main_loop_ready(); } + + if(sdl_ready) + { +#ifdef PIX_FMT_RGB565 + chan_send(&fb_sync, frameBuffer); +#else + //TODO free + PIXEL_SIZE *pixels = malloc(sizeof(uint16_t) * SCREEN_HEIGHT * SCREEN_WIDTH); + for (unsigned int x = 0; x < SCREEN_WIDTH; x++) + { + for (unsigned int y = startRow; y < endRow; y++) + { + pixels[x + y * SCREEN_WIDTH] = fetchPixelFromFb(x, y); + } + } + chan_send(&fb_sync, (void *)pixels); #endif - SDL_UnlockTexture(displayTexture); - SDL_RenderCopy(renderer, displayTexture, NULL, NULL); - SDL_RenderPresent(renderer); + } + inProgress = false; } @@ -311,4 +168,3 @@ void display_setContrast(uint8_t contrast) { printf("Setting display contrast to %d\n", contrast); } - diff --git a/platform/targets/linux/emulator/emulator.c b/platform/targets/linux/emulator/emulator.c index 4d5f64d3..55a76f14 100644 --- a/platform/targets/linux/emulator/emulator.c +++ b/platform/targets/linux/emulator/emulator.c @@ -20,22 +20,24 @@ #include "emulator.h" +#include "sdl_engine.h" #include #include #include #include +#include #include #include #include #include +/* Custom SDL Event to request a screenshot */ +extern Uint32 SDL_Screenshot_Event; radio_state Radio_State = {12, 8.2f, 3, 4, 1, false, false}; -extern int screenshot_display(const char *filename); - typedef int (*_climenu_fn)(void *self, int argc, char **argv); typedef struct @@ -250,8 +252,14 @@ int screenshot(void *_self, int _argc, char **_argv) { filename = _argv[0]; } - return screenshot_display(filename) == 0 ? SH_CONTINUE : SH_ERR; - //screenshot_display returns 0 if ok, which is same as SH_CONTINUE + + SDL_Event e; + SDL_zero(e); + e.type = SDL_Screenshot_Event; + e.user.data1 = malloc(sizeof(filename)); + strcpy(e.user.data1, filename); + + return SDL_PushEvent(&e) == 1 ? SH_CONTINUE : SH_ERR; } /* int record_start(void * _self, int _argc, char ** _argv ){ @@ -528,116 +536,15 @@ void *startCLIMenu() Radio_State.PowerOff = true; } - void emulator_start() { + init_sdl(); + pthread_t cli_thread; int err = pthread_create(&cli_thread, NULL, startCLIMenu, NULL); if(err) { - printf("An error occurred starting the emulator thread: %d\n", err); - } -} - -keyboard_t sdl_keys; -keyboard_t sdl_getKeys() { return sdl_keys; } - -bool sdk_key_code_to_key(SDL_KeyCode sym, keyboard_t *key) -{ - switch (sym) { - case SDLK_0: - *key = KEY_0; - return true; - case SDLK_1: - *key = KEY_1; - return true; - case SDLK_2: - *key = KEY_2; - return true; - case SDLK_3: - *key = KEY_3; - return true; - case SDLK_4: - *key = KEY_4; - return true; - case SDLK_5: - *key = KEY_5; - return true; - case SDLK_6: - *key = KEY_6; - return true; - case SDLK_7: - *key = KEY_7; - return true; - case SDLK_8: - *key = KEY_8; - return true; - case SDLK_9: - *key = KEY_9; - return true; - case SDLK_ASTERISK: - *key = KEY_STAR; - return true; - case SDLK_ESCAPE: - *key = KEY_ESC; - return true; - case SDLK_LEFT: - *key = KEY_LEFT; - return true; - case SDLK_RIGHT: - *key = KEY_RIGHT; - return true; - case SDLK_RETURN: - *key = KEY_ENTER; - return true; - case SDLK_HASH: - *key = KEY_HASH; - return true; - case SDLK_n: - *key = KEY_F1; - return true; - case SDLK_m: - *key = KEY_MONI; - return true; - case SDLK_PAGEUP: - *key = KNOB_LEFT; - return true; - case SDLK_PAGEDOWN: - *key = KNOB_RIGHT; - return true; - case SDLK_UP: - *key = KEY_UP; - return true; - case SDLK_DOWN: - *key = KEY_DOWN; - return true; - default: - return false; - } -} - -void emulator_process_sdl_events() -{ - Uint32 now = SDL_GetTicks(); - - SDL_Event ev = { 0 }; - keyboard_t key = 0; - - SDL_PollEvent( &ev); - - switch (ev.type) { - case SDL_QUIT: - Radio_State.PowerOff = true; - break; - case SDL_KEYDOWN: - if (sdk_key_code_to_key(ev.key.keysym.sym, &key)) - sdl_keys |= key; - break; - case SDL_KEYUP: - if (sdk_key_code_to_key(ev.key.keysym.sym, &key)) - sdl_keys ^= key; - - break; + printf("An error occurred starting the emulator CLI thread: %d\n", err); } } diff --git a/platform/targets/linux/emulator/sdl_engine.c b/platform/targets/linux/emulator/sdl_engine.c new file mode 100644 index 00000000..26d20efd --- /dev/null +++ b/platform/targets/linux/emulator/sdl_engine.c @@ -0,0 +1,335 @@ +/*************************************************************************** + * Copyright (C) 2021 by Alessio Caiazza IU5BON * + * * + * 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 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +#include "sdl_engine.h" +#include "emulator.h" + +#include +#include +#include +#include + +int screenshot_display(const char *filename); + +/* Shared channel to receive frame buffer updates */ +chan_t fb_sync; + +SDL_Window *window; +SDL_Renderer *renderer; +SDL_Texture *displayTexture; + +/* Custom SDL Event to request a screenshot */ +Uint32 SDL_Screenshot_Event; + +/* + * Mutex protected variables + */ +pthread_mutex_t mu; +bool ready = false; /* Signal if the main loop is ready */ +keyboard_t sdl_keys; /* Store the keyboard status */ + +bool sdk_key_code_to_key(SDL_KeyCode sym, keyboard_t *key) +{ + switch (sym) { + case SDLK_0: + *key = KEY_0; + return true; + case SDLK_1: + *key = KEY_1; + return true; + case SDLK_2: + *key = KEY_2; + return true; + case SDLK_3: + *key = KEY_3; + return true; + case SDLK_4: + *key = KEY_4; + return true; + case SDLK_5: + *key = KEY_5; + return true; + case SDLK_6: + *key = KEY_6; + return true; + case SDLK_7: + *key = KEY_7; + return true; + case SDLK_8: + *key = KEY_8; + return true; + case SDLK_9: + *key = KEY_9; + return true; + case SDLK_ASTERISK: + *key = KEY_STAR; + return true; + case SDLK_ESCAPE: + *key = KEY_ESC; + return true; + case SDLK_LEFT: + *key = KEY_LEFT; + return true; + case SDLK_RIGHT: + *key = KEY_RIGHT; + return true; + case SDLK_RETURN: + *key = KEY_ENTER; + return true; + case SDLK_HASH: + *key = KEY_HASH; + return true; + case SDLK_n: + *key = KEY_F1; + return true; + case SDLK_m: + *key = KEY_MONI; + return true; + case SDLK_PAGEUP: + *key = KNOB_LEFT; + return true; + case SDLK_PAGEDOWN: + *key = KNOB_RIGHT; + return true; + case SDLK_UP: + *key = KEY_UP; + return true; + case SDLK_DOWN: + *key = KEY_DOWN; + return true; + default: + return false; + } +} + +int screenshot_display(const char *filename) +{ + //https://stackoverflow.com/a/48176678 + //user1902824 + //modified to keep renderer and display texture references in the body rather than as a parameter + SDL_Renderer * ren = renderer; + SDL_Texture * tex = displayTexture; + int err = 0; + + + SDL_Texture *ren_tex; + SDL_Surface *surf; + int st; + int w; + int h; + int format; + void *pixels; + + pixels = NULL; + surf = NULL; + ren_tex = NULL; + format = SDL_PIXELFORMAT_RGBA32; + + /* Get information about texture we want to save */ + st = SDL_QueryTexture(tex, NULL, NULL, &w, &h); + if (st != 0) + { + SDL_Log("Failed querying texture: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + + ren_tex = SDL_CreateTexture(ren, format, SDL_TEXTUREACCESS_TARGET, w, h); + if (!ren_tex) + { + SDL_Log("Failed creating render texture: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + + /* + * Initialize our canvas, then copy texture to a target whose pixel data we + * can access + */ + st = SDL_SetRenderTarget(ren, ren_tex); + if (st != 0) + { + SDL_Log("Failed setting render target: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0x00); + SDL_RenderClear(ren); + st = SDL_RenderCopy(ren, tex, NULL, NULL); + if (st != 0) + { + SDL_Log("Failed copying texture data: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + /* Create buffer to hold texture data and load it */ + pixels = malloc(w * h * SDL_BYTESPERPIXEL(format)); + if (!pixels) + { + SDL_Log("Failed allocating memory\n"); + err++; + goto cleanup; + } + st = SDL_RenderReadPixels(ren, NULL, format, pixels, w * SDL_BYTESPERPIXEL(format)); + if (st != 0) + { + SDL_Log("Failed reading pixel data: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + /* Copy pixel data over to surface */ + surf = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(format), w * SDL_BYTESPERPIXEL(format), format); + if (!surf) + { + SDL_Log("Failed creating new surface: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + /* Save result to an image */ + st = SDL_SaveBMP(surf, filename); + if (st != 0) + { + SDL_Log("Failed saving image: %s\n", SDL_GetError()); + err++; + goto cleanup; + } + SDL_Log("Saved texture as BMP to \"%s\"\n", filename); + +cleanup: + SDL_FreeSurface(surf); + free(pixels); + SDL_DestroyTexture(ren_tex); + return err; +} + +bool sdl_main_loop_ready() +{ + pthread_mutex_lock(&mu); + bool is_ready = ready; + pthread_mutex_unlock(&mu); + + return is_ready; +} + +keyboard_t sdl_getKeys() +{ + pthread_mutex_lock(&mu); + keyboard_t keys = sdl_keys; + pthread_mutex_unlock(&mu); + + return keys; +} + +/* + * SDL main loop. Due to macOS restrictions, this must run on the Main Thread. + */ +void sdl_task() +{ + pthread_mutex_lock(&mu); + ready = true; + pthread_mutex_unlock(&mu); + + SDL_Event ev = { 0 }; + while(!Radio_State.PowerOff) + { + keyboard_t key = 0; + if(SDL_PollEvent(&ev) == 1) + { + switch (ev.type) + { + case SDL_QUIT: + Radio_State.PowerOff = true; + break; + case SDL_KEYDOWN: + if (sdk_key_code_to_key(ev.key.keysym.sym, &key)) + { + pthread_mutex_lock(&mu); + sdl_keys |= key; + pthread_mutex_unlock(&mu); + } + break; + case SDL_KEYUP: + if (sdk_key_code_to_key(ev.key.keysym.sym, &key)) + { + pthread_mutex_lock(&mu); + sdl_keys ^= key; + pthread_mutex_unlock(&mu); + } + break; + } + if( ev.type == SDL_Screenshot_Event) + { + char *filename = (char *)ev.user.data1; + screenshot_display(filename); + free(ev.user.data1); + } + } + + if (chan_can_recv(&fb_sync)) + { + PIXEL_SIZE *pixels; + int pitch = 0; + if (SDL_LockTexture(displayTexture, NULL, (void **) &pixels, &pitch) < 0) + { + SDL_Log("SDL_lock failed: %s", SDL_GetError()); + } + + void *fb; + chan_recv(&fb_sync, &fb); + memcpy(pixels, fb, sizeof(PIXEL_SIZE) * SCREEN_HEIGHT * SCREEN_WIDTH); + + SDL_UnlockTexture(displayTexture); + SDL_RenderCopy(renderer, displayTexture, NULL, NULL); + SDL_RenderPresent(renderer); + } + } /* while(!Radio_State.PowerOff) */ + + SDL_DestroyWindow(window); + SDL_Quit(); +} + +void init_sdl() +{ + pthread_mutex_init(&mu, NULL); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) + { + printf("SDL video init error!!\n"); + exit(1); + } + + // Register an SDL custom event type to handle screenshot requests + SDL_Screenshot_Event = SDL_RegisterEvents(1); + + chan_init(&fb_sync); + + window = SDL_CreateWindow("OpenRTX", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3, + SDL_WINDOW_SHOWN ); + + renderer = SDL_CreateRenderer(window, -1, 0); + SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT); + displayTexture = SDL_CreateTexture(renderer, + PIXEL_FORMAT, + SDL_TEXTUREACCESS_STREAMING, + SCREEN_WIDTH, + SCREEN_HEIGHT); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, displayTexture, NULL, NULL); + SDL_RenderPresent(renderer); +} diff --git a/platform/targets/linux/emulator/sdl_engine.h b/platform/targets/linux/emulator/sdl_engine.h new file mode 100644 index 00000000..2703265c --- /dev/null +++ b/platform/targets/linux/emulator/sdl_engine.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2021 by Alessio Caiazza IU5BON * + * * + * 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 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +#ifndef SDL_ENGINE_H +#define SDL_ENGINE_H + +#include "chan.h" +#include +#include + +/* + * Screen dimensions, adjust basing on the size of the screen you need to + * emulate + */ +#ifndef SCREEN_WIDTH +#define SCREEN_WIDTH 160 +#endif + +#ifndef SCREEN_HEIGHT +#define SCREEN_HEIGHT 128 +#endif + +#ifdef PIX_FMT_RGB565 +#define PIXEL_FORMAT SDL_PIXELFORMAT_RGB565 +#define PIXEL_SIZE uint16_t +#else +#define PIXEL_FORMAT SDL_PIXELFORMAT_ARGB8888 +#define PIXEL_SIZE uint32_t +#endif + +/* Initialize the SDL engine. Must be called in the Main Thread */ +void init_sdl(); +/* SDL main loop. Must be called in the Main Thread */ +void sdl_task(); +/* Thread-safe check to verify if the application entered the SDL main loop. */ +bool sdl_main_loop_ready(); + +#endif /* SDL_ENGINE_H */