esp32_composite_video_lib/lvgl_driver_video.c

304 wiersze
8.5 KiB
C

/*
ESP32 Composite Video Library
Copyright (C) 2022 aquaticus
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 <http://www.gnu.org/licenses/>.
*/
/// @file
#include "sdkconfig.h"
#if CONFIG_VIDEO_ENABLE_LVGL_SUPPORT
#include "esp_log.h"
#include "lvgl_driver_video.h"
#include "video.h"
#include "esp_timer.h"
#include "esp_heap_caps.h"
#define LV_TICK_PERIOD_MS 1
static const char* TAG="VIDEO";
static lv_disp_draw_buf_t disp_buf;
static lv_disp_drv_t disp_drv;
static lv_color_t* g_lvgl_aux_buf=NULL;
/**
* @brief Rounds to 32 bit boundary.
*
* \a composite_buffer_flush_cb() is more effective operating on 32 bits.
*/
void composite_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area)
{
#if LV_COLOR_DEPTH==1
if(NULL == g_lvgl_aux_buf)
{
// 4 pixels/32 bits
area->x1 &= ~0b11;
area->x2 |= 0b11;
}
else
{
// 32 pixels/32 bits
area->x1 &= ~0b11111;
area->x2 |= 0b11111;
}
#elif LV_COLOR_DEPTH==8
// 4 pixels/32 bits
area->x1 &= ~0b11;
area->x2 |= 0b11;
#elif LV_COLOR_DEPTH==16
// 2 pixels/32 bits
area->x1 &= ~1;
area->x2 |= 1;
#else
#pragma GCC error "Not supported LVGL color depth"
#endif
}
static void composite_dummy_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
ESP_LOGD(TAG, "LVGL Updating area (%u,%u)-(%u,%u)", area->x1, area->y1, area->x2, area->y2 );
#if CONFIG_LVGL_VSYNC
video_wait_frame();
#endif
lv_disp_flush_ready(disp_drv);
}
static void composite_buffer_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
ESP_LOGD(TAG, "LVGL Updating area (%u,%u)-(%u,%u)", area->x1, area->y1, area->x2, area->y2 );
#if CONFIG_LVGL_VSYNC
video_wait_frame();
#endif
register uint32_t pixel_data;
for(int y=area->y1; y<=area->y2; y++)
{
#if LV_COLOR_DEPTH==1
if(NULL==g_lvgl_aux_buf)
{
// Framebuffer is compatible with LVGL
// 4 pixels/uint32_t
uint32_t* dest = (uint32_t*)(video_get_frame_buffer_address()+y*video_get_width()+area->x1);
for(int x = area->x1; x <= area->x2; x+=4)
{
pixel_data = *((uint8_t*)color_p);
color_p++;
pixel_data |= *((uint8_t*)color_p) << 8;
color_p++;
pixel_data |= *((uint8_t*)color_p) << 16;
color_p++;
pixel_data |= *((uint8_t*)color_p) << 24;
color_p++;
*dest = pixel_data;
dest++;
}
}
else
{
// framebuffer 1 pixel/bit
uint32_t* dest = (uint32_t*)(video_get_frame_buffer_address()+y*video_get_width()/8+area->x1/8);
for(int x = area->x1; x <= area->x2; x+=32)
{
pixel_data=0;
for(int p=31;p>=0;p--)
{
if( color_p->full )
{
pixel_data |= (1<<p);
}
color_p++;
}
*dest = pixel_data;
dest++;
}
}
#elif LV_COLOR_DEPTH==8
// 4 pixels/uint32_t
uint32_t* dest = (uint32_t*)(video_get_frame_buffer_address()+y*video_get_width()+area->x1);
for(int x = area->x1; x <= area->x2; x+=4)
{
pixel_data = *((uint8_t*)color_p);
color_p++;
pixel_data |= *((uint8_t*)color_p) << 8;
color_p++;
pixel_data |= *((uint8_t*)color_p) << 16;
color_p++;
pixel_data |= *((uint8_t*)color_p) << 24;
color_p++;
*dest = pixel_data;
dest++;
}
#elif LV_COLOR_DEPTH==16
uint32_t* dest = (uint32_t*)(video_get_frame_buffer_address()+y*video_get_width()*2+area->x1*2);
for(int x = area->x1; x <= area->x2; x+=2)
{
pixel_data = *((uint16_t*)color_p);
color_p++;
pixel_data |= *((uint16_t*)color_p) << 16;
color_p++;
*dest = pixel_data;
dest++;
}
#endif
}
lv_disp_flush_ready(disp_drv);
}
#if CONFIG_LVGL_STATS
void composite_monitor_cb(lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px)
{
static uint32_t min=0xFFFFFFFF, max, count, avg;
if( time < min ) min = time;
if( time > max ) max = time;
avg += time;
count++;
const int64_t period = 1000*1000; //us
static int64_t prev = 0;
int64_t now = esp_timer_get_time();
if( now - prev > period )
{
prev = now;
ESP_LOGD(TAG, "LVGL AVG: %u, MIN: %u, MAX: %u [ms]", avg/count, min, max);
count = avg = max = 0;
min = 0xFFFFFFFF;
}
}
#endif
static void lv_tick_task(void* arg)
{
lv_tick_inc(LV_TICK_PERIOD_MS);
}
/**
* @brief Initializes LVGL to work directly with framebuffer.
* This mode does not use an extra memory for buffer but may degrade animations.
* Tearing effect may be visible.
*
* @param mode graphics mode, resolution and TV system PAL or NTSC.
*
* @note This function calls \a lv_video_disp_init_buf()
*
* @sa lv_video_disp_init_buf()
*/
void lv_video_disp_init(GRAPHICS_MODE mode)
{
lv_video_disp_init_buf(mode, NULL, 0);
}
/**
* @brief Initialize LVGL to use auxilary buffer.
* Using additional buffer improves quality of animations.
* If \a pixel_buffer is NULL LVGL will access framebuffer directly.
*
* @param mode graphics mode, resolution and TV system PAL or NTSC.
* @param pixel_buffer pointer to auxilary buffer. LVGL uses it to refresh a screen. If set to NULL LVGL access directly framebuffer.
* @param buffer_pixel_count size of \a pixel_buffer for in pixels.
*
* @sa lv_video_disp_init()
*/
void lv_video_disp_init_buf(GRAPHICS_MODE mode, lv_color_t* pixel_buffer, uint32_t buffer_pixel_count)
{
ESP_LOGI(TAG, "LVGL Init. Color depth %d", LV_COLOR_DEPTH);
FRAME_BUFFER_FORMAT fb_format;
#if LV_COLOR_DEPTH==1
if( NULL == g_lvgl_aux_buf )
{
// Framebuffer compatible with LVGL 1 pixel/byte
fb_format = FB_FORMAT_LVGL_1BPP;
}
else
{
// More memory efficient format 8 pixels/byte but needs buffers
fb_format = FB_FORMAT_GREY_1BPP;
}
#elif LV_COLOR_DEPTH==8
fb_format = FB_FORMAT_RGB_8BPP;
#elif LV_COLOR_DEPTH==16
fb_format = FB_FORMAT_RGB_16BPP;
#else
#pragma GCC error "Not supported LVGL color depth"
#endif
video_graphics(mode, fb_format);
lv_init();
lv_disp_drv_init(&disp_drv);
if( NULL == pixel_buffer)
{
ESP_LOGD(TAG, "LVGL updates framebuffer directly");
lv_disp_draw_buf_init(&disp_buf, video_get_frame_buffer_address(), NULL, video_get_width()*video_get_height());
disp_drv.direct_mode = 1; // Draw directly to framebuffer
disp_drv.flush_cb = composite_dummy_flush_cb;
g_lvgl_aux_buf = NULL;
}
else
{
ESP_LOGD(TAG, "LVGL updates using buffer");
lv_disp_draw_buf_init(&disp_buf, pixel_buffer, NULL, buffer_pixel_count);
disp_drv.flush_cb = composite_buffer_flush_cb;
disp_drv.rounder_cb = composite_rounder_cb;
g_lvgl_aux_buf = pixel_buffer;
}
disp_drv.draw_buf = &disp_buf;
assert(disp_drv.draw_buf);
disp_drv.draw_buf = &disp_buf;
disp_drv.hor_res = video_get_width();
disp_drv.ver_res = video_get_height();
#if CONFIG_LVGL_STATS
disp_drv.monitor_cb = composite_monitor_cb;
#endif
lv_disp_drv_register(&disp_drv);
const esp_timer_create_args_t periodic_timer_args =
{
.callback = &lv_tick_task,
.name = "lvgl_gui"
};
esp_timer_handle_t periodic_timer;
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));
}
#endif