[GTK3] Convert VRAM viewer into proper GTK widget

This commit is contained in:
Maximilian Mader 2020-05-17 05:33:41 +02:00
parent accaedbdac
commit 825786210a
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
7 changed files with 1142 additions and 999 deletions

View File

@ -14,6 +14,8 @@
#include "shader.h" #include "shader.h"
#include "check_menu_radio_group.h" #include "check_menu_radio_group.h"
#include "vram_viewer.h"
// used for audio and game controllers // used for audio and game controllers
#include "SDL.h" #include "SDL.h"
#include "../SDL/audio/audio.h" #include "../SDL/audio/audio.h"
@ -53,11 +55,6 @@ static GuiData gui_data = {
.log_to_sidebar = false, .log_to_sidebar = false,
.should_clear_sidebar = false, .should_clear_sidebar = false,
.vram_viewer_visible = false,
.vram_viewer_updating = false,
.vram_viewer_active_tab = "",
.vram_viewer_is_cgb = false,
.audio_initialized = false, .audio_initialized = false,
.border_mode_changed = false, .border_mode_changed = false,
@ -118,34 +115,6 @@ static const GActionEntry app_entries[] = {
{ "toggle_mute", NULL, NULL, "false", on_mute_changed }, { "toggle_mute", NULL, NULL, "false", on_mute_changed },
}; };
static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data_ptr) {
const gchar *title = gtk_tree_view_column_get_title(col);
const uint8_t color_index = g_ascii_strtoll(&title[6], NULL, 10);
const uint8_t column_index = 2 + (2 * color_index);
GValue color_val = G_VALUE_INIT;
gtk_tree_model_get_value(model, iter, column_index, &color_val);
gint color = g_value_get_int(&color_val);
gchar *color_string = g_strdup_printf("#%06x", color);
gint lightness = 0.299 * ((color >> 16) & 0xFF) + 0.587 * ((color >> 8) & 0xFF) + 0.114 * (color & 0xFF);
GValue color_str = G_VALUE_INIT;
g_value_init(&color_str, G_TYPE_STRING);
g_value_set_string(&color_str, color_string);
g_object_set_property(G_OBJECT(renderer), "background", &color_str);
GValue fg_color_str = G_VALUE_INIT;
g_value_init(&fg_color_str, G_TYPE_STRING);
g_value_set_static_string(&fg_color_str, (lightness > 0x7F)? "#000000" : "#FFFFFF");
g_object_set_property(G_OBJECT(renderer), "foreground", &fg_color_str);
g_value_unset(&color_val);
g_value_unset(&color_str);
g_value_unset(&fg_color_str);
g_free(color_string);
}
static const char* get_sdl_joystick_power_level_name(SDL_JoystickPowerLevel level) { static const char* get_sdl_joystick_power_level_name(SDL_JoystickPowerLevel level) {
switch (level) { switch (level) {
case SDL_JOYSTICK_POWER_EMPTY: return "Empty"; case SDL_JOYSTICK_POWER_EMPTY: return "Empty";
@ -718,17 +687,20 @@ static void update_viewport(void) {
} }
} }
unsigned new_width = x_factor * GB_get_screen_width(&gb);
unsigned new_height = y_factor * GB_get_screen_height(&gb);
gui_data.viewport = (Rect){ if (gui_data.gl_area) {
(win_width - new_width) / 2, unsigned new_width = x_factor * GB_get_screen_width(&gb);
(win_height - new_height) / 2, unsigned new_height = y_factor * GB_get_screen_height(&gb);
new_width,
new_height
};
if (!gui_data.fallback_canvas) glViewport(gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.w, gui_data.viewport.h); gui_data.viewport = (Rect){
(win_width - new_width) / 2,
(win_height - new_height) / 2,
new_width,
new_height
};
glViewport(gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.width, gui_data.viewport.height);
}
} }
// WHY DO WE NEED SUCH AN UGLY METHOD, GTK?! // WHY DO WE NEED SUCH AN UGLY METHOD, GTK?!
@ -789,8 +761,6 @@ static void update_window_geometry(void) {
if (GB_is_inited(&gb)) { if (GB_is_inited(&gb)) {
GB_set_pixels_output(&gb, get_pixels()); GB_set_pixels_output(&gb, get_pixels());
} }
update_viewport();
} }
static void stop(void) { static void stop(void) {
@ -811,140 +781,7 @@ static void stop(void) {
gui_data.stopped = true; gui_data.stopped = true;
} }
static void on_vblank(gpointer data) { static void on_vblank(GB_gameboy_t *gb) {
if (!gui_data.vram_viewer_updating && gui_data.vram_viewer_visible) {
gui_data.vram_viewer_updating = true;
if (g_strcmp0("vram_viewer_sprites", gui_data.vram_viewer_active_tab) == 0) {
GtkTreeIter iter;
GtkTreeView *tree_view = builder_get(GTK_TREE_VIEW, "vram_viewer_sprites");
// gtk_tree_view_set_model(tree_view, NULL); // Do we need this?
GtkListStore *store = gtk_list_store_new(7,
GDK_TYPE_PIXBUF, // Preview image
G_TYPE_STRING, // X position
G_TYPE_STRING, // Y position
G_TYPE_STRING, // Tile
G_TYPE_STRING, // Tile Address
G_TYPE_STRING, // OAM Address
G_TYPE_STRING // Attributes
);
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
for (unsigned row = 0; row < gui_data.oam_count; ++row) {
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_bytes(
g_bytes_new(gui_data.oam_info[row].image, 128 * sizeof(uint32_t)),
GDK_COLORSPACE_RGB, true, 8, 8, gui_data.oam_height, 8 * sizeof(uint32_t)
);
GdkPixbuf *dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 8 * 2, gui_data.oam_height * 2);
gdk_pixbuf_scale(pixbuf, dest,
0, 0, 8 * 2, gui_data.oam_height * 2,
0, 0, 2.0, 2.0,
GDK_INTERP_NEAREST
);
gtk_list_store_insert_with_values(store, &iter, -1,
0, dest,
1, g_strdup_printf("%i", gui_data.oam_info[row].x - 8),
2, g_strdup_printf("%i", gui_data.oam_info[row].y - 16),
3, g_strdup_printf("$%02x", gui_data.oam_info[row].tile),
4, g_strdup_printf("$%04x", 0x8000 + gui_data.oam_info[row].tile * 0x10),
5, g_strdup_printf("$%04x", gui_data.oam_info[row].oam_addr),
6, gui_data.vram_viewer_is_cgb
? g_strdup_printf("%c%c%c%d%d",
gui_data.oam_info[row].flags & 0x80? 'P' : '-',
gui_data.oam_info[row].flags & 0x40? 'Y' : '-',
gui_data.oam_info[row].flags & 0x20? 'X' : '-',
gui_data.oam_info[row].flags & 0x08? 1 : 0,
gui_data.oam_info[row].flags & 0x07)
: g_strdup_printf("%c%c%c%d",
gui_data.oam_info[row].flags & 0x80? 'P' : '-',
gui_data.oam_info[row].flags & 0x40? 'Y' : '-',
gui_data.oam_info[row].flags & 0x20? 'X' : '-',
gui_data.oam_info[row].flags & 0x10? 1 : 0),
-1
);
g_object_unref(pixbuf);
g_object_unref(dest);
}
gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(store));
g_object_unref(store);
}
else if (g_strcmp0("vram_viewer_palettes", gui_data.vram_viewer_active_tab) == 0) {
GtkTreeIter iter;
GtkTreeView *tree_view = builder_get(GTK_TREE_VIEW, "vram_viewer_palettes");
// gtk_tree_view_set_model(tree_view, NULL); // Do we need this?
GtkListStore *store = gtk_list_store_new(9,
G_TYPE_STRING, // Name
G_TYPE_STRING, // Color 0 string
G_TYPE_INT, // Color 0 integer
G_TYPE_STRING, // Color 1 string
G_TYPE_INT, // Color 1 integer
G_TYPE_STRING, // Color 2 string
G_TYPE_INT, // Color 2 integer
G_TYPE_STRING, // Color 3 string
G_TYPE_INT // Color 3 integer
);
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
for (unsigned row = 0; row < 16; ++row) {
uint8_t offset = (row & 7) * 4;
uint16_t color_0 = (gui_data.vram_viewer_palette_data[row][((0 + offset) << 1) + 1] << 8) | gui_data.vram_viewer_palette_data[row][((0 + offset) << 1)];
uint16_t color_1 = (gui_data.vram_viewer_palette_data[row][((1 + offset) << 1) + 1] << 8) | gui_data.vram_viewer_palette_data[row][((1 + offset) << 1)];
uint16_t color_2 = (gui_data.vram_viewer_palette_data[row][((2 + offset) << 1) + 1] << 8) | gui_data.vram_viewer_palette_data[row][((2 + offset) << 1)];
uint16_t color_3 = (gui_data.vram_viewer_palette_data[row][((3 + offset) << 1) + 1] << 8) | gui_data.vram_viewer_palette_data[row][((3 + offset) << 1)];
gtk_list_store_insert_with_values(store, &iter, -1,
0, g_strdup_printf("%s %d", row >=8 ? "Object" : "Background", row & 7),
1, g_strdup_printf("$%04x", color_0 & 0x7FFF),
2, convert_color(color_0),
3, g_strdup_printf("$%04x", color_1 & 0x7FFF),
4, convert_color(color_1),
5, g_strdup_printf("$%04x", color_2 & 0x7FFF),
6, convert_color(color_2),
7, g_strdup_printf("$%04x", color_3 & 0x7FFF),
8, convert_color(color_3),
-1
);
}
GtkTreeViewColumn *column_0 = gtk_tree_view_get_column(tree_view, 1);
GtkTreeViewColumn *column_1 = gtk_tree_view_get_column(tree_view, 2);
GtkTreeViewColumn *column_2 = gtk_tree_view_get_column(tree_view, 3);
GtkTreeViewColumn *column_3 = gtk_tree_view_get_column(tree_view, 4);
GtkCellRendererText *cell_renderer_0 = builder_get(GTK_CELL_RENDERER_TEXT, "vram_viewer_palette_cell_renderer_0");
GtkCellRendererText *cell_renderer_1 = builder_get(GTK_CELL_RENDERER_TEXT, "vram_viewer_palette_cell_renderer_1");
GtkCellRendererText *cell_renderer_2 = builder_get(GTK_CELL_RENDERER_TEXT, "vram_viewer_palette_cell_renderer_2");
GtkCellRendererText *cell_renderer_3 = builder_get(GTK_CELL_RENDERER_TEXT, "vram_viewer_palette_cell_renderer_3");
gtk_tree_view_column_set_cell_data_func(column_0, GTK_CELL_RENDERER(cell_renderer_0), palette_color_data_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func(column_1, GTK_CELL_RENDERER(cell_renderer_1), palette_color_data_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func(column_2, GTK_CELL_RENDERER(cell_renderer_2), palette_color_data_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func(column_3, GTK_CELL_RENDERER(cell_renderer_3), palette_color_data_func, NULL, NULL);
gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(store));
g_object_unref(store);
}
// Queue a redraw of the VRAM viewer
gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer));
gui_data.vram_viewer_updating = false;
}
// Queue drawing of the current frame // Queue drawing of the current frame
if (gui_data.fallback_canvas) { if (gui_data.fallback_canvas) {
gtk_widget_queue_draw(GTK_WIDGET(gui_data.main_window)); gtk_widget_queue_draw(GTK_WIDGET(gui_data.main_window));
@ -952,6 +789,8 @@ static void on_vblank(gpointer data) {
else if (gui_data.gl_area) { else if (gui_data.gl_area) {
gtk_gl_area_queue_render(gui_data.gl_area); gtk_gl_area_queue_render(gui_data.gl_area);
} }
gtk_widget_queue_draw(GTK_WIDGET(gui_data.vram_viewer));
} }
static void vblank(GB_gameboy_t *gb) { static void vblank(GB_gameboy_t *gb) {
@ -987,68 +826,11 @@ static void vblank(GB_gameboy_t *gb) {
} }
} }
if (g_strcmp0("vram_viewer_tileset", gui_data.vram_viewer_active_tab) == 0) {
const gchar *palette_id = gtk_combo_box_get_active_id(builder_get(GTK_COMBO_BOX, "vram_viewer_tileset_palette_selector"));
GB_palette_type_t palette_type = g_str_has_prefix(palette_id, "bg")? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM;
uint8_t palette_index = g_ascii_digit_value(palette_id[palette_type == GB_PALETTE_OAM ? 3 : 2]);
GB_draw_tileset(gb, gui_data.tileset_buffer,
palette_type,
palette_index
);
}
else if (g_strcmp0("vram_viewer_tilemap", gui_data.vram_viewer_active_tab) == 0) {
const gchar *palette_id = gtk_combo_box_get_active_id(builder_get(GTK_COMBO_BOX, "vram_viewer_tilemap_palette_selector"));
uint8_t palette_index = 0;
GB_palette_type_t palette_type = GB_PALETTE_AUTO;
if (g_strcmp0("auto", palette_id) != 0) {
palette_type = g_str_has_prefix(palette_id, "bg")? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM;
palette_index = g_ascii_digit_value(palette_id[palette_type == GB_PALETTE_OAM ? 3 : 2]);
}
GB_map_type_t map_type = GB_MAP_AUTO;
const gchar *map_type_id = gtk_combo_box_get_active_id(builder_get(GTK_COMBO_BOX, "vram_viewer_tilemap_tilemap_selector"));
if (g_strcmp0("auto", map_type_id) != 0) {
map_type = (g_strcmp0("9800", map_type_id) == 0)? GB_MAP_9800 : GB_MAP_9C00;
}
GB_tileset_type_t tileset_type = GB_TILESET_AUTO;
const gchar *tileset_type_id = gtk_combo_box_get_active_id(builder_get(GTK_COMBO_BOX, "vram_viewer_tilemap_tileset_selector"));
if (g_strcmp0("auto", tileset_type_id) != 0) {
tileset_type = (g_strcmp0("8800", tileset_type_id) == 0)? GB_TILESET_8800 : GB_TILESET_8000;
}
GB_draw_tilemap(gb, gui_data.tilemap_buffer,
palette_type,
palette_index,
map_type,
tileset_type
);
gui_data.scroll_rect = (Rect){
GB_read_memory(gb, 0xFF00 | GB_IO_SCX),
GB_read_memory(gb, 0xFF00 | GB_IO_SCY),
160, 144
};
}
else if (g_strcmp0("vram_viewer_sprites", gui_data.vram_viewer_active_tab) == 0) {
gui_data.oam_count = GB_get_oam_info(gb, gui_data.oam_info, &gui_data.oam_height);
gui_data.vram_viewer_is_cgb = GB_is_cgb(gb);
}
else if (g_strcmp0("vram_viewer_palettes", gui_data.vram_viewer_active_tab) == 0) {
size_t size;
for (unsigned row = 0; row < 16; ++row) {
uint8_t *palette_data = GB_get_direct_access(gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, &size, NULL);
memcpy(gui_data.vram_viewer_palette_data[row], palette_data, size);
}
}
gui_data.do_rewind = gui_data.rewind_down; gui_data.do_rewind = gui_data.rewind_down;
g_idle_add((GSourceFunc) on_vblank, NULL); vram_viewer_update(gui_data.vram_viewer, gb);
g_idle_add((GSourceFunc) on_vblank, gb);
} }
static void handle_events(GB_gameboy_t *gb) { static void handle_events(GB_gameboy_t *gb) {
@ -1402,7 +1184,6 @@ static void quit(void) {
g_debug("quit(void);"); g_debug("quit(void);");
stop(); stop();
while (gui_data.stopping);
GtkWindow *window = gui_data.main_window ? GTK_WINDOW(gui_data.main_window) : NULL; GtkWindow *window = gui_data.main_window ? GTK_WINDOW(gui_data.main_window) : NULL;
save_config(window, gui_data.config_modification_date); save_config(window, gui_data.config_modification_date);
@ -1496,249 +1277,6 @@ static void on_window_state_change(GtkWidget *w, GdkEventWindowState *event, gpo
gui_data.is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; gui_data.is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
} }
// Gets called when the VRAM viewer gets realized
static void on_vram_viewer_realize(void) {
gui_data.vram_viewer_visible = true;
gui_data.vram_viewer_active_tab = (gchar *)gtk_stack_get_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"));
}
// Gets called when the VRAM viewer gets unrealized
static void on_vram_viewer_unrealize(void) {
gui_data.vram_viewer_visible = false;
}
// Gets called when the tileset viewer should be redrawn
static gboolean on_draw_vram_viewer_tileset(GtkWidget *widget, cairo_t *cr, gpointer data) {
g_mutex_lock(&gui_data.tileset_buffer_mutex);
guint width, height;
GtkStyleContext *context;
context = gtk_widget_get_style_context(widget);
width = gtk_widget_get_allocated_width(widget);
height = gtk_widget_get_allocated_height(widget);
gtk_render_background(context, cr, 0, 0, width, height);
cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) gui_data.tileset_buffer,
CAIRO_FORMAT_RGB24,
256,
192,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 256)
);
cairo_scale(cr, 2.0, 2.0);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
if (gtk_toggle_button_get_active(builder_get(GTK_TOGGLE_BUTTON, "vram_viewer_tileset_toggle_grid_button"))) {
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.25);
cairo_set_line_width(cr, 1);
const int divisions_x = 256 / 8;
const int divisions_y = 192 / 8;
for (int i = 0; i < divisions_x; i++) {
const int j = 256 * i;
cairo_move_to(cr, j / divisions_x, 0);
cairo_line_to(cr, j / divisions_x, 192);
}
for (int i = 0; i < divisions_y; i++) {
const int j = 192 * i;
cairo_move_to(cr, 0, j / divisions_y);
cairo_line_to(cr, 256, j / divisions_y);
}
cairo_stroke(cr);
}
g_mutex_unlock(&gui_data.tileset_buffer_mutex);
return false;
}
// Gets called when the tilemap viewer should be redrawn
static gboolean on_draw_vram_viewer_tilemap(GtkWidget *widget, cairo_t *cr, gpointer data) {
g_mutex_lock(&gui_data.tilemap_buffer_mutex);
guint width, height;
GtkStyleContext *context;
context = gtk_widget_get_style_context(widget);
width = gtk_widget_get_allocated_width(widget);
height = gtk_widget_get_allocated_height(widget);
gtk_render_background(context, cr, 0, 0, width, height);
cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) gui_data.tilemap_buffer,
CAIRO_FORMAT_RGB24,
256,
256,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 256)
);
cairo_scale(cr, 2.0, 2.0);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
if (gtk_toggle_button_get_active(builder_get(GTK_TOGGLE_BUTTON, "vram_viewer_tilemap_toggle_grid_button"))) {
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.25);
cairo_set_line_width(cr, 1);
const int divisions = 256 / 8;
for (int i = 0; i < divisions; i++) {
const int j = 256 * i;
cairo_move_to(cr, j / divisions, 0);
cairo_line_to(cr, j / divisions, 256);
cairo_move_to(cr, 0, j / divisions);
cairo_line_to(cr, 256, j / divisions);
}
cairo_stroke(cr);
}
if (gtk_toggle_button_get_active(builder_get(GTK_TOGGLE_BUTTON, "vram_viewer_tilemap_toggle_scrolling_button"))) {
cairo_rectangle(cr, -2, -2, width + 2, height + 2);
for (unsigned x = 0; x < 2; x++) {
for (unsigned y = 0; y < 2; y++) {
Rect rect = gui_data.scroll_rect;
rect.x -= 256 * x;
rect.y += 256 * y;
cairo_rectangle(cr, rect.x, rect.y, rect.w, rect.h);
}
}
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_set_line_width(cr, 2);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.5);
cairo_fill_preserve(cr);
cairo_clip_preserve(cr);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.6);
cairo_stroke(cr);
}
g_mutex_unlock(&gui_data.tilemap_buffer_mutex);
return false;
}
static gboolean on_motion_vram_viewer_tileset(GtkWidget *widget, GdkEventMotion *event) {
int x, y;
if (event->is_hint) {
gdk_window_get_pointer (event->window, &x, &y, NULL);
}
else {
x = event->x;
y = event->y;
}
// Compensate for our canvas scale
x /= 2;
y /= 2;
uint8_t bank = x >= 128? 1 : 0;
x &= 127;
uint16_t tile = x / 8 + y / 8 * 16;
GtkLabel *status = builder_get(GTK_LABEL, "vram_viewer_status");
gtk_label_set_text(status, g_strdup_printf("Tile number $%02x at %d:$%04x", tile & 0xFF, bank, 0x8000 + tile * 0x10));
return true;
}
static gboolean on_motion_vram_viewer_tilemap(GtkWidget *widget, GdkEventMotion *event) {
int x, y;
if (event->is_hint) {
gdk_window_get_pointer (event->window, &x, &y, NULL);
}
else {
x = event->x;
y = event->y;
}
// Compensate for our canvas scale
x /= 2;
y /= 2;
GtkLabel *status = builder_get(GTK_LABEL, "vram_viewer_status");
uint16_t map_offset = x / 8 + y / 8 * 32;
uint16_t map_base = 0x1800;
GB_map_type_t map_type = GB_MAP_AUTO;
const gchar *map_type_id = gtk_combo_box_get_active_id(builder_get(GTK_COMBO_BOX, "vram_viewer_tilemap_tilemap_selector"));
if (g_strcmp0("auto", map_type_id) != 0) {
map_type = (g_strcmp0("9800", map_type_id) == 0)? GB_MAP_9800 : GB_MAP_9C00;
}
GB_tileset_type_t tileset_type = GB_TILESET_AUTO;
const gchar *tileset_type_id = gtk_combo_box_get_active_id(builder_get(GTK_COMBO_BOX, "vram_viewer_tilemap_tileset_selector"));
if (g_strcmp0("auto", tileset_type_id) != 0) {
tileset_type = (g_strcmp0("8800", tileset_type_id) == 0)? GB_TILESET_8800 : GB_TILESET_8000;
}
uint8_t lcdc = ((uint8_t *)GB_get_direct_access(&gb, GB_DIRECT_ACCESS_IO, NULL, NULL))[GB_IO_LCDC];
uint8_t *vram = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL);
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & 0x08)) {
map_base = 0x1c00;
}
if (tileset_type == GB_TILESET_AUTO) {
tileset_type = (lcdc & 0x10)? GB_TILESET_8800 : GB_TILESET_8000;
}
uint8_t tile = vram[map_base + map_offset];
uint16_t tile_address = 0;
if (tileset_type == GB_TILESET_8000) {
tile_address = 0x8000 + tile * 0x10;
}
else {
tile_address = 0x9000 + (int8_t)tile * 0x10;
}
if (GB_is_cgb(&gb)) {
uint8_t attributes = vram[map_base + map_offset + 0x2000];
gtk_label_set_text(status, g_strdup_printf("Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d)",
tile,
attributes & 0x8? 1 : 0,
tile_address,
0x8000 + map_base + map_offset,
(attributes & 0x80) ? 'P' : '-',
(attributes & 0x40) ? 'V' : '-',
(attributes & 0x20) ? 'H' : '-',
attributes & 0x8? 1 : 0,
attributes & 0x7
));
}
else {
gtk_label_set_text(status, g_strdup_printf("Tile number $%02x ($%04x) at map address $%04x",
tile,
tile_address,
0x8000 + map_base + map_offset
));
}
return true;
}
static void on_vram_tab_change(GtkWidget *widget, GParamSpec *pspec, GtkStackSwitcher *self) {
gtk_label_set_text(builder_get(GTK_LABEL, "vram_viewer_status"), "");
gui_data.vram_viewer_active_tab = (gchar *)gtk_stack_get_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"));
}
// This functions gets called immediately after registration of the GApplication // This functions gets called immediately after registration of the GApplication
static void startup(GApplication *app, gpointer null_ptr) { static void startup(GApplication *app, gpointer null_ptr) {
signal(SIGINT, quit_interrupt); signal(SIGINT, quit_interrupt);
@ -1774,7 +1312,7 @@ static void startup(GApplication *app, gpointer null_ptr) {
g_signal_connect(gui_data.preferences, "realize", G_CALLBACK(on_preferences_realize), (gpointer) gui_data.builder); g_signal_connect(gui_data.preferences, "realize", G_CALLBACK(on_preferences_realize), (gpointer) gui_data.builder);
init_config(app, gui_data.cli_options.config_path, &gui_data.config_modification_date, gui_data.preferences); init_config(app, gui_data.cli_options.config_path, &gui_data.config_modification_date, gui_data.preferences);
gui_data.vram_viewer = GTK_WINDOW(get_object("vram_viewer")); gui_data.vram_viewer = vram_viewer_new();
gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer")); gui_data.memory_viewer = GTK_WINDOW(get_object("memory_viewer"));
gui_data.console = GTK_WINDOW(get_object("console")); gui_data.console = GTK_WINDOW(get_object("console"));
@ -1890,26 +1428,12 @@ static void connect_signal_handlers(GApplication *app) {
g_signal_connect(gui_data.main_window, "key_release_event", G_CALLBACK(on_key_press), NULL); g_signal_connect(gui_data.main_window, "key_release_event", G_CALLBACK(on_key_press), NULL);
g_signal_connect(gui_data.main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL); g_signal_connect(gui_data.main_window, "window-state-event", G_CALLBACK(on_window_state_change), NULL);
g_signal_connect(gui_data.vram_viewer, "realize", G_CALLBACK(on_vram_viewer_realize), NULL);
g_signal_connect(gui_data.vram_viewer, "unrealize", G_CALLBACK(on_vram_viewer_unrealize), NULL);
// Just hide our sub-windows when closing them // Just hide our sub-windows when closing them
g_signal_connect(gui_data.preferences, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); g_signal_connect(gui_data.preferences, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(gui_data.vram_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); g_signal_connect(gui_data.vram_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(gui_data.memory_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); g_signal_connect(gui_data.memory_viewer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(gui_data.console, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); g_signal_connect(gui_data.console, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(gui_data.printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); g_signal_connect(gui_data.printer, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(get_object("vram_viewer_tileset_canvas"), "draw", G_CALLBACK(on_draw_vram_viewer_tileset), NULL);
g_signal_connect(get_object("vram_viewer_tilemap_canvas"), "draw", G_CALLBACK(on_draw_vram_viewer_tilemap), NULL);
gtk_widget_add_events(builder_get(GTK_WIDGET, "vram_viewer_tileset_canvas"), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
gtk_widget_add_events(builder_get(GTK_WIDGET, "vram_viewer_tilemap_canvas"), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
g_signal_connect(get_object("vram_viewer_tileset_canvas"), "motion_notify_event", G_CALLBACK(on_motion_vram_viewer_tileset), NULL);
g_signal_connect(get_object("vram_viewer_tilemap_canvas"), "motion_notify_event", G_CALLBACK(on_motion_vram_viewer_tilemap), NULL);
g_signal_connect(get_object("vram_viewer_stack"), "notify::visible-child", G_CALLBACK(on_vram_tab_change), NULL);
} }
// TODO: Comment // TODO: Comment
@ -1942,7 +1466,7 @@ static void gl_draw(void) {
render_bitmap_with_shader( render_bitmap_with_shader(
&gui_data.shader, _pixels, previous, &gui_data.shader, _pixels, previous,
GB_get_screen_width(&gb), GB_get_screen_height(&gb), GB_get_screen_width(&gb), GB_get_screen_height(&gb),
gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.w, gui_data.viewport.h, gui_data.viewport.x, gui_data.viewport.y, gui_data.viewport.width, gui_data.viewport.height,
mode mode
); );
} }
@ -1965,6 +1489,7 @@ static gboolean on_draw_fallback(GtkWidget *widget, cairo_t *cr, gpointer data)
guint screen_height = GB_get_screen_height(&gb); guint screen_height = GB_get_screen_height(&gb);
gtk_render_background(context, cr, 0, 0, width, height); gtk_render_background(context, cr, 0, 0, width, height);
gtk_render_frame(context, cr, 0, 0, width, height);
cairo_surface_t *surface = cairo_image_surface_create_for_data( cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) get_current_buffer(), (unsigned char *) get_current_buffer(),
@ -1975,7 +1500,7 @@ static gboolean on_draw_fallback(GtkWidget *widget, cairo_t *cr, gpointer data)
); );
cairo_translate(cr, gui_data.viewport.x, gui_data.viewport.y); cairo_translate(cr, gui_data.viewport.x, gui_data.viewport.y);
cairo_scale(cr, gui_data.viewport.w / screen_width, gui_data.viewport.h / screen_height); cairo_scale(cr, gui_data.viewport.width / screen_width, gui_data.viewport.height / screen_height);
cairo_set_source_surface(cr, surface, 0, 0); cairo_set_source_surface(cr, surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr); cairo_paint(cr);
@ -2103,7 +1628,6 @@ static void shutdown(GApplication *app, GFile **files, gint n_files, const gchar
g_debug("SHUTDOWN"); g_debug("SHUTDOWN");
stop(); stop();
while (gui_data.stopping);
SDL_Quit(); SDL_Quit();
if (gui_data.image_buffers[0]) g_free(gui_data.image_buffers[0]); if (gui_data.image_buffers[0]) g_free(gui_data.image_buffers[0]);
@ -2185,13 +1709,13 @@ static void close_rom(void) {
} }
// Clear the VRAM viewer // Clear the VRAM viewer
g_mutex_lock(&gui_data.tileset_buffer_mutex); // g_mutex_lock(&gui_data.tileset_buffer_mutex);
memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer); // memset(gui_data.tileset_buffer, 0, sizeof gui_data.tileset_buffer);
g_mutex_unlock(&gui_data.tileset_buffer_mutex); // g_mutex_unlock(&gui_data.tileset_buffer_mutex);
g_mutex_lock(&gui_data.tilemap_buffer_mutex); // g_mutex_lock(&gui_data.tilemap_buffer_mutex);
memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer); // memset(gui_data.tilemap_buffer, 0, sizeof gui_data.tilemap_buffer);
g_mutex_unlock(&gui_data.tilemap_buffer_mutex); // g_mutex_unlock(&gui_data.tilemap_buffer_mutex);
gtk_stack_set_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"), "vram_viewer_tileset"); gtk_stack_set_visible_child_name(builder_get(GTK_STACK, "vram_viewer_stack"), "vram_viewer_tileset");
gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_sprites"), NULL); gtk_tree_view_set_model(builder_get(GTK_TREE_VIEW, "vram_viewer_sprites"), NULL);

View File

@ -0,0 +1,516 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0
The MIT License (MIT)
Copyright (c) 2015-2019 Lior Halphon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Maximilian Mader
-->
<interface>
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name SameBoy -->
<!-- interface-description SameBoy is an open source Game Boy (DMG) and Game Boy Color (CGB) emulator, written in portable C. -->
<!-- interface-copyright 2015-2019 Lior Halphon -->
<!-- interface-authors Maximilian Mader -->
<template class="VramViewerWindow">
<property name="can_focus">False</property>
<property name="title" translatable="yes">VRAM Viewer</property>
<property name="resizable">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">3</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkStackSwitcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="stack">stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hhomogeneous">False</property>
<property name="vhomogeneous">False</property>
<property name="transition_type">slide-left-right</property>
<property name="interpolate_size">True</property>
<signal name="notify::visible-child" handler="visible_tab_changed"/>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_bottom">3</property>
<child>
<object class="GtkComboBoxText" id="tileset_palette_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">bg0</property>
<items>
<item id="bg0" translatable="yes">Background Palette 0</item>
<item id="bg1" translatable="yes">Background Palette 1</item>
<item id="bg2" translatable="yes">Background Palette 2</item>
<item id="bg3" translatable="yes">Background Palette 3</item>
<item id="bg4" translatable="yes">Background Palette 4</item>
<item id="bg5" translatable="yes">Background Palette 5</item>
<item id="bg6" translatable="yes">Background Palette 6</item>
<item id="bg7" translatable="yes">Background Palette 7</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="obj0" translatable="yes">Object Palette 0</item>
<item id="obj1" translatable="yes">Object Palette 1</item>
<item id="obj2" translatable="yes">Object Palette 2</item>
<item id="obj3" translatable="yes">Object Palette 3</item>
<item id="obj4" translatable="yes">Object Palette 4</item>
<item id="obj5" translatable="yes">Object Palette 5</item>
<item id="obj6" translatable="yes">Object Palette 6</item>
<item id="obj7" translatable="yes">Object Palette 7</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="tileset_toggle_grid_button">
<property name="label" translatable="yes">Grid</property>
<property name="width_request">70</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<style>
<class name="vram-viewer-button-bar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="tileset_canvas">
<property name="width_request">512</property>
<property name="height_request">384</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<signal name="draw" handler="draw_tileset_canvas"/>
<signal name="motion_notify_event" handler="tileset_canvas_motion"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">tileset</property>
<property name="title" translatable="yes">Tileset</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="spacing">3</property>
<child>
<object class="GtkComboBoxText" id="tilemap_palette_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">auto</property>
<items>
<item id="auto" translatable="yes">Effective Palettes</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="bg0" translatable="yes">Background Palette 0</item>
<item id="bg1" translatable="yes">Background Palette 1</item>
<item id="bg2" translatable="yes">Background Palette 2</item>
<item id="bg3" translatable="yes">Background Palette 3</item>
<item id="bg4" translatable="yes">Background Palette 4</item>
<item id="bg5" translatable="yes">Background Palette 5</item>
<item id="bg6" translatable="yes">Background Palette 6</item>
<item id="bg7" translatable="yes">Background Palette 7</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="obj0" translatable="yes">Object Palette 0</item>
<item id="obj1" translatable="yes">Object Palette 1</item>
<item id="obj2" translatable="yes">Object Palette 2</item>
<item id="obj3" translatable="yes">Object Palette 3</item>
<item id="obj4" translatable="yes">Object Palette 4</item>
<item id="obj5" translatable="yes">Object Palette 5</item>
<item id="obj6" translatable="yes">Object Palette 6</item>
<item id="obj7" translatable="yes">Object Palette 7</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="tilemap_tilemap_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">auto</property>
<items>
<item id="auto" translatable="yes">Effective Tilemap</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="9800" translatable="yes">Tilemap at $9800</item>
<item id="9C00" translatable="yes">Tilemap at $9C00</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="tilemap_toggle_grid_button">
<property name="label" translatable="yes">Grid</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="tilemap_toggle_scrolling_button">
<property name="label" translatable="yes">Scrolling</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="tilemap_tileset_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">auto</property>
<items>
<item id="auto" translatable="yes">Effective Tileset</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="8800" translatable="yes">Tileset at $8800</item>
<item id="8000" translatable="yes">Tileset at $8000</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<style>
<class name="vram-viewer-button-bar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="tilemap_canvas">
<property name="width_request">512</property>
<property name="height_request">512</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="hexpand">False</property>
<property name="vexpand">False</property>
<signal name="draw" handler="draw_tilemap_canvas"/>
<signal name="motion_notify_event" handler="tilemap_canvas_motion"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">tilemap</property>
<property name="title" translatable="yes">Tilemap</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width_request">512</property>
<property name="height_request">512</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="sprites">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_clickable">False</property>
<property name="enable_search">False</property>
<property name="show_expanders">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="min_width">32</property>
<child>
<object class="GtkCellRendererPixbuf"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">X</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Y</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Tile</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Tile Addr.</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">OAM Addr.</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Attributes</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">6</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">sprites</property>
<property name="title" translatable="yes">Sprites</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkTreeView" id="palettes">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="headers_clickable">False</property>
<property name="enable_search">False</property>
<property name="show_expanders">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Type</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 0</property>
<child>
<object class="GtkCellRendererText" id="palette_cell_renderer_0"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 1</property>
<child>
<object class="GtkCellRendererText" id="palette_cell_renderer_1"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 2</property>
<child>
<object class="GtkCellRendererText" id="palette_cell_renderer_2"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 3</property>
<child>
<object class="GtkCellRendererText" id="palette_cell_renderer_3"/>
<attributes>
<attribute name="text">7</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
<property name="name">palettes</property>
<property name="title" translatable="yes">Palettes</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<property name="justify">center</property>
<property name="single_line_mode">True</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</template>
</interface>

View File

@ -1624,479 +1624,4 @@ Maximilian Mader https://github.com/max-m</property>
</object> </object>
</child> </child>
</object> </object>
<object class="GtkWindow" id="vram_viewer">
<property name="can_focus">False</property>
<property name="title" translatable="yes">VRAM Viewer</property>
<property name="resizable">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">3</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkStackSwitcher">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="stack">vram_viewer_stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="vram_viewer_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hhomogeneous">False</property>
<property name="vhomogeneous">False</property>
<property name="transition_type">slide-left-right</property>
<property name="interpolate_size">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_bottom">3</property>
<child>
<object class="GtkComboBoxText" id="vram_viewer_tileset_palette_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">bg0</property>
<items>
<item id="bg0" translatable="yes">Background Palette 0</item>
<item id="bg1" translatable="yes">Background Palette 1</item>
<item id="bg2" translatable="yes">Background Palette 2</item>
<item id="bg3" translatable="yes">Background Palette 3</item>
<item id="bg4" translatable="yes">Background Palette 4</item>
<item id="bg5" translatable="yes">Background Palette 5</item>
<item id="bg6" translatable="yes">Background Palette 6</item>
<item id="bg7" translatable="yes">Background Palette 7</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="obj0" translatable="yes">Object Palette 0</item>
<item id="obj1" translatable="yes">Object Palette 1</item>
<item id="obj2" translatable="yes">Object Palette 2</item>
<item id="obj3" translatable="yes">Object Palette 3</item>
<item id="obj4" translatable="yes">Object Palette 4</item>
<item id="obj5" translatable="yes">Object Palette 5</item>
<item id="obj6" translatable="yes">Object Palette 6</item>
<item id="obj7" translatable="yes">Object Palette 7</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="vram_viewer_tileset_toggle_grid_button">
<property name="label" translatable="yes">Grid</property>
<property name="width_request">70</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<style>
<class name="vram-viewer-button-bar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="vram_viewer_tileset_canvas">
<property name="width_request">512</property>
<property name="height_request">384</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">vram_viewer_tileset</property>
<property name="title" translatable="yes">Tileset</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="spacing">3</property>
<child>
<object class="GtkComboBoxText" id="vram_viewer_tilemap_palette_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">auto</property>
<items>
<item id="auto" translatable="yes">Effective Palettes</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="bg0" translatable="yes">Background Palette 0</item>
<item id="bg1" translatable="yes">Background Palette 1</item>
<item id="bg2" translatable="yes">Background Palette 2</item>
<item id="bg3" translatable="yes">Background Palette 3</item>
<item id="bg4" translatable="yes">Background Palette 4</item>
<item id="bg5" translatable="yes">Background Palette 5</item>
<item id="bg6" translatable="yes">Background Palette 6</item>
<item id="bg7" translatable="yes">Background Palette 7</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="obj0" translatable="yes">Object Palette 0</item>
<item id="obj1" translatable="yes">Object Palette 1</item>
<item id="obj2" translatable="yes">Object Palette 2</item>
<item id="obj3" translatable="yes">Object Palette 3</item>
<item id="obj4" translatable="yes">Object Palette 4</item>
<item id="obj5" translatable="yes">Object Palette 5</item>
<item id="obj6" translatable="yes">Object Palette 6</item>
<item id="obj7" translatable="yes">Object Palette 7</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="vram_viewer_tilemap_tilemap_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">auto</property>
<items>
<item id="auto" translatable="yes">Effective Tilemap</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="9800" translatable="yes">Tilemap at $9800</item>
<item id="9C00" translatable="yes">Tilemap at $9C00</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="vram_viewer_tilemap_toggle_grid_button">
<property name="label" translatable="yes">Grid</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="vram_viewer_tilemap_toggle_scrolling_button">
<property name="label" translatable="yes">Scrolling</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="vram_viewer_tilemap_tileset_selector">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">0</property>
<property name="active_id">auto</property>
<items>
<item id="auto" translatable="yes">Effective Tileset</item>
<item translatable="yes">&lt;separator&gt;</item>
<item id="8800" translatable="yes">Tileset at $8800</item>
<item id="8000" translatable="yes">Tileset at $8000</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<style>
<class name="vram-viewer-button-bar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="vram_viewer_tilemap_canvas">
<property name="width_request">512</property>
<property name="height_request">512</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="hexpand">False</property>
<property name="vexpand">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">vram_viewer_tilemap</property>
<property name="title" translatable="yes">Tilemap</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width_request">512</property>
<property name="height_request">512</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="vram_viewer_sprites">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_clickable">False</property>
<property name="enable_search">False</property>
<property name="show_expanders">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="min_width">32</property>
<child>
<object class="GtkCellRendererPixbuf"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">X</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Y</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Tile</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Tile Addr.</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">OAM Addr.</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Attributes</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">6</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">vram_viewer_sprites</property>
<property name="title" translatable="yes">Sprites</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkTreeView" id="vram_viewer_palettes">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="headers_clickable">False</property>
<property name="enable_search">False</property>
<property name="show_expanders">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Type</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 0</property>
<child>
<object class="GtkCellRendererText" id="vram_viewer_palette_cell_renderer_0"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 1</property>
<child>
<object class="GtkCellRendererText" id="vram_viewer_palette_cell_renderer_1"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 2</property>
<child>
<object class="GtkCellRendererText" id="vram_viewer_palette_cell_renderer_2"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Color 3</property>
<child>
<object class="GtkCellRendererText" id="vram_viewer_palette_cell_renderer_3"/>
<attributes>
<attribute name="text">7</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
<property name="name">vram_viewer_palettes</property>
<property name="title" translatable="yes">Palettes</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="vram_viewer_status">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<property name="justify">center</property>
<property name="single_line_mode">True</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</object>
</interface> </interface>

View File

@ -2,6 +2,7 @@
<gresources> <gresources>
<gresource prefix="/io/github/sameboy"> <gresource prefix="/io/github/sameboy">
<file preprocess="xml-stripblanks" compressed="true">ui/window.ui</file> <file preprocess="xml-stripblanks" compressed="true">ui/window.ui</file>
<file preprocess="xml-stripblanks" compressed="true">ui/vram_viewer.ui</file>
<!-- <!--
<file preprocess="xml-stripblanks">gtk/menus.ui</file> <file preprocess="xml-stripblanks">gtk/menus.ui</file>
<file preprocess="xml-stripblanks">gtk/menus-common.ui</file> <file preprocess="xml-stripblanks">gtk/menus-common.ui</file>

View File

@ -3,13 +3,11 @@
#include "SDL.h" #include "SDL.h"
#include "shader.h" #include "shader.h"
#include "vram_viewer.h"
#define tileset_buffer_length 256 * 192 * 4
#define tilemap_buffer_length 256 * 256 * 4
typedef struct{ typedef struct{
int16_t x, y; int16_t x, y;
uint16_t w, h; uint16_t width, height;
} Rect; } Rect;
typedef struct GuiData { typedef struct GuiData {
@ -43,7 +41,7 @@ typedef struct GuiData {
GtkGLArea *gl_area; GtkGLArea *gl_area;
GtkDrawingArea *fallback_canvas; GtkDrawingArea *fallback_canvas;
GtkWindow *preferences; GtkWindow *preferences;
GtkWindow *vram_viewer; VramViewerWindow *vram_viewer;
GtkWindow *memory_viewer; GtkWindow *memory_viewer;
GtkWindow *console; GtkWindow *console;
GtkWindow *printer; GtkWindow *printer;
@ -58,19 +56,6 @@ typedef struct GuiData {
GCond debugger_input_cond; GCond debugger_input_cond;
GRecMutex console_output_lock; GRecMutex console_output_lock;
GPtrArray *debugger_input_queue; GPtrArray *debugger_input_queue;
bool vram_viewer_visible;
bool vram_viewer_updating;
gchar *vram_viewer_active_tab;
gboolean vram_viewer_is_cgb;
uint8_t vram_viewer_palette_data[16][0x40];
GB_oam_info_t oam_info[40];
uint16_t oam_count;
uint8_t oam_height;
uint32_t tileset_buffer[tileset_buffer_length];
uint32_t tilemap_buffer[tilemap_buffer_length];
GMutex tileset_buffer_mutex;
GMutex tilemap_buffer_mutex;
Rect scroll_rect;
// Audio and video // Audio and video
bool audio_initialized; bool audio_initialized;

562
gtk3/vram_viewer.c Normal file
View File

@ -0,0 +1,562 @@
#include "vram_viewer.h"
#include <stdbool.h>
#include "util.h"
#define tileset_buffer_length 256 * 192 * 4
#define tilemap_buffer_length 256 * 256 * 4
struct _VramViewerWindow {
GtkWindowClass parent_class;
GtkStack *stack;
GtkComboBoxText *tileset_palette_selector;
GtkToggleButton *tileset_toggle_grid_button;
GtkDrawingArea *tileset_canvas;
GtkComboBoxText *tilemap_palette_selector;
GtkComboBoxText *tilemap_tilemap_selector;
GtkToggleButton *tilemap_toggle_grid_button;
GtkToggleButton *tilemap_toggle_scrolling_button;
GtkComboBoxText *tilemap_tileset_selector;
GtkDrawingArea *tilemap_canvas;
GtkTreeView *sprites;
GtkTreeView *palettes;
GtkCellRendererText *palette_cell_renderer_0;
GtkCellRendererText *palette_cell_renderer_1;
GtkCellRendererText *palette_cell_renderer_2;
GtkCellRendererText *palette_cell_renderer_3;
GtkLabel *status;
bool is_cgb;
uint16_t oam_count;
uint8_t oam_height;
uint8_t palette_data[16][0x40];
GB_oam_info_t oam_info[40];
Rect scroll_rect;
uint8_t gb_lcdc;
uint8_t *gb_vram;
uint32_t *tileset_buffer;
uint32_t *tilemap_buffer;
};
G_DEFINE_TYPE(VramViewerWindow, vram_viewer_window, GTK_TYPE_WINDOW);
static void visible_tab_changed(GObject *stack, GParamSpec *pspec, VramViewerWindow *window) {
if (gtk_widget_in_destruction (GTK_WIDGET (stack))) return;
gtk_label_set_text(window->status, "");
}
static gboolean draw_tileset_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) {
guint width, height;
GtkStyleContext *context;
context = gtk_widget_get_style_context(widget);
width = gtk_widget_get_allocated_width(widget);
height = gtk_widget_get_allocated_height(widget);
gtk_render_background(context, cr, 0, 0, width, height);
gtk_render_frame(context, cr, 0, 0, width, height);
cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) window->tileset_buffer,
CAIRO_FORMAT_RGB24,
256,
192,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 256)
);
cairo_scale(cr, 2.0, 2.0);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
if (gtk_toggle_button_get_active(window->tileset_toggle_grid_button)) {
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.25);
cairo_set_line_width(cr, 1);
const int divisions_x = 256 / 8;
const int divisions_y = 192 / 8;
for (int i = 0; i < divisions_x; i++) {
const int j = 256 * i;
cairo_move_to(cr, j / divisions_x, 0);
cairo_line_to(cr, j / divisions_x, 192);
}
for (int i = 0; i < divisions_y; i++) {
const int j = 192 * i;
cairo_move_to(cr, 0, j / divisions_y);
cairo_line_to(cr, 256, j / divisions_y);
}
cairo_stroke(cr);
}
return false;
}
static gboolean draw_tilemap_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) {
guint width, height;
GtkStyleContext *context;
context = gtk_widget_get_style_context(widget);
width = gtk_widget_get_allocated_width(widget);
height = gtk_widget_get_allocated_height(widget);
gtk_render_background(context, cr, 0, 0, width, height);
gtk_render_frame(context, cr, 0, 0, width, height);
cairo_surface_t *surface = cairo_image_surface_create_for_data(
(unsigned char *) window->tilemap_buffer,
CAIRO_FORMAT_RGB24,
256,
256,
cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 256)
);
cairo_scale(cr, 2.0, 2.0);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_paint(cr);
if (gtk_toggle_button_get_active(window->tilemap_toggle_grid_button)) {
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.25);
cairo_set_line_width(cr, 1);
const int divisions = 256 / 8;
for (int i = 0; i < divisions; i++) {
const int j = 256 * i;
cairo_move_to(cr, j / divisions, 0);
cairo_line_to(cr, j / divisions, 256);
cairo_move_to(cr, 0, j / divisions);
cairo_line_to(cr, 256, j / divisions);
}
cairo_stroke(cr);
}
if (gtk_toggle_button_get_active(window->tilemap_toggle_scrolling_button)) {
cairo_rectangle(cr, -2, -2, width + 2, height + 2);
for (unsigned x = 0; x < 2; x++) {
for (unsigned y = 0; y < 2; y++) {
Rect rect = window->scroll_rect;
rect.x -= 256 * x;
rect.y += 256 * y;
cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
}
}
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_set_line_width(cr, 2);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.5);
cairo_fill_preserve(cr);
cairo_clip_preserve(cr);
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.6);
cairo_stroke(cr);
}
return false;
}
static gboolean tileset_canvas_motion(GtkWidget *widget, GdkEventMotion *event, VramViewerWindow *window) {
int x, y;
if (event->is_hint) {
gdk_window_get_pointer(event->window, &x, &y, NULL);
}
else {
x = event->x;
y = event->y;
}
// Compensate for our canvas scale
x /= 2;
y /= 2;
uint8_t bank = x >= 128? 1 : 0;
x &= 127;
uint16_t tile = x / 8 + y / 8 * 16;
gtk_label_set_text(window->status, g_strdup_printf("Tile number $%02x at %d:$%04x", tile & 0xFF, bank, 0x8000 + tile * 0x10));
return true;
}
static gboolean tilemap_canvas_motion(GtkWidget *widget, GdkEventMotion *event, VramViewerWindow *window) {
if (!window->gb_vram) {
gtk_label_set_text(window->status, "");
return false;
}
int x, y;
if (event->is_hint) {
gdk_window_get_pointer (event->window, &x, &y, NULL);
}
else {
x = event->x;
y = event->y;
}
// Compensate for our canvas scale
x /= 2;
y /= 2;
GtkLabel *status = window->status;
uint16_t map_offset = x / 8 + y / 8 * 32;
uint16_t map_base = 0x1800;
GB_map_type_t map_type = GB_MAP_AUTO;
const gchar *map_type_id = vram_viewer_get_tilemap_type_id(window);
if (g_strcmp0("auto", map_type_id) != 0) {
map_type = (g_strcmp0("9800", map_type_id) == 0)? GB_MAP_9800 : GB_MAP_9C00;
}
GB_tileset_type_t tileset_type = GB_TILESET_AUTO;
const gchar *tileset_type_id = vram_viewer_get_tileset_type_id(window);
if (g_strcmp0("auto", tileset_type_id) != 0) {
tileset_type = (g_strcmp0("8800", tileset_type_id) == 0)? GB_TILESET_8800 : GB_TILESET_8000;
}
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && window->gb_lcdc & 0x08)) {
map_base = 0x1c00;
}
if (tileset_type == GB_TILESET_AUTO) {
tileset_type = (window->gb_lcdc & 0x10)? GB_TILESET_8800 : GB_TILESET_8000;
}
uint8_t tile = window->gb_vram[map_base + map_offset];
uint16_t tile_address = 0;
if (tileset_type == GB_TILESET_8000) {
tile_address = 0x8000 + tile * 0x10;
}
else {
tile_address = 0x9000 + (int8_t)tile * 0x10;
}
if (window->is_cgb) {
uint8_t attributes = window->gb_vram[map_base + map_offset + 0x2000];
gtk_label_set_text(status, g_strdup_printf("Tile number $%02x (%d:$%04x) at map address $%04x (Attributes: %c%c%c%d%d)",
tile,
attributes & 0x8? 1 : 0,
tile_address,
0x8000 + map_base + map_offset,
(attributes & 0x80) ? 'P' : '-',
(attributes & 0x40) ? 'V' : '-',
(attributes & 0x20) ? 'H' : '-',
attributes & 0x8? 1 : 0,
attributes & 0x7
));
}
else {
gtk_label_set_text(status, g_strdup_printf("Tile number $%02x ($%04x) at map address $%04x",
tile,
tile_address,
0x8000 + map_base + map_offset
));
}
return true;
}
static void vram_viewer_window_init(VramViewerWindow *window) {
gtk_widget_init_template(GTK_WIDGET(window));
window->tileset_buffer = g_new0(uint32_t, tileset_buffer_length);
window->tilemap_buffer = g_new0(uint32_t, tilemap_buffer_length);
window->is_cgb = false;
window->oam_count = 0;
window->oam_height = 0;
gtk_widget_add_events(GTK_WIDGET(window->tileset_canvas), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
gtk_widget_add_events(GTK_WIDGET(window->tilemap_canvas), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
}
static void vram_viewer_window_class_init(VramViewerWindowClass *class) {
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/vram_viewer.ui");
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, stack);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tileset_palette_selector);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tileset_toggle_grid_button);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tileset_canvas);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_palette_selector);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_tilemap_selector);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_toggle_grid_button);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_toggle_scrolling_button);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_tileset_selector);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_canvas);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, sprites);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palettes);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_0);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_1);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_2);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_3);
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, status);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), visible_tab_changed);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), draw_tileset_canvas);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), draw_tilemap_canvas);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), tileset_canvas_motion);
gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), tilemap_canvas_motion);
}
VramViewerWindow *vram_viewer_new(void) {
return g_object_new (VRAM_VIEWER_WINDOW_TYPE, NULL);
}
const gchar *vram_viewer_active_tab_name(VramViewerWindow *window) {
return gtk_stack_get_visible_child_name(window->stack);
}
const gchar *vram_viewer_get_tileset_palette_id(VramViewerWindow *window) {
return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tileset_palette_selector));
}
const gchar *vram_viewer_get_tilemap_palette_id(VramViewerWindow *window) {
return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tilemap_palette_selector));
}
const gchar *vram_viewer_get_tilemap_type_id(VramViewerWindow *window) {
return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tilemap_tilemap_selector));
}
const gchar *vram_viewer_get_tileset_type_id(VramViewerWindow *window) {
return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tilemap_tileset_selector));
}
static void update_sprite_list(VramViewerWindow *window) {
GtkTreeIter iter;
GtkListStore *store = gtk_list_store_new(7,
GDK_TYPE_PIXBUF, // Preview image
G_TYPE_STRING, // X position
G_TYPE_STRING, // Y position
G_TYPE_STRING, // Tile
G_TYPE_STRING, // Tile Address
G_TYPE_STRING, // OAM Address
G_TYPE_STRING // Attributes
);
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
GB_oam_info_t *oam_info = window->oam_info;
for (unsigned row = 0; row < window->oam_count; ++row) {
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_bytes(
g_bytes_new(oam_info[row].image, 128 * sizeof(uint32_t)),
GDK_COLORSPACE_RGB, true, 8, 8, window->oam_height, 8 * sizeof(uint32_t)
);
GdkPixbuf *dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 8 * 2, window->oam_height * 2);
gdk_pixbuf_scale(pixbuf, dest,
0, 0, 8 * 2, window->oam_height * 2,
0, 0, 2.0, 2.0,
GDK_INTERP_NEAREST
);
gtk_list_store_insert_with_values(store, &iter, -1,
0, dest,
1, g_strdup_printf("%i", oam_info[row].x - 8),
2, g_strdup_printf("%i", oam_info[row].y - 16),
3, g_strdup_printf("$%02x", oam_info[row].tile),
4, g_strdup_printf("$%04x", 0x8000 + oam_info[row].tile * 0x10),
5, g_strdup_printf("$%04x", oam_info[row].oam_addr),
6, window->is_cgb
? g_strdup_printf("%c%c%c%d%d",
oam_info[row].flags & 0x80? 'P' : '-',
oam_info[row].flags & 0x40? 'Y' : '-',
oam_info[row].flags & 0x20? 'X' : '-',
oam_info[row].flags & 0x08? 1 : 0,
oam_info[row].flags & 0x07)
: g_strdup_printf("%c%c%c%d",
oam_info[row].flags & 0x80? 'P' : '-',
oam_info[row].flags & 0x40? 'Y' : '-',
oam_info[row].flags & 0x20? 'X' : '-',
oam_info[row].flags & 0x10? 1 : 0),
-1
);
g_object_unref(pixbuf);
g_object_unref(dest);
}
gtk_tree_view_set_model(window->sprites, GTK_TREE_MODEL(store));
g_object_unref(store);
}
static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data_ptr) {
const gchar *title = gtk_tree_view_column_get_title(col);
const uint8_t color_index = g_ascii_strtoll(&title[6], NULL, 10);
const uint8_t column_index = 2 + (2 * color_index);
GValue color_val = G_VALUE_INIT;
gtk_tree_model_get_value(model, iter, column_index, &color_val);
gint color = g_value_get_int(&color_val);
gchar *color_string = g_strdup_printf("#%06x", color);
gint lightness = 0.299 * ((color >> 16) & 0xFF) + 0.587 * ((color >> 8) & 0xFF) + 0.114 * (color & 0xFF);
GValue color_str = G_VALUE_INIT;
g_value_init(&color_str, G_TYPE_STRING);
g_value_set_string(&color_str, color_string);
g_object_set_property(G_OBJECT(renderer), "background", &color_str);
GValue fg_color_str = G_VALUE_INIT;
g_value_init(&fg_color_str, G_TYPE_STRING);
g_value_set_static_string(&fg_color_str, (lightness > 0x7F)? "#000000" : "#FFFFFF");
g_object_set_property(G_OBJECT(renderer), "foreground", &fg_color_str);
g_value_unset(&color_val);
g_value_unset(&color_str);
g_value_unset(&fg_color_str);
g_free(color_string);
}
static void update_palettes(VramViewerWindow *window) {
GtkTreeIter iter;
GtkListStore *store = gtk_list_store_new(9,
G_TYPE_STRING, // Name
G_TYPE_STRING, // Color 0 string
G_TYPE_INT, // Color 0 integer
G_TYPE_STRING, // Color 1 string
G_TYPE_INT, // Color 1 integer
G_TYPE_STRING, // Color 2 string
G_TYPE_INT, // Color 2 integer
G_TYPE_STRING, // Color 3 string
G_TYPE_INT // Color 3 integer
);
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
for (unsigned row = 0; row < 16; ++row) {
uint8_t offset = (row & 7) * 4;
uint16_t color_0 = (window->palette_data[row][((0 + offset) << 1) + 1] << 8) | window->palette_data[row][((0 + offset) << 1)];
uint16_t color_1 = (window->palette_data[row][((1 + offset) << 1) + 1] << 8) | window->palette_data[row][((1 + offset) << 1)];
uint16_t color_2 = (window->palette_data[row][((2 + offset) << 1) + 1] << 8) | window->palette_data[row][((2 + offset) << 1)];
uint16_t color_3 = (window->palette_data[row][((3 + offset) << 1) + 1] << 8) | window->palette_data[row][((3 + offset) << 1)];
gtk_list_store_insert_with_values(store, &iter, -1,
0, g_strdup_printf("%s %d", row >=8 ? "Object" : "Background", row & 7),
1, g_strdup_printf("$%04x", color_0 & 0x7FFF),
2, convert_color(color_0),
3, g_strdup_printf("$%04x", color_1 & 0x7FFF),
4, convert_color(color_1),
5, g_strdup_printf("$%04x", color_2 & 0x7FFF),
6, convert_color(color_2),
7, g_strdup_printf("$%04x", color_3 & 0x7FFF),
8, convert_color(color_3),
-1
);
}
GtkTreeViewColumn *column_0 = gtk_tree_view_get_column(window->palettes, 1);
GtkTreeViewColumn *column_1 = gtk_tree_view_get_column(window->palettes, 2);
GtkTreeViewColumn *column_2 = gtk_tree_view_get_column(window->palettes, 3);
GtkTreeViewColumn *column_3 = gtk_tree_view_get_column(window->palettes, 4);
GtkCellRendererText *cell_renderer_0 = window->palette_cell_renderer_0;
GtkCellRendererText *cell_renderer_1 = window->palette_cell_renderer_1;
GtkCellRendererText *cell_renderer_2 = window->palette_cell_renderer_2;
GtkCellRendererText *cell_renderer_3 = window->palette_cell_renderer_3;
gtk_tree_view_column_set_cell_data_func(column_0, GTK_CELL_RENDERER(cell_renderer_0), palette_color_data_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func(column_1, GTK_CELL_RENDERER(cell_renderer_1), palette_color_data_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func(column_2, GTK_CELL_RENDERER(cell_renderer_2), palette_color_data_func, NULL, NULL);
gtk_tree_view_column_set_cell_data_func(column_3, GTK_CELL_RENDERER(cell_renderer_3), palette_color_data_func, NULL, NULL);
gtk_tree_view_set_model(window->palettes, GTK_TREE_MODEL(store));
g_object_unref(store);
}
void vram_viewer_update(VramViewerWindow *window, GB_gameboy_t *gb) {
if (!gtk_widget_is_visible(GTK_WIDGET(window))) return;
window->is_cgb = GB_is_cgb(gb);
window->gb_lcdc = ((uint8_t *)GB_get_direct_access(gb, GB_DIRECT_ACCESS_IO, NULL, NULL))[GB_IO_LCDC];
window->gb_vram = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL);
const gchar *active_tab_name = vram_viewer_active_tab_name(window);
if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_TILESET) == 0) {
const gchar *palette_id = vram_viewer_get_tileset_palette_id(window);
GB_palette_type_t palette_type = g_str_has_prefix(palette_id, "bg")? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM;
uint8_t palette_index = g_ascii_digit_value(palette_id[palette_type == GB_PALETTE_OAM ? 3 : 2]);
GB_draw_tileset(gb, window->tileset_buffer,
palette_type,
palette_index
);
}
else if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_TILEMAP) == 0) {
const gchar *palette_id = vram_viewer_get_tilemap_palette_id(window);
uint8_t palette_index = 0;
GB_palette_type_t palette_type = GB_PALETTE_AUTO;
if (g_strcmp0("auto", palette_id) != 0) {
palette_type = g_str_has_prefix(palette_id, "bg")? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM;
palette_index = g_ascii_digit_value(palette_id[palette_type == GB_PALETTE_OAM ? 3 : 2]);
}
GB_map_type_t map_type = GB_MAP_AUTO;
const gchar *map_type_id = vram_viewer_get_tilemap_type_id(window);
if (g_strcmp0("auto", map_type_id) != 0) {
map_type = (g_strcmp0("9800", map_type_id) == 0)? GB_MAP_9800 : GB_MAP_9C00;
}
GB_tileset_type_t tileset_type = GB_TILESET_AUTO;
const gchar *tileset_type_id = vram_viewer_get_tileset_type_id(window);
if (g_strcmp0("auto", tileset_type_id) != 0) {
tileset_type = (g_strcmp0("8800", tileset_type_id) == 0)? GB_TILESET_8800 : GB_TILESET_8000;
}
GB_draw_tilemap(gb, window->tilemap_buffer,
palette_type,
palette_index,
map_type,
tileset_type
);
window->scroll_rect = (Rect){
GB_read_memory(gb, 0xFF00 | GB_IO_SCX),
GB_read_memory(gb, 0xFF00 | GB_IO_SCY),
160, 144
};
}
else if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_SPRITES) == 0) {
window->oam_count = GB_get_oam_info(gb, (GB_oam_info_t *)window->oam_info, &window->oam_height);
g_idle_add((GSourceFunc) update_sprite_list, window);
}
else if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_PALETTES) == 0) {
size_t size;
for (unsigned row = 0; row < 16; ++row) {
uint8_t *palette_data = GB_get_direct_access(gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, &size, NULL);
memcpy(window->palette_data[row], palette_data, size);
}
g_idle_add((GSourceFunc) update_palettes, window);
}
}

30
gtk3/vram_viewer.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef vram_viewer_h
#define vram_viewer_h
#include <gtk/gtk.h>
#include <Core/gb.h>
#include <stdint.h>
#define VRAM_VIEWER_WINDOW_TYPE (vram_viewer_window_get_type())
G_DECLARE_FINAL_TYPE(VramViewerWindow, vram_viewer_window, SAMEBOY, WINDOW, GtkWindow)
#define VRAM_VIEWER_TAB_TILESET "tileset"
#define VRAM_VIEWER_TAB_TILEMAP "tilemap"
#define VRAM_VIEWER_TAB_SPRITES "sprites"
#define VRAM_VIEWER_TAB_PALETTES "palettes"
VramViewerWindow *vram_viewer_new(void);
const gchar *vram_viewer_active_tab_name(VramViewerWindow *window);
const gchar *vram_viewer_get_tileset_palette_id(VramViewerWindow *window);
const gchar *vram_viewer_get_tilemap_palette_id(VramViewerWindow *window);
const gchar *vram_viewer_get_tilemap_type_id(VramViewerWindow *window);
const gchar *vram_viewer_get_tileset_type_id(VramViewerWindow *window);
uint32_t *vram_viewer_get_tileset_buffer(VramViewerWindow *window);
uint32_t *vram_viewer_get_tilemap_buffer(VramViewerWindow *window);
void vram_viewer_update(VramViewerWindow *window, GB_gameboy_t *gb);
#endif