--- tme/host/gtk/gtk-screen.c 2018/04/24 16:39:15 1.1 +++ tme/host/gtk/gtk-screen.c 2018/04/24 16:44:31 1.1.1.4 @@ -1,4 +1,4 @@ -/* $Id: gtk-screen.c,v 1.1 2018/04/24 16:39:15 root Exp $ */ +/* $Id: gtk-screen.c,v 1.1.1.4 2018/04/24 16:44:31 root Exp $ */ /* host/gtk/gtk-screen.c - GTK screen support: */ @@ -34,7 +34,7 @@ */ #include -_TME_RCSID("$Id: gtk-screen.c,v 1.1 2018/04/24 16:39:15 root Exp $"); +_TME_RCSID("$Id: gtk-screen.c,v 1.1.1.4 2018/04/24 16:44:31 root Exp $"); /* we are aware of the problems with gdk_image_new_bitmap, and we cope with them, so we define GDK_ENABLE_BROKEN to get its prototype @@ -89,6 +89,18 @@ _tme_gtk_screen_th_update(struct tme_gtk assert (rc == TME_OK); } + /* if this framebuffer needs a full redraw: */ + if (screen->tme_gtk_screen_full_redraw) { + + /* force the next translation to retranslate the entire buffer: */ + tme_fb_xlat_redraw(conn_fb_other); + conn_fb_other->tme_fb_connection_offset_updated_first = 0; + conn_fb_other->tme_fb_connection_offset_updated_last = 0 - (tme_uint32_t) 1; + + /* clear the full redraw flag: */ + screen->tme_gtk_screen_full_redraw = FALSE; + } + /* translate this framebuffer's contents: */ changed = (*screen->tme_gtk_screen_fb_xlat) (((struct tme_fb_connection *) @@ -114,13 +126,23 @@ _tme_gtk_screen_th_update(struct tme_gtk static unsigned int _tme_gtk_gdkimage_bipp(GdkImage *image) { - unsigned int bipp, total_bits_halved; + unsigned int bipp, total_bits; - total_bits_halved = image->bpl; - total_bits_halved = (total_bits_halved * 8) / 2; - for (bipp = image->depth; - (bipp * image->width) <= total_bits_halved; - bipp <<= 1); + /* if the bytes per pixel value is greater than one, or if the image + depth is 8 or greater, just convert the bytes per pixel value to + bits per pixel: */ + if (image->bpp > 1 + || image->depth >= 8) { + return (image->bpp * 8); + } + + /* otherwise, we know that the depth of the image is less than + eight, and the number of bits per pixel is eight or less: */ + total_bits = image->bpl; + total_bits *= 8; + for (bipp = 8; + bipp > image->depth && (bipp * image->width) > total_bits; + bipp >>= 1); return (bipp); } @@ -135,9 +157,7 @@ _tme_gtk_gdkimage_scanline_pad(GdkImage if ((image->bpl % sizeof(tme_uint16_t)) == 0) { return (16); } - if ((image->bpl % sizeof(tme_uint8_t)) == 0) { - return (8); - } + return (8); } /* this is called for a mode change: */ @@ -152,10 +172,22 @@ _tme_gtk_screen_mode_change(struct tme_f int scale; unsigned long fb_area, avail_area, percentage; gint width, height; + gint height_extra; + const void *map_g_old; + const void *map_r_old; + const void *map_b_old; + const tme_uint32_t *map_pixel_old; + tme_uint32_t map_pixel_count_old; + tme_uint32_t colorset; GdkImage *gdkimage; GdkVisual *visual; - int color_count, color_i; - GdkColor color; + tme_uint32_t color_count, color_i; + tme_uint32_t color_count_distinct; + tme_uint32_t color_j; + struct tme_fb_color *colors_tme; + GdkColor *colors_gdk; + gboolean *success; + gboolean warned_color_alloc; /* recover our data structures: */ display = conn_fb->tme_fb_connection.tme_connection_element->tme_element_private; @@ -204,26 +236,56 @@ _tme_gtk_screen_mode_change(struct tme_f /* get the system's default visual: */ visual = gdk_visual_get_system(); - /* create the new GdkImage for the screen: */ + /* get the required dimensions for the GdkImage: */ width = ((conn_fb_other->tme_fb_connection_width * scale) / TME_FB_XLAT_SCALE_NONE); height = ((conn_fb_other->tme_fb_connection_height * scale) / TME_FB_XLAT_SCALE_NONE); - gdkimage = gdk_image_new(GDK_IMAGE_FASTEST, - visual, - width, - height); - - /* set the new image on the image widget: */ - gtk_image_set(GTK_IMAGE(screen->tme_gtk_screen_gtkimage), - gdkimage, - NULL); - - /* destroy the previous gdkimage and remember the new one: */ - gdk_image_destroy(screen->tme_gtk_screen_gdkimage); - screen->tme_gtk_screen_gdkimage = gdkimage; + /* NB: we need to allocate an extra scanline's worth (or, if we're + doubling, an extra two scanlines' worth) of image, because the + framebuffer translation functions can sometimes overtranslate + (see the explanation of TME_FB_XLAT_RUN in fb-xlat-auto.sh): */ + height_extra + = (scale == TME_FB_XLAT_SCALE_DOUBLE + ? 2 + : 1); + + /* if the previous gdkimage isn't the right size: */ + gdkimage = screen->tme_gtk_screen_gdkimage; + if (gdkimage->width != width + || gdkimage->height != (height + height_extra)) { + + /* allocate a new gdkimage: */ + gdkimage = gdk_image_new(GDK_IMAGE_FASTEST, + visual, + width, + height + + height_extra); + + /* set the new image on the image widget: */ + gtk_image_set(GTK_IMAGE(screen->tme_gtk_screen_gtkimage), + gdkimage, + NULL); + + /* destroy the previous gdkimage and remember the new one: */ + gdk_image_destroy(screen->tme_gtk_screen_gdkimage); + screen->tme_gtk_screen_gdkimage = gdkimage; + } + + /* remember all previously allocated maps and colors, but otherwise + remove them from our framebuffer structure: */ + map_g_old = conn_fb->tme_fb_connection_map_g; + map_r_old = conn_fb->tme_fb_connection_map_r; + map_b_old = conn_fb->tme_fb_connection_map_b; + map_pixel_old = conn_fb->tme_fb_connection_map_pixel; + map_pixel_count_old = conn_fb->tme_fb_connection_map_pixel_count; + conn_fb->tme_fb_connection_map_g = NULL; + conn_fb->tme_fb_connection_map_r = NULL; + conn_fb->tme_fb_connection_map_b = NULL; + conn_fb->tme_fb_connection_map_pixel = NULL; + conn_fb->tme_fb_connection_map_pixel_count = 0; /* update our framebuffer connection: */ conn_fb->tme_fb_connection_width = width; @@ -236,48 +298,150 @@ _tme_gtk_screen_mode_change(struct tme_f ? TME_ENDIAN_LITTLE : TME_ENDIAN_BIG); conn_fb->tme_fb_connection_buffer = gdkimage->mem; - - /* when we're showing a framebuffer with a depth of one, we need to - provide the pixel values for black and white, and, if we're - halving, three shades of gray in between: */ - if (conn_fb->tme_fb_connection_depth1_map != NULL) { - - /* XXX we almost certainly should free colors - previously allocated: */ - tme_free(conn_fb->tme_fb_connection_depth1_map); - } - if (conn_fb_other->tme_fb_connection_depth == 1) { - - /* allocate the depth-one map: */ - color_count = (scale == TME_FB_XLAT_SCALE_HALF - ? 5 - : 2); - conn_fb->tme_fb_connection_depth1_map - = tme_new(tme_uint32_t, color_count); - - /* allocate the colors it needs: */ - for (color_i = 0; - color_i < color_count; - color_i++) { - - /* set the RGB values: */ - color.red - = (((65535UL * color_i) / (color_count - 1)) - ^ screen->tme_gtk_screen_mono_invert_mask); - color.green = color.red; - color.blue = color.red; - - /* allocate the color: */ - gdk_colormap_alloc_color(gdk_colormap_get_system(), - &color, - FALSE, - TRUE); - conn_fb->tme_fb_connection_depth1_map[color_i] - = color.pixel; - } + switch (visual->type) { + case GDK_VISUAL_STATIC_GRAY: + case GDK_VISUAL_GRAYSCALE: + conn_fb->tme_fb_connection_class = TME_FB_XLAT_CLASS_MONOCHROME; + break; + default: + assert(FALSE); + /* FALLTHROUGH */ + case GDK_VISUAL_STATIC_COLOR: + case GDK_VISUAL_PSEUDO_COLOR: + case GDK_VISUAL_DIRECT_COLOR: + case GDK_VISUAL_TRUE_COLOR: + conn_fb->tme_fb_connection_class = TME_FB_XLAT_CLASS_COLOR; + break; + } + switch (visual->type) { + case GDK_VISUAL_DIRECT_COLOR: + /* we set the primary maps to anything non-NULL, to indicate that + primaries are index mapped: */ + conn_fb->tme_fb_connection_map_g = conn_fb; + conn_fb->tme_fb_connection_map_r = conn_fb; + conn_fb->tme_fb_connection_map_b = conn_fb; + /* FALLTHROUGH */ + case GDK_VISUAL_TRUE_COLOR: + conn_fb->tme_fb_connection_mask_g = visual->green_mask; + conn_fb->tme_fb_connection_mask_r = visual->red_mask; + conn_fb->tme_fb_connection_mask_b = visual->blue_mask; + break; + default: + conn_fb->tme_fb_connection_mask_g = 0; + conn_fb->tme_fb_connection_mask_r = 0; + conn_fb->tme_fb_connection_mask_b = 0; + break; + } + + /* get the needed colors: */ + colorset = tme_fb_xlat_colors_get(conn_fb_other, scale, conn_fb, &colors_tme); + color_count = conn_fb->tme_fb_connection_map_pixel_count; + + /* if we need to allocate colors, but the colorset is not tied to + the source framebuffer characteristics, and is identical to the + currently allocated colorset, we can reuse the previously + allocated maps and colors: */ + if (color_count > 0 + && colorset != TME_FB_COLORSET_NONE + && colorset == screen->tme_gtk_screen_colorset) { + + /* free the requested color array: */ + tme_free(colors_tme); + + /* restore the previously allocated maps and colors: */ + conn_fb->tme_fb_connection_map_g = map_g_old; + conn_fb->tme_fb_connection_map_r = map_r_old; + conn_fb->tme_fb_connection_map_b = map_b_old; + conn_fb->tme_fb_connection_map_pixel = map_pixel_old; + conn_fb->tme_fb_connection_map_pixel_count = map_pixel_count_old; } + + /* otherwise, we may need to free and/or allocate colors: */ else { - conn_fb->tme_fb_connection_depth1_map = NULL; + + /* save the colorset signature: */ + screen->tme_gtk_screen_colorset = colorset; + + /* free any previously allocated maps and colors: */ + if (map_g_old != NULL) { + tme_free((void *) map_g_old); + } + if (map_r_old != NULL) { + tme_free((void *) map_r_old); + } + if (map_b_old != NULL) { + tme_free((void *) map_b_old); + } + if (map_pixel_old != NULL) { + + /* recreate the array of GdkColor: */ + colors_gdk = tme_new(GdkColor, map_pixel_count_old); + color_i = 0; + do { + colors_gdk[color_i].pixel = map_pixel_old[color_i]; + } while (++color_i < map_pixel_count_old); + + /* free the colors: */ + gdk_colormap_free_colors(gdk_colormap_get_system(), + colors_gdk, + map_pixel_count_old); + tme_free(colors_gdk); + tme_free((void *) map_pixel_old); + } + + /* if we need to allocate colors: */ + if (color_count > 0) { + + /* make the GdkColor array, and count the number of distinct colors: */ + colors_gdk = tme_new(GdkColor, color_count * 2); + color_count_distinct = 0; + for (color_i = 0; color_i < color_count; color_i++) { + color_j = colors_tme[color_i].tme_fb_color_pixel; + colors_gdk[color_j].green = colors_tme[color_i].tme_fb_color_value_g; + colors_gdk[color_j].red = colors_tme[color_i].tme_fb_color_value_r; + colors_gdk[color_j].blue = colors_tme[color_i].tme_fb_color_value_b; + if (color_j >= color_count_distinct) { + color_count_distinct = color_j + 1; + } + } + success = tme_new(gboolean, color_count_distinct); + + /* allocate exact matches for as many colors as possible: */ + gdk_colormap_alloc_colors(gdk_colormap_get_system(), + colors_gdk, + color_count_distinct, + FALSE, + FALSE, + success); + + /* allocate read-only best matches for any colors we failed to + allocate exactly: */ + warned_color_alloc = FALSE; + for (color_i = 0; color_i < color_count; color_i++) { + color_j = colors_tme[color_i].tme_fb_color_pixel; + if (!success[color_j]) { + if (!gdk_colormap_alloc_color(gdk_colormap_get_system(), + &colors_gdk[color_j], + FALSE, + TRUE)) { + if (!warned_color_alloc) { + warned_color_alloc = TRUE; + tme_log(&display->tme_gtk_display_element->tme_element_log_handle, 0, ENOMEM, + (&display->tme_gtk_display_element->tme_element_log_handle, + _("could not allocate all colors"))); + } + } + } + colors_tme[color_i].tme_fb_color_pixel = colors_gdk[color_j].pixel; + } + + /* free the arrays used with gdk_colormap_alloc_colors(): */ + tme_free(success); + tme_free(colors_gdk); + + /* set the needed colors: */ + tme_fb_xlat_colors_set(conn_fb_other, scale, conn_fb, colors_tme); + } } /* compose the framebuffer translation question: */ @@ -289,11 +453,25 @@ _tme_gtk_screen_mode_change(struct tme_f fb_xlat_q.tme_fb_xlat_src_skipx = conn_fb_other->tme_fb_connection_skipx; fb_xlat_q.tme_fb_xlat_src_scanline_pad = conn_fb_other->tme_fb_connection_scanline_pad; fb_xlat_q.tme_fb_xlat_src_order = conn_fb_other->tme_fb_connection_order; + fb_xlat_q.tme_fb_xlat_src_class = conn_fb_other->tme_fb_connection_class; + fb_xlat_q.tme_fb_xlat_src_map = (conn_fb_other->tme_fb_connection_map_g != NULL + ? TME_FB_XLAT_MAP_INDEX + : TME_FB_XLAT_MAP_LINEAR); + fb_xlat_q.tme_fb_xlat_src_map_bits = conn_fb_other->tme_fb_connection_map_bits; + fb_xlat_q.tme_fb_xlat_src_mask_g = conn_fb_other->tme_fb_connection_mask_g; + fb_xlat_q.tme_fb_xlat_src_mask_r = conn_fb_other->tme_fb_connection_mask_r; + fb_xlat_q.tme_fb_xlat_src_mask_b = conn_fb_other->tme_fb_connection_mask_b; fb_xlat_q.tme_fb_xlat_dst_depth = conn_fb->tme_fb_connection_depth; fb_xlat_q.tme_fb_xlat_dst_bits_per_pixel = conn_fb->tme_fb_connection_bits_per_pixel; fb_xlat_q.tme_fb_xlat_dst_skipx = conn_fb->tme_fb_connection_skipx; fb_xlat_q.tme_fb_xlat_dst_scanline_pad = conn_fb->tme_fb_connection_scanline_pad; fb_xlat_q.tme_fb_xlat_dst_order = conn_fb->tme_fb_connection_order; + fb_xlat_q.tme_fb_xlat_dst_map = (conn_fb->tme_fb_connection_map_g != NULL + ? TME_FB_XLAT_MAP_INDEX + : TME_FB_XLAT_MAP_LINEAR); + fb_xlat_q.tme_fb_xlat_dst_mask_g = conn_fb->tme_fb_connection_mask_g; + fb_xlat_q.tme_fb_xlat_dst_mask_r = conn_fb->tme_fb_connection_mask_r; + fb_xlat_q.tme_fb_xlat_dst_mask_b = conn_fb->tme_fb_connection_mask_b; /* ask the framebuffer translation question: */ fb_xlat_a = tme_fb_xlat_best(&fb_xlat_q); @@ -309,7 +487,7 @@ _tme_gtk_screen_mode_change(struct tme_f screen->tme_gtk_screen_fb_xlat = fb_xlat_a->tme_fb_xlat_func; /* force the next translation to do a complete redraw: */ - tme_fb_xlat_redraw(conn_fb_other); + screen->tme_gtk_screen_full_redraw = TRUE; /* unlock our mutex: */ tme_mutex_unlock(&display->tme_gtk_display_mutex); @@ -397,6 +575,36 @@ _tme_gtk_screen_scale_double(GtkWidget * TME_FB_XLAT_SCALE_DOUBLE); } +/* this creates the Screen scaling submenu: */ +static GtkSignalFunc +_tme_gtk_screen_submenu_scaling(void *_screen, + struct tme_gtk_display_menu_item *menu_item) +{ + struct tme_gtk_screen *screen; + + screen = (struct tme_gtk_screen *) _screen; + menu_item->tme_gtk_display_menu_item_widget = NULL; + switch (menu_item->tme_gtk_display_menu_item_which) { + case 0: + menu_item->tme_gtk_display_menu_item_string = _("Default"); + menu_item->tme_gtk_display_menu_item_widget = &screen->tme_gtk_screen_scale_default; + return (GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_default)); + case 1: + menu_item->tme_gtk_display_menu_item_string = _("Half"); + menu_item->tme_gtk_display_menu_item_widget = &screen->tme_gtk_screen_scale_half; + return (GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_half)); + case 2: + menu_item->tme_gtk_display_menu_item_string = _("Full"); + return (GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_none)); + case 3: + menu_item->tme_gtk_display_menu_item_string = _("Double"); + return (GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_double)); + default: + break; + } + return (NULL); +} + /* this makes a new screen: */ struct tme_gtk_screen * _tme_gtk_screen_new(struct tme_gtk_display *display) @@ -406,13 +614,8 @@ _tme_gtk_screen_new(struct tme_gtk_displ GtkWidget *menu; GtkWidget *submenu; GtkWidget *menu_item; - GtkWidget **_menu_item; - GSList *menu_group; tme_uint8_t *bitmap_data; unsigned int y; - const char *menu_label; - GtkSignalFunc menu_func; - int i; #define BLANK_SIDE (16 * 8) /* create the new screen and link it in: */ @@ -431,15 +634,15 @@ _tme_gtk_screen_new(struct tme_gtk_displ screen->tme_gtk_screen_fb_scale = -TME_FB_XLAT_SCALE_NONE; - /* XXX this should be controlled by an argument somewhere: */ - screen->tme_gtk_screen_mono_invert_mask = 0xffff; + /* we have no colorset: */ + screen->tme_gtk_screen_colorset = TME_FB_COLORSET_NONE; /* create the top-level window, and allow it to shrink, grow, and auto-shrink: */ screen->tme_gtk_screen_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_policy(GTK_WINDOW(screen->tme_gtk_screen_window), - TRUE, TRUE, TRUE); + FALSE, FALSE, TRUE); /* create the outer vertical packing box: */ screen->tme_gtk_screen_vbox0 @@ -461,49 +664,9 @@ _tme_gtk_screen_new(struct tme_gtk_displ menu = gtk_menu_new(); /* create the Screen scaling submenu: */ - submenu = gtk_menu_new(); - - /* create the Screen scaling submenu options: */ - menu_group = NULL; - for (i = 0;; i++) { - if (i == 0) { - menu_label = _("Default"); - _menu_item = &screen->tme_gtk_screen_scale_default; - menu_func = GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_default); - } - else if (i == 1) { - menu_label = _("Half"); - _menu_item = &screen->tme_gtk_screen_scale_half; - menu_func = GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_half); - } - else if (i == 2) { - menu_label = _("Full"); - _menu_item = NULL; - menu_func = GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_none); - } - else if (i == 3) { - menu_label = _("Double"); - _menu_item = NULL; - menu_func = GTK_SIGNAL_FUNC(_tme_gtk_screen_scale_double); - } - else { - break; - } - menu_item - = gtk_radio_menu_item_new_with_label(menu_group, - menu_label); - if (_menu_item != NULL) { - *_menu_item = menu_item; - } - menu_group - = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(menu_item)); - gtk_signal_connect(GTK_OBJECT(menu_item), - "activate", - menu_func, - (gpointer) screen); - gtk_menu_append(GTK_MENU(submenu), menu_item); - gtk_widget_show(menu_item); - } + submenu + = _tme_gtk_display_menu_radio(screen, + _tme_gtk_screen_submenu_scaling); /* create the Screen scaling submenu item: */ menu_item = gtk_menu_item_new_with_label(_("Scale"));