nuttx-apps/graphics/lvgl/port/lv_port_fbdev.c
pengyiqiang ca403ffaf7 lvgl/port: optimize fbdev buffer sync algorithm
Signed-off-by: pengyiqiang <pengyiqiang@xiaomi.com>
2023-03-02 13:52:05 +08:00

676 lines
18 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************
* apps/graphics/lvgl/port/lv_port_fbdev.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/video/fb.h>
#include <nuttx/video/rgbcolors.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include "lv_port_fbdev.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if defined(CONFIG_FB_UPDATE)
# define FBDEV_UPDATE_AREA(obj, area) fbdev_update_area(obj, area)
#else
# define FBDEV_UPDATE_AREA(obj, area)
#endif
/****************************************************************************
* Private Type Declarations
****************************************************************************/
struct fbdev_obj_s
{
lv_disp_draw_buf_t disp_draw_buf;
lv_disp_drv_t disp_drv;
FAR lv_disp_t *disp;
FAR void *last_buffer;
FAR void *act_buffer;
lv_area_t inv_areas[LV_INV_BUF_SIZE];
uint16_t inv_areas_len;
lv_area_t final_area;
int fd;
FAR void *fbmem;
uint32_t fbmem2_yoffset;
struct fb_videoinfo_s vinfo;
struct fb_planeinfo_s pinfo;
bool double_buffer;
};
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
#if defined(CONFIG_FB_UPDATE)
/****************************************************************************
* Name: fbdev_update_area
****************************************************************************/
static void fbdev_update_area(FAR struct fbdev_obj_s *fbdev_obj,
FAR const lv_area_t *area_p)
{
struct fb_area_s fb_area;
fb_area.x = area_p->x1;
fb_area.y = area_p->y1;
fb_area.w = area_p->x2 - area_p->x1 + 1;
fb_area.h = area_p->y2 - area_p->y1 + 1;
LV_LOG_TRACE("area: (%d, %d) %d x %d",
fb_area.x, fb_area.y, fb_area.w, fb_area.h);
ioctl(fbdev_obj->fd, FBIO_UPDATE,
(unsigned long)((uintptr_t)&fb_area));
LV_LOG_TRACE("finished");
}
#endif
/****************************************************************************
* Name: fbdev_switch_buffer
****************************************************************************/
static void fbdev_switch_buffer(FAR struct fbdev_obj_s *fbdev_obj)
{
FAR lv_disp_t *disp_refr = fbdev_obj->disp;
uint16_t inv_index;
/* check inv_areas_len, it must == 0 */
if (fbdev_obj->inv_areas_len != 0)
{
LV_LOG_ERROR("Repeated flush action detected! "
"inv_areas_len(%d) != 0",
fbdev_obj->inv_areas_len);
fbdev_obj->inv_areas_len = 0;
}
/* Save dirty area table for next synchronizationn */
for (inv_index = 0; inv_index < disp_refr->inv_p; inv_index++)
{
if (disp_refr->inv_area_joined[inv_index] == 0)
{
fbdev_obj->inv_areas[fbdev_obj->inv_areas_len] =
disp_refr->inv_areas[inv_index];
fbdev_obj->inv_areas_len++;
}
}
/* Save the buffer address for the next synchronization */
fbdev_obj->last_buffer = fbdev_obj->act_buffer;
LV_LOG_TRACE("Commit buffer = %p, yoffset = %" PRIu32,
fbdev_obj->act_buffer,
fbdev_obj->pinfo.yoffset);
if (fbdev_obj->act_buffer == fbdev_obj->fbmem)
{
fbdev_obj->pinfo.yoffset = 0;
fbdev_obj->act_buffer = fbdev_obj->fbmem
+ fbdev_obj->fbmem2_yoffset * fbdev_obj->pinfo.stride;
}
else
{
fbdev_obj->pinfo.yoffset = fbdev_obj->fbmem2_yoffset;
fbdev_obj->act_buffer = fbdev_obj->fbmem;
}
/* Commit buffer to fb driver */
ioctl(fbdev_obj->fd, FBIOPAN_DISPLAY,
(unsigned long)((uintptr_t)&(fbdev_obj->pinfo)));
LV_LOG_TRACE("finished");
}
#if defined(CONFIG_FB_SYNC)
/****************************************************************************
* Name: fbdev_disp_vsync_refr
****************************************************************************/
static void fbdev_disp_vsync_refr(FAR lv_timer_t *timer)
{
int ret;
FAR struct fbdev_obj_s *fbdev_obj = timer->user_data;
LV_LOG_TRACE("Check vsync...");
ret = ioctl(fbdev_obj->fd, FBIO_WAITFORVSYNC, NULL);
if (ret != OK)
{
LV_LOG_TRACE("No vsync signal detect");
return;
}
LV_LOG_TRACE("Refresh start");
_lv_disp_refr_timer(NULL);
}
#endif /* CONFIG_FB_SYNC */
/****************************************************************************
* Name: fbdev_check_inv_area_covered
****************************************************************************/
static bool fbdev_check_inv_area_covered(FAR lv_disp_t *disp_refr,
FAR const lv_area_t *area_p)
{
int i;
for (i = 0; i < disp_refr->inv_p; i++)
{
FAR const lv_area_t *cur_area;
/* Skip joined area */
if (disp_refr->inv_area_joined[i])
{
continue;
}
cur_area = &disp_refr->inv_areas[i];
/* Check cur_area is coverd area_p */
if (_lv_area_is_in(area_p, cur_area, 0))
{
return true;
}
}
return false;
}
/****************************************************************************
* Name: fbdev_render_start
****************************************************************************/
static void fbdev_render_start(FAR lv_disp_drv_t *disp_drv)
{
FAR struct fbdev_obj_s *fbdev_obj = disp_drv->user_data;
FAR lv_disp_t *disp_refr;
FAR lv_draw_ctx_t *draw_ctx;
lv_coord_t hor_res;
int i;
/* No need sync buffer when inv_areas_len == 0 */
if (fbdev_obj->inv_areas_len == 0)
{
LV_LOG_TRACE("No sync area");
return;
}
LV_LOG_TRACE("Start sync %d areas...", fbdev_obj->inv_areas_len);
disp_refr = _lv_refr_get_disp_refreshing();
draw_ctx = disp_drv->draw_ctx;
hor_res = disp_drv->hor_res;
for (i = 0; i < fbdev_obj->inv_areas_len; i++)
{
FAR const lv_area_t *last_area = &fbdev_obj->inv_areas[i];
LV_LOG_TRACE("Check area[%d]: (%d, %d) %d x %d",
i,
(int)last_area->x1, (int)last_area->y1,
(int)lv_area_get_width(last_area),
(int)lv_area_get_height(last_area));
if (fbdev_check_inv_area_covered(disp_refr, last_area))
{
LV_LOG_TRACE("Skipped");
continue;
}
/* Sync the inv area of the previous frame */
draw_ctx->buffer_copy(
draw_ctx,
fbdev_obj->act_buffer, hor_res, last_area,
fbdev_obj->last_buffer, hor_res, last_area);
LV_LOG_TRACE("Copied");
}
fbdev_obj->inv_areas_len = 0;
}
/****************************************************************************
* Name: fbdev_flush_direct
****************************************************************************/
static void fbdev_flush_direct(FAR lv_disp_drv_t *disp_drv,
FAR const lv_area_t *area_p,
FAR lv_color_t *color_p)
{
FAR struct fbdev_obj_s *fbdev_obj = disp_drv->user_data;
/* Commit the buffer after the last flush */
if (!lv_disp_flush_is_last(disp_drv))
{
lv_disp_flush_ready(disp_drv);
return;
}
fbdev_switch_buffer(fbdev_obj);
FBDEV_UPDATE_AREA(fbdev_obj, area_p);
/* Tell the flushing is ready */
lv_disp_flush_ready(disp_drv);
}
/****************************************************************************
* Name: fbdev_update_part
****************************************************************************/
static void fbdev_update_part(FAR struct fbdev_obj_s *fbdev_obj,
FAR lv_disp_drv_t *disp_drv,
FAR const lv_area_t *area_p)
{
FAR lv_area_t *final_area = &fbdev_obj->final_area;
if (final_area->x1 < 0)
{
*final_area = *area_p;
}
else
{
_lv_area_join(final_area, final_area, area_p);
}
if (!lv_disp_flush_is_last(disp_drv))
{
lv_disp_flush_ready(disp_drv);
return;
}
if (fbdev_obj->double_buffer)
{
fbdev_switch_buffer(fbdev_obj);
}
FBDEV_UPDATE_AREA(fbdev_obj, final_area);
/* Mark it is invalid */
final_area->x1 = -1;
/* Tell the flushing is ready */
lv_disp_flush_ready(disp_drv);
}
/****************************************************************************
* Name: fbdev_flush_normal
****************************************************************************/
static void fbdev_flush_normal(FAR lv_disp_drv_t *disp_drv,
FAR const lv_area_t *area_p,
FAR lv_color_t *color_p)
{
FAR struct fbdev_obj_s *fbdev_obj = disp_drv->user_data;
int x1 = area_p->x1;
int y1 = area_p->y1;
int y2 = area_p->y2;
int y;
int w = lv_area_get_width(area_p);
FAR lv_color_t *fbp = fbdev_obj->act_buffer;
fb_coord_t fb_xres = fbdev_obj->vinfo.xres;
int hor_size = w * sizeof(lv_color_t);
FAR lv_color_t *cur_pos = fbp + y1 * fb_xres + x1;
LV_LOG_TRACE("start copy");
for (y = y1; y <= y2; y++)
{
lv_memcpy(cur_pos, color_p, hor_size);
cur_pos += fb_xres;
color_p += w;
}
LV_LOG_TRACE("end copy");
fbdev_update_part(fbdev_obj, disp_drv, area_p);
}
/****************************************************************************
* Name: fbdev_get_pinfo
****************************************************************************/
static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s *pinfo)
{
int ret = ioctl(fd, FBIOGET_PLANEINFO,
(unsigned long)((uintptr_t)pinfo));
if (ret < 0)
{
LV_LOG_ERROR("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d", errno);
return ret;
}
LV_LOG_INFO("PlaneInfo (plane %d):", pinfo->display);
LV_LOG_INFO(" fbmem: %p", pinfo->fbmem);
LV_LOG_INFO(" fblen: %lu", (unsigned long)pinfo->fblen);
LV_LOG_INFO(" stride: %u", pinfo->stride);
LV_LOG_INFO(" display: %u", pinfo->display);
LV_LOG_INFO(" bpp: %u", pinfo->bpp);
/* Only these pixel depths are supported. viinfo.fmt is ignored, only
* certain color formats are supported.
*/
if (pinfo->bpp != 32 && pinfo->bpp != 16 &&
pinfo->bpp != 8 && pinfo->bpp != 1)
{
LV_LOG_ERROR("bpp = %u not supported", pinfo->bpp);
return -1;
}
return 0;
}
/****************************************************************************
* Name: fbdev_try_init_fbmem2
****************************************************************************/
static int fbdev_try_init_fbmem2(FAR struct fbdev_obj_s *state)
{
uintptr_t buf_offset;
struct fb_planeinfo_s pinfo;
memset(&pinfo, 0, sizeof(pinfo));
/* Get display[1] planeinfo */
pinfo.display = state->pinfo.display + 1;
if (fbdev_get_pinfo(state->fd, &pinfo) < 0)
{
return -1;
}
/* check display and match bpp */
if (!(pinfo.display != state->pinfo.display
&& pinfo.bpp == state->pinfo.bpp))
{
LV_LOG_INFO("fbmem2 is incorrect");
return -1;
}
/* Check the buffer address offset,
* It needs to be divisible by pinfo.stride
*/
buf_offset = pinfo.fbmem - state->fbmem;
if ((buf_offset % state->pinfo.stride) != 0)
{
LV_LOG_ERROR("The buf_offset(%" PRIuPTR ") is incorrect,"
" it needs to be divisible"
" by pinfo.stride(%d)",
buf_offset, state->pinfo.stride);
return -1;
}
/* Enable double buffer mode */
state->double_buffer = true;
state->fbmem2_yoffset = buf_offset / state->pinfo.stride;
LV_LOG_INFO("Use non-consecutive fbmem2 = %p, yoffset = %" PRIu32,
pinfo.fbmem, state->fbmem2_yoffset);
return 0;
}
/****************************************************************************
* Name: fbdev_init
****************************************************************************/
static FAR lv_disp_t *fbdev_init(FAR struct fbdev_obj_s *state)
{
FAR struct fbdev_obj_s *fbdev_obj = malloc(sizeof(struct fbdev_obj_s));
FAR lv_disp_drv_t *disp_drv;
int fb_xres = state->vinfo.xres;
int fb_yres = state->vinfo.yres;
size_t fb_size = fb_xres * fb_yres;
FAR lv_color_t *buf1 = NULL;
FAR lv_color_t *buf2 = NULL;
if (fbdev_obj == NULL)
{
LV_LOG_ERROR("fbdev_obj_s malloc failed");
return NULL;
}
*fbdev_obj = *state;
disp_drv = &(fbdev_obj->disp_drv);
lv_disp_drv_init(disp_drv);
disp_drv->draw_buf = &(fbdev_obj->disp_draw_buf);
disp_drv->screen_transp = false;
disp_drv->user_data = fbdev_obj;
disp_drv->hor_res = fb_xres;
disp_drv->ver_res = fb_yres;
if (fbdev_obj->double_buffer)
{
LV_LOG_INFO("Double buffer mode");
buf1 = fbdev_obj->fbmem;
buf2 = fbdev_obj->fbmem
+ fbdev_obj->fbmem2_yoffset * fbdev_obj->pinfo.stride;
disp_drv->direct_mode = true;
disp_drv->flush_cb = fbdev_flush_direct;
disp_drv->render_start_cb = fbdev_render_start;
}
else
{
LV_LOG_INFO("Single buffer mode");
buf1 = malloc(fb_size * sizeof(lv_color_t));
LV_ASSERT_MALLOC(buf1);
if (!buf1)
{
LV_LOG_ERROR("failed to malloc draw buffer");
goto failed;
}
disp_drv->flush_cb = fbdev_flush_normal;
}
lv_disp_draw_buf_init(&(fbdev_obj->disp_draw_buf), buf1, buf2, fb_size);
fbdev_obj->act_buffer = fbdev_obj->fbmem;
fbdev_obj->disp = lv_disp_drv_register(&(fbdev_obj->disp_drv));
#if defined(CONFIG_FB_SYNC)
/* If double buffer and vsync is supported, use active refresh method */
if (fbdev_obj->disp_drv.direct_mode)
{
FAR lv_timer_t *refr_timer = _lv_disp_get_refr_timer(fbdev_obj->disp);
lv_timer_del(refr_timer);
fbdev_obj->disp->refr_timer = NULL;
lv_timer_create(fbdev_disp_vsync_refr, 1, fbdev_obj);
}
#endif
return fbdev_obj->disp;
failed:
free(fbdev_obj);
return NULL;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: lv_port_fbdev_init
*
* Description:
* Framebuffer device interface initialization.
*
* Input Parameters:
* dev_path - Framebuffer device path, set to NULL to use the default path.
*
* Returned Value:
* lv_disp object address on success; NULL on failure.
*
****************************************************************************/
FAR lv_disp_t *lv_port_fbdev_init(FAR const char *dev_path)
{
FAR const char *device_path = dev_path;
struct fbdev_obj_s state;
int ret;
FAR lv_disp_t *disp;
memset(&state, 0, sizeof(state));
if (device_path == NULL)
{
device_path = CONFIG_LV_PORT_FBDEV_DEFAULT_DEVICEPATH;
}
LV_LOG_INFO("fbdev %s opening", device_path);
state.fd = open(device_path, O_RDWR);
if (state.fd < 0)
{
LV_LOG_ERROR("fbdev %s open failed: %d", device_path, errno);
return NULL;
}
/* Get the characteristics of the framebuffer */
ret = ioctl(state.fd, FBIOGET_VIDEOINFO,
(unsigned long)((uintptr_t)&state.vinfo));
if (ret < 0)
{
LV_LOG_ERROR("ioctl(FBIOGET_VIDEOINFO) failed: %d", errno);
close(state.fd);
return NULL;
}
LV_LOG_INFO("VideoInfo:");
LV_LOG_INFO(" fmt: %u", state.vinfo.fmt);
LV_LOG_INFO(" xres: %u", state.vinfo.xres);
LV_LOG_INFO(" yres: %u", state.vinfo.yres);
LV_LOG_INFO(" nplanes: %u", state.vinfo.nplanes);
ret = fbdev_get_pinfo(state.fd, &state.pinfo);
if (ret < 0)
{
close(state.fd);
return NULL;
}
/* Check color depth */
if (!(state.pinfo.bpp == LV_COLOR_DEPTH ||
(state.pinfo.bpp == 24 && LV_COLOR_DEPTH == 32)))
{
LV_LOG_ERROR("fbdev bpp = %d, LV_COLOR_DEPTH = %d, "
"color depth does not match.",
state.pinfo.bpp, LV_COLOR_DEPTH);
close(state.fd);
return NULL;
}
state.double_buffer = (state.pinfo.yres_virtual == (state.vinfo.yres * 2));
/* mmap() the framebuffer.
*
* NOTE: In the FLAT build the frame buffer address returned by the
* FBIOGET_PLANEINFO IOCTL command will be the same as the framebuffer
* address. mmap(), however, is the preferred way to get the framebuffer
* address because in the KERNEL build, it will perform the necessary
* address mapping to make the memory accessible to the application.
*/
state.fbmem = mmap(NULL, state.pinfo.fblen, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FILE, state.fd, 0);
if (state.fbmem == MAP_FAILED)
{
LV_LOG_ERROR("ioctl(FBIOGET_PLANEINFO) failed: %d", errno);
close(state.fd);
return NULL;
}
LV_LOG_INFO("Mapped FB: %p", state.fbmem);
if (state.double_buffer)
{
state.fbmem2_yoffset = state.vinfo.yres;
}
else
{
fbdev_try_init_fbmem2(&state);
}
disp = fbdev_init(&state);
if (!disp)
{
munmap(state.fbmem, state.pinfo.fblen);
close(state.fd);
return NULL;
}
return disp;
}