Compare commits

...
18 Commits
Author SHA1 Message Date
Vincent Wei 8edfdd6dd9 Update version to 5.0.17 2026-02-07 11:55:11 +08:00
Vincent Wei e5a6cb3056 fix a compilation error about bool type 2026-02-07 11:48:05 +08:00
Vincent Wei ea3f345252 Merge remote-tracking branch 'origin/drm-page-flip' 2026-02-07 11:31:00 +08:00
xueshuming 42b28fcc59 1. Fix legacy BIDI bracket reordering/mirroring for MBC Achar32.
2. Fix wrong BIDI type mapping for ISO8859-6 0xEF.
2026-02-03 22:40:14 +08:00
xueshuming 2bde8e299a Fix: Arabic letter 'ي' incorrectly using medial form at word start. 2026-02-03 22:30:41 +08:00
xueshuming 93671fc379 Use drmModePageFlip for drmVideo to fix screen tearing on low-end devices 2026-01-30 09:09:05 +08:00
VincentWei 08d4ab78e5 do not check pciaccess library if the cpu is not i386 2025-05-29 11:25:25 +08:00
Vincent Wei 0cda03a2eb Merge branch 'subpixel' into 'rel-5-0'
add func blend_pixel to fix font rendering issues on transparent backgrounds(SUBPIXEL)

See merge request VincentWei/minigui!18
2025-05-29 02:59:47 +00:00
xueshuming 4cf4d204e7 add func blend_pixel to fix font rendering issues on transparent backgrounds(SUBPIXEL) 2025-05-28 20:55:38 +08:00
VincentWei 6aa294e75b ignore ltmain.sh 2025-05-28 14:45:58 +08:00
VincentWei 7718be5200 ignore m4/*.m4 2025-05-28 14:44:43 +08:00
VincentWei 78e6d6581e update Makefile.am 2025-05-28 14:43:56 +08:00
VincentWei 62b3dc11d0 update libtool files 2025-05-28 14:43:25 +08:00
Vincent Wei 67cddafa19 change version to 5.0.16 2025-05-27 16:04:53 +08:00
Vincent Wei 4ae46a4b94 update 2025-05-27 15:53:44 +08:00
xueshuming 2791dc99ef Init Order: call 'mg_InitTimer' before call 'SystemThreads' 2025-05-27 15:45:03 +08:00
Vincent Wei b7525f3001 fix a bug: MSG_TIMEOUT for desktop thread was not fired correctly 2025-05-27 14:45:53 +08:00
Vincent Wei 4c55f2eac3 use sem_timedwait in the idle handler for desktop threads 2025-05-27 11:08:55 +08:00
20 changed files with 580 additions and 8985 deletions
+2
View File
@@ -10,6 +10,7 @@ Makefile
Makefile.in
.deps/
.libs/
m4/*.m4
/acinclude.m4
/aclocal.m4
/autom4te.cache
@@ -21,6 +22,7 @@ Makefile.in
/depcomp
/install-sh
/libtool
/ltmain.sh
/*.pc
/*config.h
/*config.h.in
+2
View File
@@ -1,3 +1,5 @@
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src 3rd-party include rtos etc m4 cmake build
EXTRA_DIST = minigui.pc.in \
+1 -1
View File
@@ -566,7 +566,7 @@ A brief history description of the development progress is listed as follow:
1. Jan., 2022: FMSoft released MiniGUI version 5.0.9.
1. Sep., 2022: FMSoft released MiniGUI version 5.0.10.
1. Aug., 2023: FMSoft released MiniGUI version 5.0.13.
1. May., 2025: FMSoft released MiniGUI version 5.0.15.
1. May., 2025: FMSoft released MiniGUI version 5.0.16.
## AUTHORS AND COPYING
+30
View File
@@ -1,5 +1,9 @@
# Release Notes
- [Version 5.0.17](#version-5017)
+ [What's new in version 5.0.17](#whats-new-in-version-5017)
- [Version 5.0.16](#version-5016)
+ [What's new in version 5.0.16](#whats-new-in-version-5016)
- [Version 5.0.15](#version-5015)
+ [What's new in version 5.0.15](#whats-new-in-version-5015)
- [Version 5.0.13](#version-5013)
@@ -34,6 +38,32 @@
+ [Changes leading to incompatibility](#changes-leading-to-incompatibility)
+ [Deprecated APIs](#deprecated-apis)
## Version 5.0.17
On Feb. 07, 2026, FMSoft announces the availability of MiniGUI 5.0.17,
which is a bugfix release with some minor enhancements of MiniGUI 5.0.x.
* BUGFIXING:
- Fix legacy BIDI bracket reordering/mirroring for MBC Achar32.
- Fix wrong BIDI type mapping for ISO8859-6 0xEF.
- Fix a compilation error about bool type
- Fix Arabic letter 'ي' incorrectly using medial form at word start.
* OPTIMIZATION:
- Use drmModePageFlip for drmVideo to fix screen tearing.
## Version 5.0.16
On May 27, 2025, FMSoft announces the availability of MiniGUI 5.0.16,
which is a bugfix release with some minor enhancements of MiniGUI 5.0.x.
* BUGFIXING:
- The desktop thread can not handle the request from other threads as soon as possible.
- The `MSG_TIMEOUT` message can not be fired correctly under MiniGUI-Threads mode.
- Bad subpixel rendering when background color is RGBA0000.
* TUNING:
- Tune `configure.ac` and `Makefile.am` to use the system libtool scripts.
- Do not check `pciaccess` if the target architecture is not `i386`.
## Version 5.0.15
On May 09, 2025, FMSoft announces the availability of MiniGUI 5.0.15,
+1 -1
View File
@@ -1,4 +1,4 @@
Version 5.0.15 (2025/05/09)
Version 5.0.17 (2026/02/07)
This is a minor enhancement and bugfix release of MiniGUI 5.0.x, the stable version.
+2 -1
View File
@@ -1,6 +1,7 @@
#!/bin/sh
cat m4/*.m4 > acinclude.m4
libtoolize
cat m4/*.m4 > aclocal.m4
aclocal
autoheader
automake --add-missing --foreign --copy
+30 -28
View File
@@ -1,7 +1,8 @@
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.60)
AC_INIT(libminigui, 5.0.15)
AC_PREREQ([2.71])
AC_INIT([libminigui],[5.0.17])
AC_CONFIG_SRCDIR(src/main/main.c)
AC_CONFIG_MACRO_DIRS([m4])
dnl Set various version strings - taken gratefully from the SDL sources
#
@@ -16,9 +17,9 @@ dnl Set various version strings - taken gratefully from the SDL sources
#
MINIGUI_MAJOR_VERSION=5
MINIGUI_MINOR_VERSION=0
MINIGUI_MICRO_VERSION=15
MINIGUI_INTERFACE_AGE=0
MINIGUI_BINARY_AGE=0
MINIGUI_MICRO_VERSION=16
MINIGUI_INTERFACE_AGE=1
MINIGUI_BINARY_AGE=1
MINIGUI_VERSION=$MINIGUI_MAJOR_VERSION.$MINIGUI_MINOR_VERSION.$MINIGUI_MICRO_VERSION
AC_SUBST(MINIGUI_MAJOR_VERSION)
@@ -40,27 +41,24 @@ AC_SUBST(LT_CURRENT)
AC_SUBST(LT_REVISION)
AC_SUBST(LT_AGE)
AC_CANONICAL_SYSTEM
AC_CANONICAL_TARGET
dnl ========================================================================
dnl Init automake
AM_INIT_AUTOMAKE
AC_CONFIG_HEADER(mgconfig.h)
AC_CONFIG_HEADERS([mgconfig.h])
dnl ========================================================================
dnl Check for tools
AC_PROG_LIBTOOL
AC_PROG_CC
AC_PROG_LD
AC_PROG_RANLIB
AC_PROG_INSTALL
AC_PROG_LN_S
AC_PROG_MAKE_SET
LT_INIT
dnl ========================================================================
dnl Checks for header files.
AC_HEADER_STDC
AC_HEADER_DIRENT
AC_CHECK_HEADERS(limits.h math.h stdatomic.h locale.h unistd.h termio.h sys/types.h sys/time.h sys/select.h sys/memfd.h)
@@ -68,7 +66,6 @@ dnl Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_C_INLINE
AC_TYPE_SIZE_T
AC_HEADER_TIME
AC_STRUCT_TM
dnl Checks for library functions.
@@ -884,14 +881,14 @@ CheckFBCON()
if test "x$enable_video_fbcon" = "xyes"; then
AC_MSG_CHECKING(for FrameBuffer console support)
video_fbcon=no
AC_TRY_COMPILE([
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <linux/fb.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
],[
],[
]], [[
]])],[
video_fbcon=yes
])
],[])
AC_MSG_RESULT($video_fbcon)
if test "x$video_fbcon" = "xyes"; then
AC_DEFINE(_MGGAL_FBCON, 1,
@@ -899,9 +896,13 @@ CheckFBCON()
VIDEO_SUBDIRS="$VIDEO_SUBDIRS fbcon"
VIDEO_DRIVERS="$VIDEO_DRIVERS fbcon/libvideo_fbcon.la"
have_pciaccess="yes"
AC_CHECK_LIB(pciaccess, pci_system_init,
DEP_LIBS="$DEP_LIBS -lpciaccess", have_pciaccess="no")
if test "x$target_cpu" = "xi386"; then
have_pciaccess="yes"
AC_CHECK_LIB(pciaccess, pci_system_init,
DEP_LIBS="$DEP_LIBS -lpciaccess", have_pciaccess="no")
else
have_pciaccess="no"
fi
if test "x$have_pciaccess" = "xyes"; then
AC_DEFINE(_MGHAVE_PCIACCESS, 1,
@@ -1170,12 +1171,12 @@ CheckDirectFBVideo()
if test "x$enable_video_dfb" = "xyes"; then
AC_MSG_CHECKING(for DirectFB support)
video_dfb=no
AC_TRY_COMPILE([
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <directfb.h>
],[
],[
]], [[
]])],[
video_dfb=yes
])
],[])
AC_MSG_RESULT($video_dfb)
if test "x$video_dfb" = "xyes"; then
AC_DEFINE(_MGGAL_DFB, 1,
@@ -2641,7 +2642,7 @@ if test "x$devel_mode" = "xyes"; then
CPPFLAGS="$CPPFLAGS -D_DEBUG"
fi
if test "$ac_cv_prog_gcc" = "yes"; then
if test "$ac_cv_c_compiler_gnu" = "yes"; then
CFLAGS="$CFLAGS -Wall -Werror"
fi
@@ -2651,7 +2652,7 @@ else
CPPFLAGS="$CPPFLAGS -DNDEBUG"
fi
if test "$ac_cv_prog_gcc" = "yes"; then
if test "$ac_cv_c_compiler_gnu" = "yes"; then
CFLAGS="$CFLAGS -fmax-errors=10 -Wstrict-prototypes -pipe"
fi
@@ -2688,8 +2689,7 @@ AM_CONDITIONAL(MGLIB_MVFB, test "x$with_libsuffix" = "xmvfb")
AM_CONDITIONAL(MG_ENABLE_SPLASH, test "x$build_splash" = "xyes")
AM_CONDITIONAL(MG_ENABLE_SCREENSAVER, test "x$build_screensaver" = "xyes")
AC_OUTPUT(
minigui.pc
AC_CONFIG_FILES([minigui.pc
Makefile
cmake/Makefile
src/Makefile
@@ -2756,7 +2756,8 @@ etc/Makefile
3rd-party/scripteasy/Makefile
m4/Makefile
build/Makefile
)
])
AC_OUTPUT
CCVERSION=`$CC --version | head -n 1`
@@ -2765,6 +2766,7 @@ AC_MSG_NOTICE([
# Main building configuration:
## Global Features:
* Architecture: ${target_cpu}
* Operating system: ${osname}
* Runtime mode: ${runtime_mode}
* Virtual Window: ${virtual_window}
-5064
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
# This file does not do anything, except making sure these files are included
# when you run 'make dist'.
EXTRA_DIST = libtool.m4
EXTRA_DIST =
-3724
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -166,7 +166,7 @@ static unsigned short iso8859_68x_unicode_map [] =
0xFEB3, 0xFEB7, 0xFEBB, 0xFEBF, /*0xF1~0xF4*/
0xFECB, 0xFECF, 0xFED3, 0xFED7, /*0xF5~0xF8*/
0xFEDB, 0xFEDF, 0xFEE3, 0xFEE7, /*0xF9~0xFC*/
0xFEEB, 0xFEF4, 0xFFFF, /*0xFD~0xFF*/
0xFEEB, 0xFEF3, 0xFFFF, /*0xFD~0xFF*/
0xFEC4, 0xFEC6, 0xFE82, 0xFE84, /*0x100~0x103*/
0xFE86, 0xFE88, 0xFE8C, 0xFE8E, /*0x104~0x107*/
@@ -576,7 +576,7 @@ static BidiType __mg_iso8859_6_bidi_char_type_map[] = {
BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL,
BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL,
BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL,
BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_ON,
BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL,
/*0xf0~0xff*/
BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL, BIDI_TYPE_AL,
+110 -9
View File
@@ -286,7 +286,7 @@ static TYPERUN* get_runtype_link (const CHARSETOPS* charset_ops, Achar32* achars
last = list;
for (i = 0; i < len; i++){
if ((type = charset_ops->bidi_char_type (achars[i])) != last->type){
if ((type = charset_ops->bidi_char_type (REAL_ACHAR(achars[i]))) != last->type){
link = calloc(1, sizeof(TYPERUN));
link->type = type;
link->pos = i;
@@ -456,7 +456,7 @@ static void bidi_resolveWeak(TYPERUN **ptype_rl_list, Uint32 base_dir)
prev_type_org = TYPE(pp);
}
compact_neutrals (type_rl_list);
compact_list (type_rl_list);
#ifdef BIDI_DEBUG
print_run_types(type_rl_list);
@@ -469,19 +469,115 @@ static void bidi_resolveWeak(TYPERUN **ptype_rl_list, Uint32 base_dir)
#define BIDI_EMBEDDING_DIR(list) BIDI_LEVEL_TO_DIR(LEVEL(list))
/* 4.Resolving Neutral Types */
static void bidi_resolveNeutrals(TYPERUN **ptype_rl_list, Uint32 base_bir)
static void bidi_resolveNeutrals(const CHARSETOPS* charset_ops, Achar32* achars, int len,
TYPERUN **ptype_rl_list, Uint32 base_bir)
{
TYPERUN *type_rl_list = *ptype_rl_list, *pp = NULL;
Uint32* bracket_types = NULL;
int i;
if (charset_ops->bidi_mirror_char && achars && len > 0) {
typedef struct {
int pos;
Achar32 mirror;
} BRSTACK;
BRSTACK* stack = NULL;
int top = 0;
bracket_types = calloc(len, sizeof(Uint32));
stack = calloc(len, sizeof(BRSTACK));
if (bracket_types && stack) {
for (i = 0; i < len; i++) {
Achar32 ch = REAL_ACHAR(achars[i]);
Achar32 mirrored;
if (!charset_ops->bidi_mirror_char(ch, &mirrored))
continue;
if (ch < mirrored) {
stack[top].pos = i;
stack[top].mirror = mirrored;
top++;
}
else {
if (top > 0 && stack[top - 1].mirror == ch) {
int open_pos = stack[top - 1].pos;
int close_pos = i;
Uint32 found_dir = BIDI_TYPE_ON;
int k;
for (k = open_pos + 1; k < close_pos; k++) {
Uint32 t = charset_ops->bidi_char_type(REAL_ACHAR(achars[k]));
if (t == BIDI_TYPE_AL)
t = BIDI_TYPE_RTL;
if (BIDI_IS_LETTER(t)) {
found_dir = BIDI_IS_RTL(t) ? BIDI_TYPE_RTL : BIDI_TYPE_LTR;
break;
}
}
if (found_dir != BIDI_TYPE_ON) {
bracket_types[open_pos] = found_dir;
bracket_types[close_pos] = found_dir;
}
top--;
}
}
}
}
if (stack)
free(stack);
}
if (bracket_types) {
for (pp = type_rl_list->next; pp->next; pp = pp->next) {
if (BIDI_IS_NEUTRAL(TYPE(pp))) {
int start = POS(pp);
int end = start + LEN(pp);
Uint32 forced = BIDI_TYPE_ON;
if (end > len)
end = len;
for (i = start; i < end; i++) {
if (bracket_types[i] == BIDI_TYPE_RTL) {
forced = BIDI_TYPE_RTL;
break;
}
if (bracket_types[i] == BIDI_TYPE_LTR) {
forced = BIDI_TYPE_LTR;
}
}
if (forced != BIDI_TYPE_ON) {
TYPE(pp) = forced;
}
}
}
compact_list (type_rl_list);
}
DBGLOG ("\n4.Resolving neutral types\n");
for (pp = type_rl_list->next; pp->next; pp = pp->next)
{
Uint32 prev_type, this_type, next_type;
TYPERUN* prev = NULL;
TYPERUN* next = NULL;
prev = pp->prev;
while (prev && BIDI_IS_NEUTRAL(TYPE(prev)) && !BIDI_IS_SENTINEL(TYPE(prev)))
prev = prev->prev;
prev_type = (!prev || BIDI_IS_SENTINEL(TYPE(prev))) ? base_bir : TYPE(prev);
prev_type = TYPE(pp->prev);
this_type = TYPE(pp);
next_type = (pp->next) ? TYPE(pp->next) : this_type;
next = pp->next;
while (next && BIDI_IS_NEUTRAL(TYPE(next)) && !BIDI_IS_SENTINEL(TYPE(next)))
next = next->next;
next_type = (!next || BIDI_IS_SENTINEL(TYPE(next))) ? base_bir : TYPE(next);
/* "European and arabic numbers are treated as though they were R, NUMBER_TO_RTL does this. */
this_type = BIDI_NUMBER_TO_RTL(this_type);
prev_type = BIDI_NUMBER_TO_RTL(prev_type);
@@ -498,6 +594,9 @@ static void bidi_resolveNeutrals(TYPERUN **ptype_rl_list, Uint32 base_bir)
print_resolved_levels (type_rl_list);
print_resolved_types (type_rl_list);
#endif
if (bracket_types)
free(bracket_types);
}
/* 5.Resolving implicit levels. */
@@ -549,10 +648,13 @@ bidi_resolveMirrorChar (const CHARSETOPS* charset_ops, Achar32* achars, int len,
int i;
for (i = POS (pp); i < POS (pp) + LEN (pp); i++)
{
Achar32 ch = achars[i];
Achar32 real_ch = REAL_ACHAR(ch);
Achar32 mirrored_ch;
if (charset_ops->bidi_mirror_char &&
charset_ops->bidi_mirror_char (achars[i], &mirrored_ch))
achars[i] = mirrored_ch;
charset_ops->bidi_mirror_char (real_ch, &mirrored_ch)) {
achars[i] = IS_MBCHV(ch) ? SET_MBCHV(mirrored_ch) : mirrored_ch;
}
}
}
}
@@ -593,7 +695,7 @@ static void bidi_resolve_string (const CHARSETOPS* charset_ops,
bidi_resolveWeak(&type_rl_list, base_dir);
/* 4.Resolving Neutral Types */
bidi_resolveNeutrals(&type_rl_list, base_dir);
bidi_resolveNeutrals(charset_ops, achars, len, &type_rl_list, base_dir);
/* 5.Resolving implicit levels. */
*pmax_level = bidi_resolveImplicit(&type_rl_list, base_level);
@@ -826,4 +928,3 @@ Uint32 __mg_legacy_bidi_str_base_dir (const CHARSETOPS* charset_ops,
return base_dir;
}
+3 -4
View File
@@ -15,7 +15,7 @@
* and Graphics User Interface (GUI) support system for embedded systems
* and smart IoT devices.
*
* Copyright (C) 2002~2018, Beijing FMSoft Technologies Co., Ltd.
* Copyright (C) 2002~2026, Beijing FMSoft Technologies Co., Ltd.
* Copyright (C) 1998~2002, WEI Yongming
*
* This program is free software: you can redistribute it and/or modify
@@ -57,6 +57,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "common.h"
#include "minigui.h"
@@ -215,8 +216,6 @@ static unsigned char emojiSegmentationCategory (Uchar32 codepoint)
}
typedef BOOL bool;
enum { false = FALSE, true = TRUE };
typedef unsigned char *emoji_text_iter_t;
#include "emoji_presentation_scanner.inc"
@@ -251,7 +250,7 @@ EmojiIterator * __mg_emoji_iterator_init (EmojiIterator *iter,
BOOL __mg_emoji_iterator_next (EmojiIterator *iter)
{
unsigned int old_cursor, cursor;
BOOL is_emoji;
bool is_emoji;
if (iter->end >= iter->text_end)
return FALSE;
+23 -4
View File
@@ -55,6 +55,7 @@
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "common.h"
@@ -141,12 +142,31 @@ BOOL mg_InitDesktop (void)
#include "debug.h"
static IDLEHANDLER std_idle_handler;
/* According to a bug report, we use sem_timedwait() instead of
calling std_idle_handler for desktop thread.
Otherwise, the desktop might not handle the request from other GUI threads
as soon as possible. */
static BOOL idle_handler_for_desktop_thread (MSGQUEUE *msg_queue, BOOL wait)
{
int retv, n = 0;
__mg_update_tick_count (msg_queue);
return std_idle_handler (msg_queue, wait);
if (wait) {
struct timespec ts;
if (clock_gettime (CLOCK_REALTIME, &ts) != -1) {
ts.tv_nsec += 10 * 1000 * 1000L; // 10ms
if (ts.tv_nsec >= 1000 * 1000 * 1000L) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000 * 1000 * 1000L;
}
if (sem_timedwait (&msg_queue->wait, &ts) == 0)
return TRUE;
}
}
return n > 0;
}
void* __kernel_desktop_main (void* data)
@@ -162,7 +182,6 @@ void* __kernel_desktop_main (void* data)
/* For bug reported in Issue #116, under threads mode, the idle handler
for the desktop thread should call __mg_update_tick_count () */
std_idle_handler = __mg_dsk_msg_queue->OnIdle;
__mg_dsk_msg_queue->OnIdle = idle_handler_for_desktop_thread;
/* init desktop window */
+7 -7
View File
@@ -562,6 +562,13 @@ int GUIAPI InitGUI (int args, const char *agr[])
_is_minigui_running = 1;
/* init timer for tick counter */
step++;
if (!mg_InitTimer()) {
_ERR_PRINTF ("KERNEL>InitGUI: failed to start time counter\n");
goto failure;
}
step++;
if (!SystemThreads()) {
_ERR_PRINTF ("KERNEL>InitGUI: failed to init system threads!\n");
@@ -575,13 +582,6 @@ int GUIAPI InitGUI (int args, const char *agr[])
goto failure;
}
/* init timer for tick counter */
step++;
if (!mg_InitTimer()) {
_ERR_PRINTF ("KERNEL>InitGUI: failed to start time counter\n");
goto failure;
}
SetKeyboardLayout ("default");
SetCursor (GetSystemCursor (IDC_ARROW));
+5 -5
View File
@@ -100,14 +100,14 @@ DWORD __mg_update_tick_count (MSGQUEUE* msg_queue)
/* Since 5.0.0, the desktop only handles caret blinking in MSG_TIMEOUT
message, and the interval for the timer of desktop changes to 0.05s.
*/
if (msg_queue == __mg_dsk_msg_queue) {
if (ticks > __mg_dsk_msg_queue->last_ticks +
if (msg_queue == __mg_dsk_msg_queue &&
ticks > __mg_dsk_msg_queue->old_tick_count +
DESKTOP_TIMER_INERTVAL) {
__mg_dsk_msg_queue->dwState |= QS_DESKTIMER;
__mg_dsk_msg_queue->dwState |= QS_DESKTIMER;
#ifdef _MGRM_THREADS /* only wake up desktop for threads mode */
POST_MSGQ (__mg_dsk_msg_queue);
POST_MSGQ (__mg_dsk_msg_queue);
#endif
}
__mg_dsk_msg_queue->old_tick_count = ticks;
}
msg_queue->last_ticks = ticks;
-84
View File
@@ -495,28 +495,6 @@ DWORD __mg_os_get_time_ticks (void)
return (current - startup_time_win32) / 10;
}
#if 0 /* deprecated code */
DWORD __mg_os_get_elapsed_ms (void)
{
static DWORD last;
DWORD current = timeGetTime();
DWORD elapsed;
if (current >= last) {
elapsed = current - last;
return elapsed;
}
else {
/* overflowed */
elapsed = BITMASK_DWORD - last;
elapsed += current;
}
last = current;
return elapsed;
}
#endif /* deprecated code */
#elif defined (HAVE_CLOCK_GETTIME)
static struct timespec timeval_startup;
@@ -573,68 +551,6 @@ DWORD __mg_os_get_time_ticks (void)
return ds * 100 + dms;
}
#if 0 /* deprecated code */
DWORD __mg_os_get_elapsed_ms (void)
{
static struct timespec last;
DWORD ds, dms;
struct timespec current;
clock_gettime (CLOCK_MONOTONIC, &current);
ds = (current.tv_sec - last.tv_sec);
if (current.tv_sec == last.tv_sec) {
dms = (current.tv_nsec - last.tv_nsec) / 1000000L;
}
else if (current.tv_nsec >= last.tv_nsec) {
dms = (current.tv_nsec - last.tv_nsec) / 1000000L;
}
else {
assert (ds > 0);
ds--;
dms = 1000L - (last.tv_nsec - current.tv_nsec) / 1000000L;
}
last = current;
return ds * 1000 + dms;
}
#include <unistd.h>
#include <poll.h>
#include <sys/time.h>
static struct timeval timeval_startup;
void __mg_os_start_time(void)
{
gettimeofday(&timeval_startup, NULL);
}
DWORD __mg_os_get_time_ms(void)
{
DWORD ds, dms;
struct timeval current;
gettimeofday(&current, NULL);
ds = (current.tv_sec - timeval_startup.tv_sec);
if (current.tv_sec == timeval_startup.tv_sec) {
dms = (current.tv_usec - timeval_startup.tv_usec) / 1000L;
}
else if (current.tv_usec >= timeval_startup.tv_usec) {
dms = (current.tv_usec - timeval_startup.tv_usec) / 1000L;
}
else {
assert(ds > 0);
ds--;
dms = 1000L - (timeval_startup.tv_usec - current.tv_usec) / 1000L;
}
return ds * 1000 + dms;
}
#endif /* deprecated code */
#else /* defined HAVE_CLOCK_GETTIME */
#error "Please implement __mg_os_start_time and __mg_os_get_time_ticks for your OS"
+274 -41
View File
@@ -837,6 +837,9 @@ static DrmSurfaceBuffer *drm_create_dumb_buffer(DrmVideoData* vdata,
static void drm_destroy_dumb_buffer(DrmVideoData* vdata,
DrmSurfaceBuffer *surface_buffer);
static void drm_destroy_flip_buffer(DrmVideoData* vdata,
DrmSurfaceBuffer *flip_buffer);
/*
* The following helpers derived from DRM HOWTO by David Herrmann.
*
@@ -909,6 +912,12 @@ static void drm_cleanup(DrmVideoData* vdata)
}
#endif /* _MGSCHEMA_COMPOSITING */
/* Clean up flip buffer used for page flipping */
if (vdata->flip_buff) {
drm_destroy_flip_buffer(vdata, vdata->flip_buff);
vdata->flip_buff = NULL;
}
if (vdata->scanout_buff_id) {
/* remove frame buffer */
drmModeRmFB(vdata->dev_fd, vdata->scanout_buff_id);
@@ -940,12 +949,22 @@ static void drm_cleanup(DrmVideoData* vdata)
static void DRM_DeleteDevice(GAL_VideoDevice *device)
{
if (device->hidden->real_screen) {
/* FIXME */
DrmSurfaceBuffer *surface_buffer =
(DrmSurfaceBuffer *)device->hidden->real_screen->hwdata;
if (surface_buffer && !device->hidden->driver) {
drm_destroy_dumb_buffer(device->hidden, surface_buffer);
device->hidden->real_screen->hwdata = NULL;
}
GAL_FreeSurface(device->hidden->real_screen);
device->hidden->real_screen = NULL;
}
drm_cleanup(device->hidden);
if (device->hidden->dev_name)
free (device->hidden->dev_name);
@@ -1663,6 +1682,9 @@ static int DRM_VideoInit(_THIS, GAL_PixelFormat *vformat)
}
#endif
/* Initialize page flipping capability */
this->hidden->flip_buff = NULL;
if (this->hidden->driver) {
if (this->hidden->driver_ops->fill_rect) {
this->info.blit_fill = 1;
@@ -2169,6 +2191,50 @@ err_destroy:
return NULL;
}
/* Create flip buffer for page flipping */
static DrmSurfaceBuffer *drm_create_flip_buffer(DrmVideoData *vdata,
uint32_t drm_format, int width, int height)
{
DrmSurfaceBuffer *flip_buffer = NULL;
int ret;
uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
flip_buffer = drm_create_dumb_buffer(vdata, drm_format, 0, width, height);
if (flip_buffer == NULL) {
_ERR_PRINTF("NEWGAL>DRM: Failed to create flip buffer\n");
goto error_exit;
}
/* Add framebuffer */
if (flip_buffer->fb_id == 0) {
/* Setup handles, pitches and offsets for FB2 */
handles[0] = flip_buffer->handle;
pitches[0] = flip_buffer->pitch;
offsets[0] = flip_buffer->offset;
/* Use drmModeAddFB2 for better format support */
ret = drmModeAddFB2(vdata->dev_fd, flip_buffer->width,
flip_buffer->height, flip_buffer->drm_format,
handles, pitches, offsets, &flip_buffer->fb_id, 0);
if (ret) {
_ERR_PRINTF(
"NEWGAL>DRM: Failed to add framebuffer for flip buffer: %m\n");
goto error_cleanup;
}
}
_DBG_PRINTF("NEWGAL>DRM: Created flip buffer: fb_id(%u)\n",
flip_buffer->fb_id);
return flip_buffer;
error_cleanup:
drm_destroy_dumb_buffer(vdata, flip_buffer);
error_exit:
return NULL;
}
static DrmSurfaceBuffer *drm_create_dumb_buffer_from_handle(DrmVideoData* vdata,
uint32_t handle, size_t size)
{
@@ -2362,6 +2428,17 @@ static void drm_destroy_dumb_buffer(DrmVideoData* vdata,
free (surface_buffer);
}
/* Destroy flip buffer used for page flipping */
static void drm_destroy_flip_buffer(DrmVideoData* vdata,
DrmSurfaceBuffer *flip_buffer)
{
if (flip_buffer == NULL) {
return;
}
drm_destroy_dumb_buffer(vdata, flip_buffer);
}
static DrmModeInfo* find_mode(DrmVideoData* vdata, int width, int height)
{
DrmModeInfo *iter;
@@ -2393,18 +2470,71 @@ static double get_elapsed_seconds(const struct timespec *ts_from,
}
#endif
#if defined(__arm__) && defined(__linux__)
// ARM64 NEON accelerated memcpy implementation
static void *neon_memcpy(void *dst, const void *src, size_t n)
{
// Backup original pointer for return
void *ret = dst;
// Handle unaligned portion
uintptr_t dst_addr = (uintptr_t)dst;
size_t pre_align = (16 - (dst_addr & 15)) & 15;
if (pre_align > 0) {
if (pre_align > n) {
pre_align = n;
}
memcpy(dst, src, pre_align);
dst = (char *)dst + pre_align;
src = (char *)src + pre_align;
n -= pre_align;
}
// NEON vector copy main loop, copy 128 bytes each time
if (n >= 128) {
size_t count = n >> 7;
asm volatile("1:\n"
// Load 128 bytes into v0-v7 registers
"ld1 {v0.2d, v1.2d, v2.2d, v3.2d}, [%1], #64\n"
"ld1 {v4.2d, v5.2d, v6.2d, v7.2d}, [%1], #64\n"
// Store 128 bytes
"st1 {v0.2d, v1.2d, v2.2d, v3.2d}, [%0], #64\n"
"st1 {v4.2d, v5.2d, v6.2d, v7.2d}, [%0], #64\n"
"subs %2, %2, #1\n"
"b.ne 1b\n"
: "+r"(dst), "+r"(src), "+r"(count)
:
: "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6",
"v7");
n &= 127;
}
// Handle remaining bytes
if (n > 0) {
memcpy(dst, src, n);
}
return ret;
}
#endif /* defined(__arm__) && defined(__linux__) */
static void update_real_screen_memcpy(_THIS, const GAL_Rect *dirty_rect)
{
DrmSurfaceBuffer *real_buff, *shadow_buff;
real_buff = (DrmSurfaceBuffer *)this->hidden->real_screen->hwdata;
shadow_buff = (DrmSurfaceBuffer *)this->hidden->shadow_screen->hwdata;
DrmSurfaceBuffer *target_buff, *shadow_buff;
DrmVideoData *vdata = this->hidden;
shadow_buff = (DrmSurfaceBuffer *)vdata->shadow_screen->hwdata;
if (!vdata->flip_buff || (vdata->curr_buff == vdata->flip_buff)) {
target_buff = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
}
else {
target_buff = (DrmSurfaceBuffer *)vdata->flip_buff;
}
uint32_t i;
uint8_t *src, *dst;
int cpp = real_buff->cpp;
int shadow_pitch = this->hidden->shadow_screen->pitch;
int real_pitch = real_buff->pitch;
size_t count = cpp * dirty_rect->w;
int shadow_pitch = vdata->shadow_screen->pitch;
int target_pitch = target_buff->pitch;
if (shadow_buff) {
src = shadow_buff->vaddr;
@@ -2415,16 +2545,18 @@ static void update_real_screen_memcpy(_THIS, const GAL_Rect *dirty_rect)
src += this->hidden->shadow_screen->pixels_off;
}
src += shadow_pitch * dirty_rect->y + cpp * dirty_rect->x;
dst = target_buff->vaddr;
dst += target_buff->offset;
dst = real_buff->vaddr;
dst += real_pitch * dirty_rect->y + cpp * dirty_rect->x;
dst += real_buff->offset;
for (i = 0; i < dirty_rect->h; i++) {
memcpy(dst, src, count);
for (uint32_t i = 0; i < target_buff->height; i++) {
#if defined(__arm__) && defined(__linux__)
neon_memcpy(dst, src, target_pitch);
#else
memcpy(dst, src, target_pitch);
#endif /* defined(__arm__) && defined(__linux__) */
src += shadow_pitch;
dst += real_pitch;
dst += target_pitch;
}
}
@@ -2518,18 +2650,99 @@ static inline void refresh_cursor(_THIS, const GAL_Rect *dirty_rect) {
}
#endif /* _MGSCHEMA_COMPOSITING */
static int is_fd_readable(int fd)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Initialize the file descriptor set */
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
/* Set timeout to 0 to return immediately */
tv.tv_sec = 0;
tv.tv_usec = 0;
/* Call select to check if fd is readable */
retval = select(fd + 1, &rfds, NULL, NULL, &tv);
if (retval == -1) {
/* Error occurred */
return -1;
} else if (retval) {
/* File descriptor is readable */
return 1;
} else {
/* Timeout occurred, file descriptor is not readable */
return 0;
}
}
/* Perform page flip between flip_buffer and real_screen->hwdata using curr_buff */
static int drm_page_flip(DrmVideoData* vdata)
{
int ret;
/* Perform the page flip */
if (!vdata->flip_buff || !vdata->flip_buff->fb_id) {
_ERR_PRINTF("NEWGAL>DRM: No valid flip buffer for page flipping\n");
return -1;
}
/* Get the next buffer to display */
DrmSurfaceBuffer *next_buffer;
if (vdata->curr_buff == vdata->flip_buff) {
next_buffer = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
} else {
next_buffer = vdata->flip_buff;
}
uint32_t next_fb_id = next_buffer->fb_id;
/* drmModePageFlip will wait for vertical sync signal internally */
ret = drmModePageFlip(vdata->dev_fd, vdata->saved_crtc->crtc_id,
next_fb_id, DRM_MODE_PAGE_FLIP_EVENT, NULL);
if (ret) {
_ERR_PRINTF("NEWGAL>DRM: Failed to perform page flip: %m\n");
return -1;
}
/* Update scanout buffer ID */
vdata->scanout_buff_id = next_fb_id;
/* Update current buffer pointer */
vdata->curr_buff = next_buffer;
return 0;
}
static void update_real_screen_helper(_THIS, const GAL_Rect *dirty_rect)
{
DrmVideoData* vdata = this->hidden;
DrmSurfaceBuffer *real_buff, *shadow_buff;
DrmSurfaceBuffer *target_buff, *shadow_buff;
/* Initialize curr_buff if not set */
if (!vdata->curr_buff) {
vdata->curr_buff = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
}
/* Use the buffer that is not currently displayed as the target buffer */
if (vdata->flip_buff) {
target_buff = (vdata->curr_buff == vdata->flip_buff)
? (DrmSurfaceBuffer *)vdata->real_screen->hwdata
: vdata->flip_buff;
}
else {
target_buff = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
}
real_buff = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
shadow_buff = (DrmSurfaceBuffer *)vdata->shadow_screen->hwdata;
#if 0 // def _DEBUG
_DBG_PRINTF("Copy pixels to real screen (pitch: %u, count: %u, %d x %d)\n",
(unsigned)real_buff->pitch, (unsigned)count,
RECTWP(update_rect), RECTHP(update_rect));
_DBG_PRINTF("Copy pixels to target buffer (pitch: %u, %d x %d)\n",
(unsigned)target_buff->pitch,
dirty_rect->w, dirty_rect->h);
struct timespec ts_start;
clock_gettime(CLOCK_REALTIME, &ts_start);
@@ -2539,7 +2752,7 @@ static void update_real_screen_helper(_THIS, const GAL_Rect *dirty_rect)
if (shadow_buff && vdata->driver && vdata->driver_ops->copy_buff) {
if ((dirty_rect->w * dirty_rect->h) >= vdata->min_pixels_using_hwaccl) {
if (vdata->driver_ops->copy_buff(vdata->driver, shadow_buff,
dirty_rect, real_buff, dirty_rect,
dirty_rect, target_buff, dirty_rect,
BLIT_COPY_TRANSLATE) == 0) {
hw_ok = TRUE;
}
@@ -2569,6 +2782,8 @@ static void* task_do_update(void *data)
{
_THIS = data;
DrmVideoData* vdata = this->hidden;
drmEventContext ev = {};
ev.version = DRM_EVENT_CONTEXT_VERSION;
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)) {
goto error;
@@ -2627,20 +2842,18 @@ static void* task_do_update(void *data)
RECTH(vdata->update_rect) };
update_real_screen_helper(this, &dirty_rect);
/* Flush driver buffer if needed */
if (vdata->driver && vdata->driver_ops->flush) {
DrmSurfaceBuffer *real_buff;
real_buff = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
vdata->driver_ops->flush(vdata->driver,
real_buff, &dirty_rect);
vdata->curr_buff, &dirty_rect);
}
else if (vdata->dirty_fb_ok) {
drmModeClip clip = {
vdata->update_rect.left,
vdata->update_rect.top,
vdata->update_rect.right,
vdata->update_rect.bottom };
drmModeDirtyFB(vdata->dev_fd,
vdata->scanout_buff_id, &clip, 1);
/* Perform page flip */
else if (vdata->flip_buff) {
drm_page_flip(vdata);
if (1 == is_fd_readable(vdata->dev_fd)) {
drmHandleEvent(vdata->dev_fd, &ev);
}
}
SetRectEmpty(&vdata->update_rect);
@@ -2788,6 +3001,20 @@ static GAL_Surface *DRM_SetVideoMode(_THIS, GAL_Surface *current,
_ERR_PRINTF("NEWGAL>DRM: cannot setup scanout buffer\n");
goto error;
}
real_buffer->fb_id = vdata->scanout_buff_id;
/* Create flip buffer for page flipping */
if (!vdata->driver) {
vdata->flip_buff =
drm_create_flip_buffer(vdata, drm_format, info->width, info->height);
if (vdata->flip_buff == NULL) {
_ERR_PRINTF("NEWGAL>DRM: failed to create flip buffer\n");
goto error;
}
}
/* Initialize curr_buff to point to real_screen buffer */
vdata->curr_buff = real_buffer;
if (vdata->crtc_idx >= 0)
this->WaitVBlank = DRM_WaitVBlank;
@@ -2924,6 +3151,12 @@ error:
}
}
/* Clean up flip buffer if created */
if (vdata->flip_buff) {
drm_destroy_flip_buffer(vdata, vdata->flip_buff);
vdata->flip_buff = NULL;
}
if (vdata->real_screen) {
GAL_FreeSurface (vdata->real_screen);
vdata->real_screen = NULL;
@@ -3834,16 +4067,16 @@ static BOOL DRM_SyncUpdate(_THIS)
refresh_cursor(this, &dirty_rect);
if (vdata->driver && vdata->driver_ops->flush) {
DrmSurfaceBuffer *real_buff;
real_buff = (DrmSurfaceBuffer *)vdata->real_screen->hwdata;
vdata->driver_ops->flush(vdata->driver,
real_buff, &dirty_rect);
vdata->driver_ops->flush(vdata->driver, vdata->curr_buff, &dirty_rect);
}
else if (vdata->dirty_fb_ok) {
drmModeClip clip = { clipped.left, clipped.top,
clipped.right, clipped.bottom };
drmModeDirtyFB(vdata->dev_fd,
vdata->scanout_buff_id, &clip, 1);
else if (vdata->dirty_fb_ok && vdata->flip_buff) {
/* Perform page flip */
drm_page_flip(vdata);
if (1 == is_fd_readable(vdata->dev_fd)) {
drmEventContext ev = {};
ev.version = DRM_EVENT_CONTEXT_VERSION;
drmHandleEvent(vdata->dev_fd, &ev);
}
}
ret:
+6
View File
@@ -89,6 +89,12 @@ typedef struct GAL_PrivateVideoData {
uint32_t real_name, shadow_name;
#endif /* not defined _MGSCHEMA_COMPOSITING */
/* flip buffer used for page flipping */
DrmSurfaceBuffer *flip_buff;
/* current buffer pointer for managing buffer switching */
DrmSurfaceBuffer *curr_buff;
sem_t *update_lock;
#if !IS_SHAREDFB_SCHEMA_PROCS
+81 -9
View File
@@ -1092,6 +1092,67 @@ nodraw_ret:
}
#ifdef _MGFONT_FT2
static RGB blend_pixel(RGB font_pixel, RGB fg_color, RGB bg_color)
{
RGB result;
Uint8 src_pre_r, src_pre_g, src_pre_b;
Uint8 bg_pre_r, bg_pre_g, bg_pre_b, alpha_bg;
Uint8 out_r, out_g, out_b, alpha_out;
uint8_t alpha_font = 0.299 * font_pixel.r + 0.587 * font_pixel.g +
0.114 * font_pixel.b;
uint16_t alpha_src = (fg_color.a * alpha_font + 127) / 255;
if (alpha_src == 0) {
result = bg_color;
goto out;
}
src_pre_r = (fg_color.r * alpha_src + 127) / 255;
src_pre_g = (fg_color.g * alpha_src + 127) / 255;
src_pre_b = (fg_color.b * alpha_src + 127) / 255;
bg_pre_r = (bg_color.r * bg_color.a + 127) / 255;
bg_pre_g = (bg_color.g * bg_color.a + 127) / 255;
bg_pre_b = (bg_color.b * bg_color.a + 127) / 255;
alpha_bg = bg_color.a;
out_r = src_pre_r + (bg_pre_r * (255 - alpha_src)) / 255;
out_g = src_pre_g + (bg_pre_g * (255 - alpha_src)) / 255;
out_b = src_pre_b + (bg_pre_b * (255 - alpha_src)) / 255;
alpha_out = alpha_src + (alpha_bg * (255 - alpha_src)) / 255;
if (out_r > 255) {
out_r = 255;
}
if (out_g > 255) {
out_g = 255;
}
if (out_b > 255) {
out_b = 255;
}
if (alpha_out > 0) {
result.r = (out_r * 255 + alpha_out / 2) / alpha_out;
result.g = (out_g * 255 + alpha_out / 2) / alpha_out;
result.b = (out_b * 255 + alpha_out / 2) / alpha_out;
result.a = (uint8_t) alpha_out;
} else {
result.r = 0;
result.g = 0;
result.b = 0;
result.a = 0;
}
out:
return result;
}
static void _dc_ft2subpixel_scan_line(PDC pdc, int xpos, int ypos,
SCANLINE_CTXT* ctxt)
{
@@ -1112,8 +1173,8 @@ static void _dc_ft2subpixel_scan_line(PDC pdc, int xpos, int ypos,
ctxt->glyph_ascent, ctxt->glyph_line,
ctxt->glyph_advance);
GAL_GetRGB (pdc->textcolor, pdc->surface->format, &rgba_fg.r,
&rgba_fg.g, &rgba_fg.b);
GAL_GetRGBA (pdc->textcolor, pdc->surface->format, &rgba_fg.r,
&rgba_fg.g, &rgba_fg.b, &rgba_fg.a);
for (x = 0; x < ctxt->bmp_w; x++) {
if (!PtInRect (&pdc->rc_output, x+xpos, ypos)) {
@@ -1130,8 +1191,8 @@ static void _dc_ft2subpixel_scan_line(PDC pdc, int xpos, int ypos,
_glyph_move_to_pixel (pdc, x+xpos, ypos);
pixel = _mem_get_pixel(pdc->cur_dst, GAL_BytesPerPixel (pdc->surface));
GAL_GetRGB (pixel, pdc->surface->format, &rgba_cur.r,
&rgba_cur.g, &rgba_cur.b);
GAL_GetRGBA (pixel, pdc->surface->format, &rgba_cur.r,
&rgba_cur.g, &rgba_cur.b, &rgba_cur.a);
#define C_ALPHA(p, Cs, Cd) \
if (*p++) { \
@@ -1140,14 +1201,25 @@ static void _dc_ft2subpixel_scan_line(PDC pdc, int xpos, int ypos,
Cd = sub_val >> 8; \
}
C_ALPHA(bits, rgba_fg.r, rgba_cur.r);
C_ALPHA(bits, rgba_fg.g, rgba_cur.g);
C_ALPHA(bits, rgba_fg.b, rgba_cur.b);
if (rgba_cur.r == 0 && rgba_cur.g == 0 && rgba_cur.b == 0 &&
rgba_cur.a == 0) {
RGB font_pixel = {bits[0], bits[1], bits[2], 255};
RGB ret = blend_pixel(font_pixel, rgba_fg, rgba_cur);
rgba_cur = ret;
bits += 3;
}
else {
C_ALPHA(bits, rgba_fg.r, rgba_cur.r);
C_ALPHA(bits, rgba_fg.g, rgba_cur.g);
C_ALPHA(bits, rgba_fg.b, rgba_cur.b);
rgba_cur.a = 255.0;
}
#undef C_ALPHA
pdc->cur_pixel = GAL_MapRGB (pdc->surface->format, rgba_cur.r,
rgba_cur.g, rgba_cur.b);
pdc->cur_pixel = GAL_MapRGBA (pdc->surface->format, rgba_cur.r,
rgba_cur.g, rgba_cur.b, rgba_cur.a);
if (pdc->cur_pixel != pixel) {
_glyph_draw_pixel (pdc, x+xpos, ypos, pdc->cur_pixel);