/** * @file sdl_gpu.c * */ /********************* * INCLUDES *********************/ #include "sdl_gpu.h" #if USE_SDL_GPU #if LV_USE_GPU_SDL == 0 # error "LV_USE_GPU_SDL must be enabled" #endif #if USE_KEYBOARD # warning "KEYBOARD is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" #endif #if USE_MOUSE # warning "MOUSE is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" #endif #if USE_MOUSEWHEEL # warning "MOUSEWHEEL is deprecated, use SDL instead that. See lv_drivers/sdl/sdl.c" #endif #if USE_MONITOR # error "Cannot enable both MONITOR and SDL at the same time. " #endif #include #include #include #include SDL_INCLUDE_PATH /********************* * DEFINES *********************/ #ifndef KEYBOARD_BUFFER_SIZE #define KEYBOARD_BUFFER_SIZE SDL_TEXTINPUTEVENT_TEXT_SIZE #endif /********************** * TYPEDEFS **********************/ typedef struct { SDL_Window * window; SDL_Renderer * renderer; SDL_Texture * texture; volatile bool sdl_refr_qry; }monitor_t; /********************** * STATIC PROTOTYPES **********************/ static void window_create(monitor_t * m); static void window_update(monitor_t * m); int quit_filter(void * userdata, SDL_Event * event); static void monitor_sdl_clean_up(void); static void sdl_event_handler(lv_timer_t * t); static void monitor_sdl_refr(lv_timer_t * t); static void mouse_handler(SDL_Event * event); static void mousewheel_handler(SDL_Event * event); static uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key); static void keyboard_handler(SDL_Event * event); static int tick_thread(void *data); /*********************** * GLOBAL PROTOTYPES ***********************/ /********************** * STATIC VARIABLES **********************/ monitor_t monitor; #if SDL_DUAL_DISPLAY monitor_t monitor2; #endif static volatile bool sdl_quit_qry = false; static bool left_button_down = false; static int16_t last_x = 0; static int16_t last_y = 0; static int16_t wheel_diff = 0; static lv_indev_state_t wheel_state = LV_INDEV_STATE_RELEASED; static char buf[KEYBOARD_BUFFER_SIZE]; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void sdl_init(void) { /*Initialize the SDL*/ SDL_Init(SDL_INIT_VIDEO); SDL_SetEventFilter(quit_filter, NULL); window_create(&monitor); #if SDL_DUAL_DISPLAY window_create(&monitor2); int x, y; SDL_GetWindowPosition(monitor2.window, &x, &y); SDL_SetWindowPosition(monitor.window, x + (SDL_HOR_RES * SDL_ZOOM) / 2 + 10, y); SDL_SetWindowPosition(monitor2.window, x - (SDL_HOR_RES * SDL_ZOOM) / 2 - 10, y); #endif SDL_StartTextInput(); /* Tick init. * You have to call 'lv_tick_inc()' in periodically to inform LittelvGL about * how much time were elapsed Create an SDL thread to do this*/ SDL_CreateThread(tick_thread, "tick", NULL); lv_timer_create(sdl_event_handler, 10, NULL); } void sdl_gpu_disp_draw_buf_init(lv_disp_draw_buf_t *draw_buf) { lv_disp_draw_buf_init(draw_buf, monitor.texture, NULL, SDL_HOR_RES * SDL_VER_RES); } void sdl_gpu_disp_drv_init(lv_disp_drv_t *driver) { lv_disp_drv_init(driver); driver->user_data = monitor.renderer; } /** * Flush a buffer to the marked area * @param disp_drv pointer to driver where this function belongs * @param area an area where to copy `color_p` * @param color_p an array of pixels to copy to the `area` part of the screen */ void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { lv_coord_t hres = disp_drv->hor_res; lv_coord_t vres = disp_drv->ver_res; // printf("x1:%d,y1:%d,x2:%d,y2:%d\n", area->x1, area->y1, area->x2, area->y2); /*Return if the area is out the screen*/ if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { lv_disp_flush_ready(disp_drv); return; } monitor.sdl_refr_qry = true; /* TYPICALLY YOU DO NOT NEED THIS * If it was the last part to refresh update the texture of the window.*/ if(lv_disp_flush_is_last(disp_drv)) { monitor_sdl_refr(NULL); } /*IMPORTANT! It must be called to tell the system the flush is ready*/ lv_disp_flush_ready(disp_drv); } #if SDL_DUAL_DISPLAY /** * Flush a buffer to the marked area * @param disp_drv pointer to driver where this function belongs * @param area an area where to copy `color_p` * @param color_p an array of pixels to copy to the `area` part of the screen */ void sdl_display_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { lv_coord_t hres = disp_drv->hor_res; lv_coord_t vres = disp_drv->ver_res; /*Return if the area is out the screen*/ if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { lv_disp_flush_ready(disp_drv); return; } monitor2.sdl_refr_qry = true; /* TYPICALLY YOU DO NOT NEED THIS * If it was the last part to refresh update the texture of the window.*/ if(lv_disp_flush_is_last(disp_drv)) { monitor_sdl_refr(NULL); } /*IMPORTANT! It must be called to tell the system the flush is ready*/ lv_disp_flush_ready(disp_drv); } #endif void sdl_display_resize(lv_disp_t *disp, int width, int height) { lv_disp_drv_t *driver = disp->driver; SDL_Renderer *renderer = driver->user_data; if (driver->draw_buf->buf1) { SDL_DestroyTexture(driver->draw_buf->buf1); } SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, width, height); monitor.texture = texture; lv_disp_draw_buf_init(driver->draw_buf, texture, NULL, width * height); driver->hor_res = (lv_coord_t) width; driver->ver_res = (lv_coord_t) height; SDL_RendererInfo renderer_info; SDL_GetRendererInfo(renderer, &renderer_info); SDL_assert(renderer_info.flags & SDL_RENDERER_TARGETTEXTURE); SDL_SetRenderTarget(renderer, texture); lv_disp_drv_update(disp, driver); } /** * Get the current position and state of the mouse * @param indev_drv pointer to the related input device driver * @param data store the mouse data here */ void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { (void) indev_drv; /*Unused*/ /*Store the collected data*/ data->point.x = last_x; data->point.y = last_y; data->state = left_button_down ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; } /** * Get encoder (i.e. mouse wheel) ticks difference and pressed state * @param indev_drv pointer to the related input device driver * @param data store the read data here */ void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { (void) indev_drv; /*Unused*/ data->state = wheel_state; data->enc_diff = wheel_diff; wheel_diff = 0; } /** * Get input from the keyboard. * @param indev_drv pointer to the related input device driver * @param data store the red data here */ void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { (void) indev_drv; /*Unused*/ static bool dummy_read = false; const size_t len = strlen(buf); /*Send a release manually*/ if (dummy_read) { dummy_read = false; data->state = LV_INDEV_STATE_RELEASED; data->continue_reading = len > 0; } /*Send the pressed character*/ else if (len > 0) { dummy_read = true; data->state = LV_INDEV_STATE_PRESSED; data->key = buf[0]; memmove(buf, buf + 1, len); data->continue_reading = true; } } /********************** * STATIC FUNCTIONS **********************/ /** * SDL main thread. All SDL related task have to be handled here! * It initializes SDL, handles drawing and the mouse. */ static void sdl_event_handler(lv_timer_t * t) { (void)t; /*Refresh handling*/ SDL_Event event; while(SDL_PollEvent(&event)) { mouse_handler(&event); mousewheel_handler(&event); keyboard_handler(&event); if((&event)->type == SDL_WINDOWEVENT) { switch((&event)->window.event) { #if SDL_VERSION_ATLEAST(2, 0, 5) case SDL_WINDOWEVENT_TAKE_FOCUS: #endif case SDL_WINDOWEVENT_EXPOSED: window_update(&monitor); #if SDL_DUAL_DISPLAY window_update(&monitor2); #endif break; default: break; } } } /*Run until quit event not arrives*/ if(sdl_quit_qry) { monitor_sdl_clean_up(); exit(0); } } /** * SDL main thread. All SDL related task have to be handled here! * It initializes SDL, handles drawing and the mouse. */ static void monitor_sdl_refr(lv_timer_t * t) { (void)t; /*Refresh handling*/ if(monitor.sdl_refr_qry != false) { monitor.sdl_refr_qry = false; window_update(&monitor); } #if SDL_DUAL_DISPLAY if(monitor2.sdl_refr_qry != false) { monitor2.sdl_refr_qry = false; window_update(&monitor2); } #endif } int quit_filter(void * userdata, SDL_Event * event) { (void)userdata; if(event->type == SDL_WINDOWEVENT) { if(event->window.event == SDL_WINDOWEVENT_CLOSE) { sdl_quit_qry = true; } } else if(event->type == SDL_QUIT) { sdl_quit_qry = true; } return 1; } static void monitor_sdl_clean_up(void) { SDL_DestroyTexture(monitor.texture); SDL_DestroyRenderer(monitor.renderer); SDL_DestroyWindow(monitor.window); #if SDL_DUAL_DISPLAY SDL_DestroyTexture(monitor2.texture); SDL_DestroyRenderer(monitor2.renderer); SDL_DestroyWindow(monitor2.window); #endif SDL_Quit(); } static void window_create(monitor_t * m) { m->window = SDL_CreateWindow("TFT Simulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_HOR_RES * SDL_ZOOM, SDL_VER_RES * SDL_ZOOM, 0); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/ m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED); m->texture = SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, SDL_HOR_RES, SDL_VER_RES); SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_BLEND); /* For first frame */ SDL_SetRenderTarget(m->renderer, m->texture); m->sdl_refr_qry = true; } static void window_update(monitor_t * m) { SDL_SetRenderTarget(m->renderer, NULL); SDL_RenderClear(m->renderer); #if LV_COLOR_SCREEN_TRANSP SDL_SetRenderDrawColor(m->renderer, 0xff, 0, 0, 0xff); SDL_Rect r; r.x = 0; r.y = 0; r.w = SDL_HOR_RES; r.h = SDL_VER_RES; SDL_RenderDrawRect(m->renderer, &r); #endif /*Update the renderer with the texture containing the rendered image*/ SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_BLEND); SDL_RenderSetClipRect(m->renderer, NULL); SDL_RenderCopy(m->renderer, m->texture, NULL, NULL); SDL_RenderPresent(m->renderer); SDL_SetRenderTarget(m->renderer, m->texture); } static void mouse_handler(SDL_Event * event) { switch(event->type) { case SDL_MOUSEBUTTONUP: if(event->button.button == SDL_BUTTON_LEFT) left_button_down = false; break; case SDL_MOUSEBUTTONDOWN: if(event->button.button == SDL_BUTTON_LEFT) { left_button_down = true; last_x = event->motion.x / SDL_ZOOM; last_y = event->motion.y / SDL_ZOOM; } break; case SDL_MOUSEMOTION: last_x = event->motion.x / SDL_ZOOM; last_y = event->motion.y / SDL_ZOOM; break; case SDL_FINGERUP: left_button_down = false; last_x = LV_HOR_RES * event->tfinger.x / SDL_ZOOM; last_y = LV_VER_RES * event->tfinger.y / SDL_ZOOM; break; case SDL_FINGERDOWN: left_button_down = true; last_x = LV_HOR_RES * event->tfinger.x / SDL_ZOOM; last_y = LV_VER_RES * event->tfinger.y / SDL_ZOOM; break; case SDL_FINGERMOTION: last_x = LV_HOR_RES * event->tfinger.x / SDL_ZOOM; last_y = LV_VER_RES * event->tfinger.y / SDL_ZOOM; break; } } /** * It is called periodically from the SDL thread to check mouse wheel state * @param event describes the event */ static void mousewheel_handler(SDL_Event * event) { switch(event->type) { case SDL_MOUSEWHEEL: // Scroll down (y = -1) means positive encoder turn, // so invert it #ifdef __EMSCRIPTEN__ /*Escripten scales it wrong*/ if(event->wheel.y < 0) wheel_diff++; if(event->wheel.y > 0) wheel_diff--; #else wheel_diff = -event->wheel.y; #endif break; case SDL_MOUSEBUTTONDOWN: if(event->button.button == SDL_BUTTON_MIDDLE) { wheel_state = LV_INDEV_STATE_PRESSED; } break; case SDL_MOUSEBUTTONUP: if(event->button.button == SDL_BUTTON_MIDDLE) { wheel_state = LV_INDEV_STATE_RELEASED; } break; default: break; } } /** * Called periodically from the SDL thread, store text input or control characters in the buffer. * @param event describes the event */ static void keyboard_handler(SDL_Event * event) { /* We only care about SDL_KEYDOWN and SDL_TEXTINPUT events */ switch(event->type) { case SDL_KEYDOWN: /*Button press*/ { const uint32_t ctrl_key = keycode_to_ctrl_key(event->key.keysym.sym); if (ctrl_key == '\0') return; const size_t len = strlen(buf); if (len < KEYBOARD_BUFFER_SIZE - 1) { buf[len] = ctrl_key; buf[len + 1] = '\0'; } break; } case SDL_TEXTINPUT: /*Text input*/ { const size_t len = strlen(buf) + strlen(event->text.text); if (len < KEYBOARD_BUFFER_SIZE - 1) strcat(buf, event->text.text); } break; default: break; } } /** * Convert a SDL key code to it's LV_KEY_* counterpart or return '\0' if it's not a control character. * @param sdl_key the key code * @return LV_KEY_* control character or '\0' */ static uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key) { /*Remap some key to LV_KEY_... to manage groups*/ switch(sdl_key) { case SDLK_RIGHT: case SDLK_KP_PLUS: return LV_KEY_RIGHT; case SDLK_LEFT: case SDLK_KP_MINUS: return LV_KEY_LEFT; case SDLK_UP: return LV_KEY_UP; case SDLK_DOWN: return LV_KEY_DOWN; case SDLK_ESCAPE: return LV_KEY_ESC; case SDLK_BACKSPACE: return LV_KEY_BACKSPACE; case SDLK_DELETE: return LV_KEY_DEL; case SDLK_KP_ENTER: case '\r': return LV_KEY_ENTER; case SDLK_TAB: case SDLK_PAGEDOWN: return LV_KEY_NEXT; case SDLK_PAGEUP: return LV_KEY_PREV; default: return '\0'; } } /** * A task to measure the elapsed time for LVGL * @param data unused * @return never return */ static int tick_thread(void *data) { (void)data; while(1) { SDL_Delay(5); lv_tick_inc(5); /*Tell LittelvGL that 5 milliseconds were elapsed*/ } return 0; } #endif /*USE_SDL_GPU*/