diff options
author | Lionel Landwerlin <[email protected]> | 2018-07-28 19:13:28 +0100 |
---|---|---|
committer | Lionel Landwerlin <[email protected]> | 2018-08-22 18:02:11 +0100 |
commit | 38f10d5a03542c60a589ff6a347df86790de00b7 (patch) | |
tree | 4094129182c1d822dd7439fb2b2c46171651fcff | |
parent | ea83a1d304dc97d1d155a633e95d902a08703175 (diff) |
intel: tools: add aubinator viewer
A graphical user interface version of aubinator.
Allows you to :
- simultaneously look at multiple points in the aub file (using all
the goodness of the existing decoding in aubinator)
- edit an aub file
v2: Switch from GLFW to GTK+3
v3: Fix warning when exiting
Signed-off-by: Lionel Landwerlin <[email protected]>
Acked-by: Rafael Antognolli <[email protected]> (v1)
-rw-r--r-- | src/intel/Makefile.am | 4 | ||||
-rw-r--r-- | src/intel/tools/aubinator_viewer.cpp | 1137 | ||||
-rw-r--r-- | src/intel/tools/aubinator_viewer.h | 71 | ||||
-rw-r--r-- | src/intel/tools/aubinator_viewer_decoder.cpp | 860 | ||||
-rw-r--r-- | src/intel/tools/imgui/imgui_memory_editor.h | 704 | ||||
-rw-r--r-- | src/intel/tools/meson.build | 12 |
6 files changed, 2788 insertions, 0 deletions
diff --git a/src/intel/Makefile.am b/src/intel/Makefile.am index f585221ac9d..cc43669d6be 100644 --- a/src/intel/Makefile.am +++ b/src/intel/Makefile.am @@ -69,6 +69,9 @@ EXTRA_DIST = \ dev/meson.build \ genxml/meson.build \ isl/meson.build \ + tools/aubinator_viewer.cpp \ + tools/aubinator_viewer.h \ + tools/aubinator_viewer_decoder.cpp \ tools/imgui/README \ tools/imgui/stb_textedit.h \ tools/imgui/meson.build \ @@ -82,6 +85,7 @@ EXTRA_DIST = \ tools/imgui/imgui_impl_gtk3.h \ tools/imgui/imgui_impl_opengl3.cpp \ tools/imgui/imgui_impl_opengl3.h \ + tools/imgui/imgui_memory_editor.h \ tools/imgui/stb_truetype.h \ tools/imgui/stb_rect_pack.h \ tools/imgui/LICENSE.txt \ diff --git a/src/intel/tools/aubinator_viewer.cpp b/src/intel/tools/aubinator_viewer.cpp new file mode 100644 index 00000000000..7ef56aa035f --- /dev/null +++ b/src/intel/tools/aubinator_viewer.cpp @@ -0,0 +1,1137 @@ +/* + * Copyright © 2016 Intel Corporation + * + * 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 (including the next + * paragraph) 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <getopt.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <ctype.h> + +#include "util/macros.h" + +#include "aub_read.h" +#include "aub_mem.h" + +#include "common/gen_disasm.h" + +#define xtzalloc(name) ((decltype(&name)) calloc(1, sizeof(name))) +#define xtalloc(name) ((decltype(&name)) malloc(sizeof(name))) + +struct aub_file { + uint8_t *map, *end, *cursor; + + uint16_t pci_id; + char app_name[33]; + + /* List of batch buffers to process */ + struct { + const uint8_t *start; + const uint8_t *end; + } *execs; + int n_execs; + int n_allocated_execs; + + uint32_t idx_reg_write; + + /* Device state */ + struct gen_device_info devinfo; + struct gen_spec *spec; + struct gen_disasm *disasm; +}; + +static void +store_exec_begin(struct aub_file *file) +{ + if (unlikely(file->n_execs >= file->n_allocated_execs)) { + file->n_allocated_execs = MAX2(2U * file->n_allocated_execs, + 4096 / sizeof(file->execs[0])); + file->execs = (decltype(file->execs)) + realloc(static_cast<void *>(file->execs), + file->n_allocated_execs * sizeof(file->execs[0])); + } + + file->execs[file->n_execs++].start = file->cursor; +} + +static void +store_exec_end(struct aub_file *file) +{ + if (file->n_execs > 0 && file->execs[file->n_execs - 1].end == NULL) + file->execs[file->n_execs - 1].end = file->cursor; +} + +static void +handle_mem_write(void *user_data, uint64_t phys_addr, + const void *data, uint32_t data_len) +{ + struct aub_file *file = (struct aub_file *) user_data; + file->idx_reg_write = 0; + store_exec_end(file); +} + +static void +handle_ring_write(void *user_data, enum gen_engine engine, + const void *ring_data, uint32_t ring_data_len) +{ + struct aub_file *file = (struct aub_file *) user_data; + file->idx_reg_write = 0; + store_exec_begin(file); +} + +static void +handle_reg_write(void *user_data, uint32_t reg_offset, uint32_t reg_value) +{ + struct aub_file *file = (struct aub_file *) user_data; + + /* Only store the first register write of a series (execlist writes take + * involve 2 dwords). + */ + if (file->idx_reg_write++ == 0) + store_exec_begin(file); +} + +static void +handle_info(void *user_data, int pci_id, const char *app_name) +{ + struct aub_file *file = (struct aub_file *) user_data; + store_exec_end(file); + + file->pci_id = pci_id; + snprintf(file->app_name, sizeof(app_name), "%s", app_name); + + if (!gen_get_device_info(file->pci_id, &file->devinfo)) { + fprintf(stderr, "can't find device information: pci_id=0x%x\n", file->pci_id); + exit(EXIT_FAILURE); + } + file->spec = gen_spec_load(&file->devinfo); + file->disasm = gen_disasm_create(&file->devinfo); +} + +static void +handle_error(void *user_data, const void *aub_data, const char *msg) +{ + fprintf(stderr, "ERROR: %s\n", msg); +} + +static struct aub_file * +aub_file_open(const char *filename) +{ + struct aub_file *file; + struct stat sb; + int fd; + + file = xtzalloc(*file); + fd = open(filename, O_RDWR); + if (fd == -1) { + fprintf(stderr, "open %s failed: %s\n", filename, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (fstat(fd, &sb) == -1) { + fprintf(stderr, "stat failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + file->map = (uint8_t *) mmap(NULL, sb.st_size, + PROT_READ, MAP_SHARED, fd, 0); + if (file->map == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + close(fd); + + file->cursor = file->map; + file->end = file->map + sb.st_size; + + struct aub_read aub_read = {}; + aub_read.user_data = file; + aub_read.info = handle_info; + aub_read.error = handle_error; + aub_read.reg_write = handle_reg_write; + aub_read.ring_write = handle_ring_write; + aub_read.local_write = handle_mem_write; + aub_read.phys_write = handle_mem_write; + aub_read.ggtt_write = handle_mem_write; + aub_read.ggtt_entry_write = handle_mem_write; + + int consumed; + while (file->cursor < file->end && + (consumed = aub_read_command(&aub_read, file->cursor, + file->end - file->cursor)) > 0) { + file->cursor += consumed; + } + + /* Ensure we have an end on the last register write. */ + if (file->n_execs > 0 && file->execs[file->n_execs - 1].end == NULL) + file->execs[file->n_execs - 1].end = file->end; + + return file; +} + +/**/ + +static void +update_mem_for_exec(struct aub_mem *mem, struct aub_file *file, int exec_idx) +{ + struct aub_read read = {}; + read.user_data = mem; + read.local_write = aub_mem_local_write; + read.phys_write = aub_mem_phys_write; + read.ggtt_write = aub_mem_ggtt_write; + read.ggtt_entry_write = aub_mem_ggtt_entry_write; + + /* Replay the aub file from the beginning up to just before the + * commands we want to read. where the context setup happens. + */ + const uint8_t *iter = file->map; + while (iter < file->execs[exec_idx].start) { + iter += aub_read_command(&read, iter, file->execs[exec_idx].start - iter); + } +} + +/* UI */ + +#include <epoxy/gl.h> + +#include "imgui.h" +#include "imgui_impl_gtk3.h" +#include "imgui_impl_opengl3.h" + +#include "aubinator_viewer.h" +#include "imgui_memory_editor.h" + +struct window { + struct list_head link; /* link in the global list of windows */ + struct list_head parent_link; /* link in parent window list of children */ + + struct list_head children_windows; /* list of children windows */ + + char name[128]; + bool opened; + + ImVec2 position; + ImVec2 size; + + void (*display)(struct window*); + void (*destroy)(struct window*); +}; + +struct edit_window { + struct window base; + + struct aub_mem *mem; + uint64_t address; + uint32_t len; + + struct gen_batch_decode_bo aub_bo; + uint64_t aub_offset; + + struct gen_batch_decode_bo gtt_bo; + uint64_t gtt_offset; + + struct MemoryEditor editor; +}; + +struct pml4_window { + struct window base; + + struct aub_mem *mem; +}; + +struct shader_window { + struct window base; + + uint64_t address; + char *shader; + size_t shader_size; +}; + +struct batch_window { + struct window base; + + struct aub_mem mem; + struct aub_read read; + + bool uses_ppgtt; + + bool collapsed; + int exec_idx; + + struct aub_viewer_decode_cfg decode_cfg; + struct aub_viewer_decode_ctx decode_ctx; + + struct pml4_window pml4_window; + + char edit_address[20]; +}; + +static struct Context { + struct aub_file *file; + char *input_file; + char *xml_path; + + GtkWidget *gtk_window; + + /* UI state*/ + bool show_commands_window; + bool show_registers_window; + + struct aub_viewer_cfg cfg; + + struct list_head windows; + + struct window file_window; + struct window commands_window; + struct window registers_window; +} context; + +static int +map_key(int k) +{ + return ImGuiKey_COUNT + k; +} + +static bool +has_ctrl_key(int key) +{ + return ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(map_key(key)); +} + +static bool +window_has_ctrl_key(int key) +{ + return ImGui::IsRootWindowOrAnyChildFocused() && has_ctrl_key(key); +} + +static void +destroy_window_noop(struct window *win) +{ +} + +/* Shader windows */ + +static void +display_shader_window(struct window *win) +{ + struct shader_window *window = (struct shader_window *) win; + + if (window->shader) { + ImGui::InputTextMultiline("Assembly", + window->shader, window->shader_size, + ImGui::GetContentRegionAvail(), + ImGuiInputTextFlags_ReadOnly); + } else { + ImGui::Text("Shader not available"); + } +} + +static void +destroy_shader_window(struct window *win) +{ + struct shader_window *window = (struct shader_window *) win; + + free(window->shader); + free(window); +} + +static struct shader_window * +new_shader_window(struct aub_mem *mem, uint64_t address, const char *desc) +{ + struct shader_window *window = xtzalloc(*window); + + snprintf(window->base.name, sizeof(window->base.name), + "%s (0x%lx)##%p", desc, address, window); + + list_inithead(&window->base.parent_link); + window->base.position = ImVec2(-1, -1); + window->base.size = ImVec2(700, 300); + window->base.opened = true; + window->base.display = display_shader_window; + window->base.destroy = destroy_shader_window; + + struct gen_batch_decode_bo shader_bo; + if (mem->pml4) + shader_bo = aub_mem_get_ppgtt_bo(mem, address); + else + shader_bo = aub_mem_get_ggtt_bo(mem, address); + + if (shader_bo.map) { + FILE *f = open_memstream(&window->shader, &window->shader_size); + if (f) { + gen_disasm_disassemble(context.file->disasm, shader_bo.map, 0, f); + fclose(f); + } + } + + list_addtail(&window->base.link, &context.windows); + + return window; +} + +/* Memory editor windows */ + +static uint8_t +read_edit_window(const uint8_t *data, size_t off) +{ + struct edit_window *window = (struct edit_window *) data; + + return *((const uint8_t *) window->gtt_bo.map + window->gtt_offset + off); +} + +static void +write_edit_window(uint8_t *data, size_t off, uint8_t d) +{ + struct edit_window *window = (struct edit_window *) data; + uint8_t *gtt = (uint8_t *) window->gtt_bo.map + window->gtt_offset + off; + uint8_t *aub = (uint8_t *) window->aub_bo.map + window->aub_offset + off; + + *gtt = *aub = d; +} + +static void +display_edit_window(struct window *win) +{ + struct edit_window *window = (struct edit_window *) win; + + if (window->aub_bo.map && window->gtt_bo.map) { + ImGui::BeginChild(ImGui::GetID("##block")); + window->editor.DrawContents((uint8_t *) window, + MIN3(window->len, + window->gtt_bo.size - window->gtt_offset, + window->aub_bo.size - window->aub_offset), + window->address); + ImGui::EndChild(); + } else { + ImGui::Text("Memory view at 0x%lx not available", window->address); + } +} + +static void +destroy_edit_window(struct window *win) +{ + struct edit_window *window = (struct edit_window *) win; + + if (window->aub_bo.map) + mprotect((void *) window->aub_bo.map, 4096, PROT_READ); + free(window); +} + +static struct edit_window * +new_edit_window(struct aub_mem *mem, uint64_t address, uint32_t len) +{ + struct edit_window *window = xtzalloc(*window); + + snprintf(window->base.name, sizeof(window->base.name), + "Editing aub at 0x%lx##%p", address, window); + + list_inithead(&window->base.parent_link); + window->base.position = ImVec2(-1, -1); + window->base.size = ImVec2(500, 600); + window->base.opened = true; + window->base.display = display_edit_window; + window->base.destroy = destroy_edit_window; + + window->mem = mem; + window->address = address; + window->aub_bo = aub_mem_get_ppgtt_addr_aub_data(mem, address); + window->gtt_bo = aub_mem_get_ppgtt_addr_data(mem, address); + window->len = len; + window->editor = MemoryEditor(); + window->editor.OptShowDataPreview = true; + window->editor.OptShowAscii = false; + window->editor.ReadFn = read_edit_window; + window->editor.WriteFn = write_edit_window; + + if (window->aub_bo.map) { + uint64_t unaligned_map = (uint64_t) window->aub_bo.map; + window->aub_bo.map = (const void *)(unaligned_map & ~0xffful); + window->aub_offset = unaligned_map - (uint64_t) window->aub_bo.map; + + if (mprotect((void *) window->aub_bo.map, window->aub_bo.size, PROT_READ | PROT_WRITE) != 0) { + window->aub_bo.map = NULL; + } + } + + window->gtt_offset = address - window->gtt_bo.addr; + + list_addtail(&window->base.link, &context.windows); + + return window; +} + +/* 4 level page table walk windows */ + +static void +display_pml4_level(struct aub_mem *mem, uint64_t table_addr, uint64_t table_virt_addr, int level) +{ + if (level == 0) + return; + + struct gen_batch_decode_bo table_bo = + aub_mem_get_phys_addr_data(mem, table_addr); + const uint64_t *table = (const uint64_t *) ((const uint8_t *) table_bo.map + + table_addr - table_bo.addr); + if (!table) { + ImGui::TextColored(context.cfg.missing_color, "Page not available"); + return; + } + + uint64_t addr_increment = 1ULL << (12 + 9 * (level - 1)); + + if (level == 1) { + for (int e = 0; e < 512; e++) { + bool available = (table[e] & 1) != 0; + uint64_t entry_virt_addr = table_virt_addr + e * addr_increment; + if (!available) + continue; + ImGui::Text("Entry%03i - phys_addr=0x%lx - virt_addr=0x%lx", + e, table[e], entry_virt_addr); + } + } else { + for (int e = 0; e < 512; e++) { + bool available = (table[e] & 1) != 0; + uint64_t entry_virt_addr = table_virt_addr + e * addr_increment; + if (available && + ImGui::TreeNodeEx(&table[e], + available ? ImGuiTreeNodeFlags_Framed : 0, + "Entry%03i - phys_addr=0x%lx - virt_addr=0x%lx", + e, table[e], entry_virt_addr)) { + display_pml4_level(mem, table[e] & ~0xffful, entry_virt_addr, level -1); + ImGui::TreePop(); + } + } + } +} + +static void +display_pml4_window(struct window *win) +{ + struct pml4_window *window = (struct pml4_window *) win; + + ImGui::Text("pml4: %lx", window->mem->pml4); + ImGui::BeginChild(ImGui::GetID("##block")); + display_pml4_level(window->mem, window->mem->pml4, 0, 4); + ImGui::EndChild(); +} + +static void +show_pml4_window(struct pml4_window *window, struct aub_mem *mem) +{ + if (window->base.opened) { + window->base.opened = false; + return; + } + + snprintf(window->base.name, sizeof(window->base.name), + "4-Level page tables##%p", window); + + list_inithead(&window->base.parent_link); + window->base.position = ImVec2(-1, -1); + window->base.size = ImVec2(500, 600); + window->base.opened = true; + window->base.display = display_pml4_window; + window->base.destroy = destroy_window_noop; + + window->mem = mem; + + list_addtail(&window->base.link, &context.windows); +} + +/* Batch decoding windows */ + +static void +display_decode_options(struct aub_viewer_decode_cfg *cfg) +{ + char name[40]; + snprintf(name, sizeof(name), "command filter##%p", &cfg->command_filter); + cfg->command_filter.Draw(name); ImGui::SameLine(); + snprintf(name, sizeof(name), "field filter##%p", &cfg->field_filter); + cfg->field_filter.Draw(name); ImGui::SameLine(); + if (ImGui::Button("Dwords")) cfg->show_dwords ^= 1; +} + +static void +batch_display_shader(void *user_data, const char *shader_desc, uint64_t address) +{ + struct batch_window *window = (struct batch_window *) user_data; + struct shader_window *shader_window = + new_shader_window(&window->mem, address, shader_desc); + + list_add(&shader_window->base.parent_link, &window->base.children_windows); +} + +static void +batch_edit_address(void *user_data, uint64_t address, uint32_t len) +{ + struct batch_window *window = (struct batch_window *) user_data; + struct edit_window *edit_window = + new_edit_window(&window->mem, address, len); + + list_add(&edit_window->base.parent_link, &window->base.children_windows); +} + +static struct gen_batch_decode_bo +batch_get_bo(void *user_data, uint64_t address) +{ + struct batch_window *window = (struct batch_window *) user_data; + + if (window->uses_ppgtt) + return aub_mem_get_ppgtt_bo(&window->mem, address); + else + return aub_mem_get_ggtt_bo(&window->mem, address); +} + +static void +update_batch_window(struct batch_window *window, bool reset, int exec_idx) +{ + if (reset) + aub_mem_fini(&window->mem); + aub_mem_init(&window->mem); + + window->exec_idx = MAX2(MIN2(context.file->n_execs - 1, exec_idx), 0); + update_mem_for_exec(&window->mem, context.file, window->exec_idx); +} + +static void +display_batch_ring_write(void *user_data, enum gen_engine engine, + const void *data, uint32_t data_len) +{ + struct batch_window *window = (struct batch_window *) user_data; + + window->uses_ppgtt = false; + + aub_viewer_render_batch(&window->decode_ctx, data, data_len, 0); +} + +static void +display_batch_execlist_write(void *user_data, enum gen_engine engine, + uint64_t context_descriptor) +{ + struct batch_window *window = (struct batch_window *) user_data; + + const uint32_t pphwsp_size = 4096; + uint32_t pphwsp_addr = context_descriptor & 0xfffff000; + struct gen_batch_decode_bo pphwsp_bo = + aub_mem_get_ggtt_bo(&window->mem, pphwsp_addr); + uint32_t *context_img = (uint32_t *)((uint8_t *)pphwsp_bo.map + + (pphwsp_addr - pphwsp_bo.addr) + + pphwsp_size); + + uint32_t ring_buffer_head = context_img[5]; + uint32_t ring_buffer_tail = context_img[7]; + uint32_t ring_buffer_start = context_img[9]; + + window->mem.pml4 = (uint64_t)context_img[49] << 32 | context_img[51]; + + struct gen_batch_decode_bo ring_bo = + aub_mem_get_ggtt_bo(&window->mem, ring_buffer_start); + assert(ring_bo.size > 0); + void *commands = (uint8_t *)ring_bo.map + (ring_buffer_start - ring_bo.addr); + + window->uses_ppgtt = true; + + aub_viewer_render_batch(&window->decode_ctx, commands, + ring_buffer_tail - ring_buffer_head, + ring_buffer_start); +} + +static void +display_batch_window(struct window *win) +{ + struct batch_window *window = (struct batch_window *) win; + + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() / (2 * 2)); + if (window_has_ctrl_key('f')) ImGui::SetKeyboardFocusHere(); + display_decode_options(&window->decode_cfg); + ImGui::PopItemWidth(); + + if (ImGui::InputInt("Execbuf", &window->exec_idx)) + update_batch_window(window, true, window->exec_idx); + + if (window_has_ctrl_key('p')) + update_batch_window(window, true, window->exec_idx - 1); + if (window_has_ctrl_key('n')) + update_batch_window(window, true, window->exec_idx + 1); + + ImGui::Text("execbuf %i", window->exec_idx); + if (ImGui::Button("Show PPGTT")) { show_pml4_window(&window->pml4_window, &window->mem); } + + ImGui::BeginChild(ImGui::GetID("##block")); + + struct aub_read read = {}; + read.user_data = window; + read.ring_write = display_batch_ring_write; + read.execlist_write = display_batch_execlist_write; + + const uint8_t *iter = context.file->execs[window->exec_idx].start; + while (iter < context.file->execs[window->exec_idx].end) { + iter += aub_read_command(&read, iter, + context.file->execs[window->exec_idx].end - iter); + } + + ImGui::EndChild(); +} + +static void +destroy_batch_window(struct window *win) +{ + struct batch_window *window = (struct batch_window *) win; + + aub_mem_fini(&window->mem); + + /* This works because children windows are inserted at the back of the + * list, ensuring the deletion loop goes through the children after calling + * this function. + */ + list_for_each_entry(struct window, child_window, + &window->base.children_windows, parent_link) + child_window->opened = false; + window->pml4_window.base.opened = false; + + free(window); +} + +static void +new_batch_window(int exec_idx) +{ + struct batch_window *window = xtzalloc(*window); + + snprintf(window->base.name, sizeof(window->base.name), + "Batch view##%p", window); + + list_inithead(&window->base.parent_link); + list_inithead(&window->base.children_windows); + window->base.position = ImVec2(-1, -1); + window->base.size = ImVec2(600, 700); + window->base.opened = true; + window->base.display = display_batch_window; + window->base.destroy = destroy_batch_window; + + window->collapsed = true; + window->decode_cfg = aub_viewer_decode_cfg(); + + aub_viewer_decode_ctx_init(&window->decode_ctx, + &context.cfg, + &window->decode_cfg, + context.file->spec, + context.file->disasm, + batch_get_bo, + NULL, + window); + window->decode_ctx.display_shader = batch_display_shader; + window->decode_ctx.edit_address = batch_edit_address; + + update_batch_window(window, false, exec_idx); + + list_addtail(&window->base.link, &context.windows); +} + +/**/ + +static void +display_registers_window(struct window *win) +{ + static struct ImGuiTextFilter filter; + if (window_has_ctrl_key('f')) ImGui::SetKeyboardFocusHere(); + filter.Draw(); + + ImGui::BeginChild(ImGui::GetID("##block")); + struct hash_entry *entry; + hash_table_foreach(context.file->spec->registers_by_name, entry) { + struct gen_group *reg = (struct gen_group *) entry->data; + if (filter.PassFilter(reg->name) && + ImGui::CollapsingHeader(reg->name)) { + const struct gen_field *field = reg->fields; + while (field) { + ImGui::Text("%s : %i -> %i\n", field->name, field->start, field->end); + field = field->next; + } + } + } + ImGui::EndChild(); +} + +static void +show_register_window(void) +{ + struct window *window = &context.registers_window; + + if (window->opened) { + window->opened = false; + return; + } + + snprintf(window->name, sizeof(window->name), "Registers"); + + list_inithead(&window->parent_link); + window->position = ImVec2(-1, -1); + window->size = ImVec2(200, 400); + window->opened = true; + window->display = display_registers_window; + window->destroy = destroy_window_noop; + + list_addtail(&window->link, &context.windows); +} + +static void +display_commands_window(struct window *win) +{ + static struct ImGuiTextFilter cmd_filter; + if (window_has_ctrl_key('f')) ImGui::SetKeyboardFocusHere(); + cmd_filter.Draw("name filter"); + static struct ImGuiTextFilter field_filter; + field_filter.Draw("field filter"); + + static char opcode_str[9] = { 0, }; + ImGui::InputText("opcode filter", opcode_str, sizeof(opcode_str), + ImGuiInputTextFlags_CharsHexadecimal); + size_t opcode_len = strlen(opcode_str); + uint64_t opcode = strtol(opcode_str, NULL, 16); + + static bool show_dwords = true; + if (ImGui::Button("Dwords")) show_dwords ^= 1; + + ImGui::BeginChild(ImGui::GetID("##block")); + struct hash_entry *entry; + hash_table_foreach(context.file->spec->commands, entry) { + struct gen_group *cmd = (struct gen_group *) entry->data; + if ((cmd_filter.PassFilter(cmd->name) && + (opcode_len == 0 || (opcode & cmd->opcode_mask) == cmd->opcode)) && + ImGui::CollapsingHeader(cmd->name)) { + const struct gen_field *field = cmd->fields; + int32_t last_dword = -1; + while (field) { + if (show_dwords && field->start / 32 != last_dword) { + for (last_dword = MAX2(0, last_dword + 1); + last_dword < field->start / 32; last_dword++) { + ImGui::TextColored(context.cfg.dwords_color, + "Dword %d", last_dword); + } + ImGui::TextColored(context.cfg.dwords_color, "Dword %d", last_dword); + } + if (field_filter.PassFilter(field->name)) + ImGui::Text("%s : %i -> %i\n", field->name, field->start, field->end); + field = field->next; + } + } + } + hash_table_foreach(context.file->spec->structs, entry) { + struct gen_group *cmd = (struct gen_group *) entry->data; + if (cmd_filter.PassFilter(cmd->name) && opcode_len == 0 && + ImGui::CollapsingHeader(cmd->name)) { + const struct gen_field *field = cmd->fields; + int32_t last_dword = -1; + while (field) { + if (show_dwords && field->start / 32 != last_dword) { + last_dword = field->start / 32; + ImGui::TextColored(context.cfg.dwords_color, + "Dword %d", last_dword); + } + if (field_filter.PassFilter(field->name)) + ImGui::Text("%s : %i -> %i\n", field->name, field->start, field->end); + field = field->next; + } + } + } + ImGui::EndChild(); +} + +static void +show_commands_window(void) +{ + struct window *window = &context.commands_window; + + if (window->opened) { + window->opened = false; + return; + } + + snprintf(window->name, sizeof(window->name), "Commands & structs"); + + list_inithead(&window->parent_link); + window->position = ImVec2(-1, -1); + window->size = ImVec2(300, 400); + window->opened = true; + window->display = display_commands_window; + window->destroy = destroy_window_noop; + + list_addtail(&window->link, &context.windows); +} + +/* Main window */ + +static const char * +human_size(size_t size) +{ + unsigned divisions = 0; + double v = size; + double divider = 1024; + while (v >= divider) { + v /= divider; + divisions++; + } + + static const char *units[] = { "Bytes", "Kilobytes", "Megabytes", "Gigabytes" }; + static char result[20]; + snprintf(result, sizeof(result), "%.2f %s", + v, divisions >= ARRAY_SIZE(units) ? "Too much!" : units[divisions]); + return result; +} + +static void +display_aubfile_window(struct window *win) +{ + ImGuiColorEditFlags cflags = (ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoLabel | + ImGuiColorEditFlags_NoInputs); + struct aub_viewer_cfg *cfg = &context.cfg; + + ImGui::ColorEdit3("background", (float *)&cfg->clear_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("missing", (float *)&cfg->missing_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("error", (float *)&cfg->error_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("highlight", (float *)&cfg->highlight_color, cflags); ImGui::SameLine(); + ImGui::ColorEdit3("dwords", (float *)&cfg->dwords_color, cflags); ImGui::SameLine(); + + if (ImGui::Button("Commands list") || has_ctrl_key('c')) { show_commands_window(); } ImGui::SameLine(); + if (ImGui::Button("Registers list") || has_ctrl_key('r')) { show_register_window(); } ImGui::SameLine(); + if (ImGui::Button("Help") || has_ctrl_key('h')) { ImGui::OpenPopup("Help"); } + + if (ImGui::Button("New batch window") || has_ctrl_key('b')) { new_batch_window(0); } + + ImGui::Text("File name: %s", context.input_file); + ImGui::Text("File size: %s", human_size(context.file->end - context.file->map)); + ImGui::Text("Execbufs %u", context.file->n_execs); + ImGui::Text("PCI ID: 0x%x", context.file->pci_id); + ImGui::Text("Application name: %s", context.file->app_name); + ImGui::Text(gen_get_device_name(context.file->pci_id)); + + ImGui::SetNextWindowContentWidth(500); + if (ImGui::BeginPopupModal("Help", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Some global keybindings:"); + ImGui::Separator(); + + static const char *texts[] = { + "Ctrl-h", "show this screen", + "Ctrl-c", "show commands list", + "Ctrl-r", "show registers list", + "Ctrl-b", "new batch window", + "Ctrl-p/n", "switch to previous/next batch buffer", + "Ctrl-Tab", "switch focus between window", + "Ctrl-left/right", "align window to the side of the screen", + }; + float align = 0.0f; + for (uint32_t i = 0; i < ARRAY_SIZE(texts); i += 2) + align = MAX2(align, ImGui::CalcTextSize(texts[i]).x); + align += ImGui::GetStyle().WindowPadding.x + 10; + + for (uint32_t i = 0; i < ARRAY_SIZE(texts); i += 2) { + ImGui::Text(texts[i]); ImGui::SameLine(align); ImGui::Text(texts[i + 1]); + } + + if (ImGui::Button("Done") || ImGui::IsKeyPressed(ImGuiKey_Escape)) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } +} + +static void +show_aubfile_window(void) +{ + struct window *window = &context.file_window; + + if (window->opened) + return; + + snprintf(window->name, sizeof(window->name), + "Aubinator Viewer: Intel AUB file decoder/editor"); + + list_inithead(&window->parent_link); + window->size = ImVec2(-1, 250); + window->position = + ImVec2(0, ImGui::GetIO().DisplaySize.y - window->size.y); + window->opened = true; + window->display = display_aubfile_window; + window->destroy = NULL; + + list_addtail(&window->link, &context.windows); +} + +/* Main redrawing */ + +static void +display_windows(void) +{ + /* Start by disposing closed windows, we don't want to destroy windows that + * have already been scheduled to be painted. So destroy always happens on + * the next draw cycle, prior to any drawing. + */ + list_for_each_entry_safe(struct window, window, &context.windows, link) { + if (window->opened) + continue; + + /* Can't close this one. */ + if (window == &context.file_window) { + window->opened = true; + continue; + } + + list_del(&window->link); + list_del(&window->parent_link); + if (window->destroy) + window->destroy(window); + } + + list_for_each_entry(struct window, window, &context.windows, link) { + ImGui::SetNextWindowPos(window->position, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(window->size, ImGuiCond_FirstUseEver); + if (ImGui::Begin(window->name, &window->opened)) { + window->display(window); + window->position = ImGui::GetWindowPos(); + window->size = ImGui::GetWindowSize(); + } + if (window_has_ctrl_key('w')) + window->opened = false; + ImGui::End(); + } +} + +static void +repaint_area(GtkGLArea *area, GdkGLContext *gdk_gl_context) +{ + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGtk3_NewFrame(); + ImGui::NewFrame(); + + display_windows(); + + ImGui::EndFrame(); + ImGui::Render(); + + glClearColor(context.cfg.clear_color.Value.x, + context.cfg.clear_color.Value.y, + context.cfg.clear_color.Value.z, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} + +static void +realize_area(GtkGLArea *area) +{ + ImGui::CreateContext(); + ImGui_ImplGtk3_Init(GTK_WIDGET(area), true); + ImGui_ImplOpenGL3_Init("#version 130"); + + list_inithead(&context.windows); + + ImGui::StyleColorsDark(); + context.cfg = aub_viewer_cfg(); + + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; +} + +static void +unrealize_area(GtkGLArea *area) +{ + gtk_gl_area_make_current(area); + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGtk3_Shutdown(); + ImGui::DestroyContext(); +} + +static void +print_help(const char *progname, FILE *file) +{ + fprintf(file, + "Usage: %s [OPTION]... FILE\n" + "Decode aub file contents from FILE.\n\n" + " --help display this help and exit\n" + " -x, --xml=DIR load hardware xml description from directory DIR\n", + progname); +} + +int main(int argc, char *argv[]) +{ + int c, i; + bool help = false; + const struct option aubinator_opts[] = { + { "help", no_argument, (int *) &help, true }, + { "xml", required_argument, NULL, 'x' }, + { NULL, 0, NULL, 0 } + }; + + memset(&context, 0, sizeof(context)); + + i = 0; + while ((c = getopt_long(argc, argv, "x:s:", aubinator_opts, &i)) != -1) { + switch (c) { + case 'x': + context.xml_path = strdup(optarg); + break; + default: + break; + } + } + + if (optind < argc) + context.input_file = argv[optind]; + + if (help || !context.input_file) { + print_help(argv[0], stderr); + exit(0); + } + + context.file = aub_file_open(context.input_file); + + gtk_init(NULL, NULL); + + context.gtk_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(context.gtk_window), "Aubinator Viewer"); + g_signal_connect(context.gtk_window, "delete-event", G_CALLBACK(gtk_main_quit), NULL); + gtk_window_resize(GTK_WINDOW(context.gtk_window), 1280, 720); + + GtkWidget* gl_area = gtk_gl_area_new(); + g_signal_connect(gl_area, "render", G_CALLBACK(repaint_area), NULL); + g_signal_connect(gl_area, "realize", G_CALLBACK(realize_area), NULL); + g_signal_connect(gl_area, "unrealize", G_CALLBACK(unrealize_area), NULL); + gtk_container_add(GTK_CONTAINER(context.gtk_window), gl_area); + + gtk_widget_show_all(context.gtk_window); + + show_aubfile_window(); + + gtk_main(); + + free(context.xml_path); + + return EXIT_SUCCESS; +} diff --git a/src/intel/tools/aubinator_viewer.h b/src/intel/tools/aubinator_viewer.h new file mode 100644 index 00000000000..2d89d9cf658 --- /dev/null +++ b/src/intel/tools/aubinator_viewer.h @@ -0,0 +1,71 @@ +#ifndef AUBINATOR_VIEWER_H +#define AUBINATOR_VIEWER_H + +#include "imgui.h" + +#include "common/gen_decoder.h" +#include "common/gen_disasm.h" + +struct aub_viewer_cfg { + ImColor clear_color; + ImColor dwords_color; + ImColor highlight_color; + ImColor error_color; + ImColor missing_color; + + aub_viewer_cfg() : + clear_color(114, 144, 154), + dwords_color(29, 177, 194, 255), + highlight_color(0, 230, 0, 255), + error_color(236, 255, 0, 255), + missing_color(230, 0, 230, 255) {} +}; + +struct aub_viewer_decode_cfg { + struct ImGuiTextFilter command_filter; + struct ImGuiTextFilter field_filter; + + bool drop_filtered; + bool show_dwords; + + aub_viewer_decode_cfg() : + drop_filtered(false), + show_dwords(true) {} +}; + +struct aub_viewer_decode_ctx { + struct gen_batch_decode_bo (*get_bo)(void *user_data, uint64_t address); + unsigned (*get_state_size)(void *user_data, + uint32_t offset_from_dynamic_state_base_addr); + + void (*display_shader)(void *user_data, const char *shader_desc, uint64_t address); + void (*edit_address)(void *user_data, uint64_t address, uint32_t length); + + void *user_data; + + struct gen_spec *spec; + struct gen_disasm *disasm; + + struct aub_viewer_cfg *cfg; + struct aub_viewer_decode_cfg *decode_cfg; + + uint64_t surface_base; + uint64_t dynamic_base; + uint64_t instruction_base; + +}; + +void aub_viewer_decode_ctx_init(struct aub_viewer_decode_ctx *ctx, + struct aub_viewer_cfg *cfg, + struct aub_viewer_decode_cfg *decode_cfg, + struct gen_spec *spec, + struct gen_disasm *disasm, + struct gen_batch_decode_bo (*get_bo)(void *, uint64_t), + unsigned (*get_state_size)(void *, uint32_t), + void *user_data); + +void aub_viewer_render_batch(struct aub_viewer_decode_ctx *ctx, + const void *batch, uint32_t batch_size, + uint64_t batch_addr); + +#endif /* AUBINATOR_VIEWER_H */ diff --git a/src/intel/tools/aubinator_viewer_decoder.cpp b/src/intel/tools/aubinator_viewer_decoder.cpp new file mode 100644 index 00000000000..a2ea3ba4a64 --- /dev/null +++ b/src/intel/tools/aubinator_viewer_decoder.cpp @@ -0,0 +1,860 @@ +/* + * Copyright © 2017 Intel Corporation + * + * 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 (including the next + * paragraph) 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. + */ + +#include <string.h> + +#include "util/macros.h" + +#include "aubinator_viewer.h" + +void +aub_viewer_decode_ctx_init(struct aub_viewer_decode_ctx *ctx, + struct aub_viewer_cfg *cfg, + struct aub_viewer_decode_cfg *decode_cfg, + struct gen_spec *spec, + struct gen_disasm *disasm, + struct gen_batch_decode_bo (*get_bo)(void *, uint64_t), + unsigned (*get_state_size)(void *, uint32_t), + void *user_data) +{ + memset(ctx, 0, sizeof(*ctx)); + + ctx->get_bo = get_bo; + ctx->get_state_size = get_state_size; + ctx->user_data = user_data; + + ctx->cfg = cfg; + ctx->decode_cfg = decode_cfg; + ctx->spec = spec; + ctx->disasm = disasm; +} + +static void +aub_viewer_print_group(struct aub_viewer_decode_ctx *ctx, + struct gen_group *group, + uint64_t address, const void *map) +{ + struct gen_field_iterator iter; + int last_dword = -1; + const uint32_t *p = (const uint32_t *) map; + + gen_field_iterator_init(&iter, group, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (ctx->decode_cfg->show_dwords) { + int iter_dword = iter.end_bit / 32; + if (last_dword != iter_dword) { + for (int i = last_dword + 1; i <= iter_dword; i++) { + ImGui::TextColored(ctx->cfg->dwords_color, + "0x%08" PRIx64 ": 0x%08x : Dword %d", + address + 4 * i, iter.p[i], i); + } + last_dword = iter_dword; + } + } + if (!gen_field_is_header(iter.field)) { + if (ctx->decode_cfg->field_filter.PassFilter(iter.name)) { + ImGui::Text("%s: %s", iter.name, iter.value); + if (iter.struct_desc) { + int struct_dword = iter.start_bit / 32; + uint64_t struct_address = address + 4 * struct_dword; + aub_viewer_print_group(ctx, iter.struct_desc, struct_address, + &p[struct_dword]); + } + } + } + } +} + +static struct gen_batch_decode_bo +ctx_get_bo(struct aub_viewer_decode_ctx *ctx, uint64_t addr) +{ + if (gen_spec_get_gen(ctx->spec) >= gen_make_gen(8,0)) { + /* On Broadwell and above, we have 48-bit addresses which consume two + * dwords. Some packets require that these get stored in a "canonical + * form" which means that bit 47 is sign-extended through the upper + * bits. In order to correctly handle those aub dumps, we need to mask + * off the top 16 bits. + */ + addr &= (~0ull >> 16); + } + + struct gen_batch_decode_bo bo = ctx->get_bo(ctx->user_data, addr); + + if (gen_spec_get_gen(ctx->spec) >= gen_make_gen(8,0)) + bo.addr &= (~0ull >> 16); + + /* We may actually have an offset into the bo */ + if (bo.map != NULL) { + assert(bo.addr <= addr); + uint64_t offset = addr - bo.addr; + bo.map = (const uint8_t *)bo.map + offset; + bo.addr += offset; + bo.size -= offset; + } + + return bo; +} + +static int +update_count(struct aub_viewer_decode_ctx *ctx, + uint32_t offset_from_dsba, + unsigned element_dwords, + unsigned guess) +{ + unsigned size = 0; + + if (ctx->get_state_size) + size = ctx->get_state_size(ctx->user_data, offset_from_dsba); + + if (size > 0) + return size / (sizeof(uint32_t) * element_dwords); + + /* In the absence of any information, just guess arbitrarily. */ + return guess; +} + +static void +ctx_disassemble_program(struct aub_viewer_decode_ctx *ctx, + uint32_t ksp, const char *type) +{ + uint64_t addr = ctx->instruction_base + ksp; + struct gen_batch_decode_bo bo = ctx_get_bo(ctx, addr); + if (!bo.map) { + ImGui::TextColored(ctx->cfg->missing_color, "Shader unavailable"); + return; + } + + ImGui::PushID(addr); + if (ImGui::Button(type) && ctx->display_shader) + ctx->display_shader(ctx->user_data, type, addr); + ImGui::PopID(); +} + +static void +handle_state_base_address(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + + while (gen_field_iterator_next(&iter)) { + if (strcmp(iter.name, "Surface State Base Address") == 0) { + ctx->surface_base = iter.raw_value; + } else if (strcmp(iter.name, "Dynamic State Base Address") == 0) { + ctx->dynamic_base = iter.raw_value; + } else if (strcmp(iter.name, "Instruction Base Address") == 0) { + ctx->instruction_base = iter.raw_value; + } + } +} + +static void +dump_binding_table(struct aub_viewer_decode_ctx *ctx, uint32_t offset, int count) +{ + struct gen_group *strct = + gen_spec_find_struct(ctx->spec, "RENDER_SURFACE_STATE"); + if (strct == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, "did not find RENDER_SURFACE_STATE info"); + return; + } + + if (count < 0) + count = update_count(ctx, offset, 1, 8); + + if (offset % 32 != 0 || offset >= UINT16_MAX) { + ImGui::TextColored(ctx->cfg->missing_color, "invalid binding table pointer"); + return; + } + + struct gen_batch_decode_bo bind_bo = + ctx_get_bo(ctx, ctx->surface_base + offset); + + if (bind_bo.map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "binding table unavailable addr=0x%08" PRIx64, + ctx->surface_base + offset); + return; + } + + const uint32_t *pointers = (const uint32_t *) bind_bo.map; + for (int i = 0; i < count; i++) { + if (pointers[i] == 0) + continue; + + uint64_t addr = ctx->surface_base + pointers[i]; + struct gen_batch_decode_bo bo = ctx_get_bo(ctx, addr); + uint32_t size = strct->dw_length * 4; + + if (pointers[i] % 32 != 0 || + addr < bo.addr || addr + size >= bo.addr + bo.size) { + ImGui::TextColored(ctx->cfg->missing_color, + "pointer %u: %08x <not valid>", i, pointers[i]); + continue; + } + + ImGui::Text("pointer %u: %08x", i, pointers[i]); + aub_viewer_print_group(ctx, strct, addr, (const uint8_t *) bo.map + (addr - bo.addr)); + } +} + +static void +dump_samplers(struct aub_viewer_decode_ctx *ctx, uint32_t offset, int count) +{ + struct gen_group *strct = gen_spec_find_struct(ctx->spec, "SAMPLER_STATE"); + + if (count < 0) + count = update_count(ctx, offset, strct->dw_length, 4); + + uint64_t state_addr = ctx->dynamic_base + offset; + struct gen_batch_decode_bo bo = ctx_get_bo(ctx, state_addr); + const uint8_t *state_map = (const uint8_t *) bo.map; + + if (state_map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "samplers unavailable addr=0x%08" PRIx64, state_addr); + return; + } + + if (offset % 32 != 0 || state_addr - bo.addr >= bo.size) { + ImGui::TextColored(ctx->cfg->missing_color, "invalid sampler state pointer"); + return; + } + + for (int i = 0; i < count; i++) { + ImGui::Text("sampler state %d", i); + aub_viewer_print_group(ctx, strct, state_addr, state_map); + state_addr += 16; + state_map += 16; + } +} + +static void +handle_media_interface_descriptor_load(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + struct gen_group *desc = + gen_spec_find_struct(ctx->spec, "INTERFACE_DESCRIPTOR_DATA"); + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + uint32_t descriptor_offset = 0; + int descriptor_count = 0; + while (gen_field_iterator_next(&iter)) { + if (strcmp(iter.name, "Interface Descriptor Data Start Address") == 0) { + descriptor_offset = strtol(iter.value, NULL, 16); + } else if (strcmp(iter.name, "Interface Descriptor Total Length") == 0) { + descriptor_count = + strtol(iter.value, NULL, 16) / (desc->dw_length * 4); + } + } + + uint64_t desc_addr = ctx->dynamic_base + descriptor_offset; + struct gen_batch_decode_bo bo = ctx_get_bo(ctx, desc_addr); + const uint32_t *desc_map = (const uint32_t *) bo.map; + + if (desc_map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "interface descriptors unavailable addr=0x%08" PRIx64, desc_addr); + return; + } + + for (int i = 0; i < descriptor_count; i++) { + ImGui::Text("descriptor %d: %08x", i, descriptor_offset); + + aub_viewer_print_group(ctx, desc, desc_addr, desc_map); + + gen_field_iterator_init(&iter, desc, desc_map, 0, false); + uint64_t ksp = 0; + uint32_t sampler_offset = 0, sampler_count = 0; + uint32_t binding_table_offset = 0, binding_entry_count = 0; + while (gen_field_iterator_next(&iter)) { + if (strcmp(iter.name, "Kernel Start Pointer") == 0) { + ksp = strtoll(iter.value, NULL, 16); + } else if (strcmp(iter.name, "Sampler State Pointer") == 0) { + sampler_offset = strtol(iter.value, NULL, 16); + } else if (strcmp(iter.name, "Sampler Count") == 0) { + sampler_count = strtol(iter.value, NULL, 10); + } else if (strcmp(iter.name, "Binding Table Pointer") == 0) { + binding_table_offset = strtol(iter.value, NULL, 16); + } else if (strcmp(iter.name, "Binding Table Entry Count") == 0) { + binding_entry_count = strtol(iter.value, NULL, 10); + } + } + + ctx_disassemble_program(ctx, ksp, "compute shader"); + + dump_samplers(ctx, sampler_offset, sampler_count); + dump_binding_table(ctx, binding_table_offset, binding_entry_count); + + desc_map += desc->dw_length; + desc_addr += desc->dw_length * 4; + } +} + +static void +handle_3dstate_vertex_buffers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + struct gen_group *vbs = gen_spec_find_struct(ctx->spec, "VERTEX_BUFFER_STATE"); + + struct gen_batch_decode_bo vb = {}; + uint32_t vb_size = 0; + int index = -1; + int pitch = -1; + bool ready = false; + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (iter.struct_desc != vbs) + continue; + + uint64_t buffer_addr; + + struct gen_field_iterator vbs_iter; + gen_field_iterator_init(&vbs_iter, vbs, &iter.p[iter.start_bit / 32], 0, false); + while (gen_field_iterator_next(&vbs_iter)) { + if (strcmp(vbs_iter.name, "Vertex Buffer Index") == 0) { + index = vbs_iter.raw_value; + } else if (strcmp(vbs_iter.name, "Buffer Pitch") == 0) { + pitch = vbs_iter.raw_value; + } else if (strcmp(vbs_iter.name, "Buffer Starting Address") == 0) { + buffer_addr = vbs_iter.raw_value; + vb = ctx_get_bo(ctx, buffer_addr); + } else if (strcmp(vbs_iter.name, "Buffer Size") == 0) { + vb_size = vbs_iter.raw_value; + ready = true; + } else if (strcmp(vbs_iter.name, "End Address") == 0) { + if (vb.map && vbs_iter.raw_value >= vb.addr) + vb_size = vbs_iter.raw_value - vb.addr; + else + vb_size = 0; + ready = true; + } + + if (!ready) + continue; + + ImGui::Text("vertex buffer %d, size %d", index, vb_size); + + if (vb.map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "buffer contents unavailable addr=0x%08" PRIx64, buffer_addr); + continue; + } + + if (vb.map == 0 || vb_size == 0) + continue; + + vb.map = NULL; + vb_size = 0; + index = -1; + pitch = -1; + ready = false; + } + } +} + +static void +handle_3dstate_index_buffer(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + struct gen_batch_decode_bo ib = {}; + uint64_t buffer_addr = 0; + uint32_t ib_size = 0; + uint32_t format = 0; + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (strcmp(iter.name, "Index Format") == 0) { + format = iter.raw_value; + } else if (strcmp(iter.name, "Buffer Starting Address") == 0) { + buffer_addr = iter.raw_value; + ib = ctx_get_bo(ctx, buffer_addr); + } else if (strcmp(iter.name, "Buffer Size") == 0) { + ib_size = iter.raw_value; + } + } + + if (ib.map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "buffer contents unavailable addr=0x%08" PRIx64, + buffer_addr); + return; + } + + const uint8_t *m = (const uint8_t *) ib.map; + const uint8_t *ib_end = m + MIN2(ib.size, ib_size); + for (int i = 0; m < ib_end && i < 10; i++) { + switch (format) { + case 0: + m += 1; + break; + case 1: + m += 2; + break; + case 2: + m += 4; + break; + } + } +} + +static void +decode_single_ksp(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + uint64_t ksp = 0; + bool is_simd8 = false; /* vertex shaders on Gen8+ only */ + bool is_enabled = true; + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (strcmp(iter.name, "Kernel Start Pointer") == 0) { + ksp = iter.raw_value; + } else if (strcmp(iter.name, "SIMD8 Dispatch Enable") == 0) { + is_simd8 = iter.raw_value; + } else if (strcmp(iter.name, "Dispatch Mode") == 0) { + is_simd8 = strcmp(iter.value, "SIMD8") == 0; + } else if (strcmp(iter.name, "Dispatch Enable") == 0) { + is_simd8 = strcmp(iter.value, "SIMD8") == 0; + } else if (strcmp(iter.name, "Enable") == 0) { + is_enabled = iter.raw_value; + } + } + + const char *type = + strcmp(inst->name, "VS_STATE") == 0 ? "vertex shader" : + strcmp(inst->name, "GS_STATE") == 0 ? "geometry shader" : + strcmp(inst->name, "SF_STATE") == 0 ? "strips and fans shader" : + strcmp(inst->name, "CLIP_STATE") == 0 ? "clip shader" : + strcmp(inst->name, "3DSTATE_DS") == 0 ? "tessellation evaluation shader" : + strcmp(inst->name, "3DSTATE_HS") == 0 ? "tessellation control shader" : + strcmp(inst->name, "3DSTATE_VS") == 0 ? (is_simd8 ? "SIMD8 vertex shader" : "vec4 vertex shader") : + strcmp(inst->name, "3DSTATE_GS") == 0 ? (is_simd8 ? "SIMD8 geometry shader" : "vec4 geometry shader") : + NULL; + + if (is_enabled) + ctx_disassemble_program(ctx, ksp, type); +} + +static void +decode_ps_kernels(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + uint64_t ksp[3] = {0, 0, 0}; + bool enabled[3] = {false, false, false}; + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (strncmp(iter.name, "Kernel Start Pointer ", + strlen("Kernel Start Pointer ")) == 0) { + int idx = iter.name[strlen("Kernel Start Pointer ")] - '0'; + ksp[idx] = strtol(iter.value, NULL, 16); + } else if (strcmp(iter.name, "8 Pixel Dispatch Enable") == 0) { + enabled[0] = strcmp(iter.value, "true") == 0; + } else if (strcmp(iter.name, "16 Pixel Dispatch Enable") == 0) { + enabled[1] = strcmp(iter.value, "true") == 0; + } else if (strcmp(iter.name, "32 Pixel Dispatch Enable") == 0) { + enabled[2] = strcmp(iter.value, "true") == 0; + } + } + + /* Reorder KSPs to be [8, 16, 32] instead of the hardware order. */ + if (enabled[0] + enabled[1] + enabled[2] == 1) { + if (enabled[1]) { + ksp[1] = ksp[0]; + ksp[0] = 0; + } else if (enabled[2]) { + ksp[2] = ksp[0]; + ksp[0] = 0; + } + } else { + uint64_t tmp = ksp[1]; + ksp[1] = ksp[2]; + ksp[2] = tmp; + } + + if (enabled[0]) + ctx_disassemble_program(ctx, ksp[0], "SIMD8 fragment shader"); + if (enabled[1]) + ctx_disassemble_program(ctx, ksp[1], "SIMD16 fragment shader"); + if (enabled[2]) + ctx_disassemble_program(ctx, ksp[2], "SIMD32 fragment shader"); +} + +static void +decode_3dstate_constant(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + struct gen_group *body = + gen_spec_find_struct(ctx->spec, "3DSTATE_CONSTANT_BODY"); + + uint32_t read_length[4] = {0}; + uint64_t read_addr[4]; + + struct gen_field_iterator outer; + gen_field_iterator_init(&outer, inst, p, 0, false); + while (gen_field_iterator_next(&outer)) { + if (outer.struct_desc != body) + continue; + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, body, &outer.p[outer.start_bit / 32], + 0, false); + + while (gen_field_iterator_next(&iter)) { + int idx; + if (sscanf(iter.name, "Read Length[%d]", &idx) == 1) { + read_length[idx] = iter.raw_value; + } else if (sscanf(iter.name, "Buffer[%d]", &idx) == 1) { + read_addr[idx] = iter.raw_value; + } + } + + for (int i = 0; i < 4; i++) { + if (read_length[i] == 0) + continue; + + struct gen_batch_decode_bo buffer = ctx_get_bo(ctx, read_addr[i]); + if (!buffer.map) { + ImGui::TextColored(ctx->cfg->missing_color, + "constant buffer %d unavailable addr=0x%08" PRIx64, + i, read_addr[i]); + continue; + } + + unsigned size = read_length[i] * 32; + ImGui::Text("constant buffer %d, size %u", i, size); + + if (ctx->edit_address) { + if (ImGui::Button("Show/Edit buffer")) + ctx->edit_address(ctx->user_data, read_addr[i], size); + } + } + } +} + +static void +decode_3dstate_binding_table_pointers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + dump_binding_table(ctx, p[1], -1); +} + +static void +decode_3dstate_sampler_state_pointers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + dump_samplers(ctx, p[1], -1); +} + +static void +decode_3dstate_sampler_state_pointers_gen6(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + dump_samplers(ctx, p[1], -1); + dump_samplers(ctx, p[2], -1); + dump_samplers(ctx, p[3], -1); +} + +static bool +str_ends_with(const char *str, const char *end) +{ + int offset = strlen(str) - strlen(end); + if (offset < 0) + return false; + + return strcmp(str + offset, end) == 0; +} + +static void +decode_dynamic_state_pointers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, const uint32_t *p, + const char *struct_type, int count) +{ + struct gen_group *state = gen_spec_find_struct(ctx->spec, struct_type); + + uint32_t state_offset = 0; + + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (str_ends_with(iter.name, "Pointer")) { + state_offset = iter.raw_value; + break; + } + } + + uint64_t state_addr = ctx->dynamic_base + state_offset; + struct gen_batch_decode_bo bo = ctx_get_bo(ctx, state_addr); + const uint8_t *state_map = (const uint8_t *) bo.map; + + if (state_map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "dynamic %s state unavailable addr=0x%08" PRIx64, + struct_type, state_addr); + return; + } + + for (int i = 0; i < count; i++) { + ImGui::Text("%s %d", struct_type, i); + aub_viewer_print_group(ctx, state, state_offset, state_map); + + state_addr += state->dw_length * 4; + state_map += state->dw_length; + } +} + +static void +decode_3dstate_viewport_state_pointers_cc(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + decode_dynamic_state_pointers(ctx, inst, p, "CC_VIEWPORT", 4); +} + +static void +decode_3dstate_viewport_state_pointers_sf_clip(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + decode_dynamic_state_pointers(ctx, inst, p, "SF_CLIP_VIEWPORT", 4); +} + +static void +decode_3dstate_blend_state_pointers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + decode_dynamic_state_pointers(ctx, inst, p, "BLEND_STATE", 1); +} + +static void +decode_3dstate_cc_state_pointers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + decode_dynamic_state_pointers(ctx, inst, p, "COLOR_CALC_STATE", 1); +} + +static void +decode_3dstate_scissor_state_pointers(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + decode_dynamic_state_pointers(ctx, inst, p, "SCISSOR_RECT", 1); +} + +static void +decode_load_register_imm(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p) +{ + struct gen_group *reg = gen_spec_find_register(ctx->spec, p[1]); + + if (reg != NULL && + ImGui::TreeNodeEx(&p[1], ImGuiTreeNodeFlags_Framed, + "%s (0x%x) = 0x%x", + reg->name, reg->register_offset, p[2])) { + aub_viewer_print_group(ctx, reg, reg->register_offset, &p[2]); + ImGui::TreePop(); + } +} + +struct custom_decoder { + const char *cmd_name; + void (*decode)(struct aub_viewer_decode_ctx *ctx, + struct gen_group *inst, + const uint32_t *p); +} display_decoders[] = { + { "STATE_BASE_ADDRESS", handle_state_base_address }, + { "MEDIA_INTERFACE_DESCRIPTOR_LOAD", handle_media_interface_descriptor_load }, + { "3DSTATE_VERTEX_BUFFERS", handle_3dstate_vertex_buffers }, + { "3DSTATE_INDEX_BUFFER", handle_3dstate_index_buffer }, + { "3DSTATE_VS", decode_single_ksp }, + { "3DSTATE_GS", decode_single_ksp }, + { "3DSTATE_DS", decode_single_ksp }, + { "3DSTATE_HS", decode_single_ksp }, + { "3DSTATE_PS", decode_ps_kernels }, + { "3DSTATE_CONSTANT_VS", decode_3dstate_constant }, + { "3DSTATE_CONSTANT_GS", decode_3dstate_constant }, + { "3DSTATE_CONSTANT_PS", decode_3dstate_constant }, + { "3DSTATE_CONSTANT_HS", decode_3dstate_constant }, + { "3DSTATE_CONSTANT_DS", decode_3dstate_constant }, + + { "3DSTATE_BINDING_TABLE_POINTERS_VS", decode_3dstate_binding_table_pointers }, + { "3DSTATE_BINDING_TABLE_POINTERS_HS", decode_3dstate_binding_table_pointers }, + { "3DSTATE_BINDING_TABLE_POINTERS_DS", decode_3dstate_binding_table_pointers }, + { "3DSTATE_BINDING_TABLE_POINTERS_GS", decode_3dstate_binding_table_pointers }, + { "3DSTATE_BINDING_TABLE_POINTERS_PS", decode_3dstate_binding_table_pointers }, + + { "3DSTATE_SAMPLER_STATE_POINTERS_VS", decode_3dstate_sampler_state_pointers }, + { "3DSTATE_SAMPLER_STATE_POINTERS_HS", decode_3dstate_sampler_state_pointers }, + { "3DSTATE_SAMPLER_STATE_POINTERS_DS", decode_3dstate_sampler_state_pointers }, + { "3DSTATE_SAMPLER_STATE_POINTERS_GS", decode_3dstate_sampler_state_pointers }, + { "3DSTATE_SAMPLER_STATE_POINTERS_PS", decode_3dstate_sampler_state_pointers }, + { "3DSTATE_SAMPLER_STATE_POINTERS", decode_3dstate_sampler_state_pointers_gen6 }, + + { "3DSTATE_VIEWPORT_STATE_POINTERS_CC", decode_3dstate_viewport_state_pointers_cc }, + { "3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP", decode_3dstate_viewport_state_pointers_sf_clip }, + { "3DSTATE_BLEND_STATE_POINTERS", decode_3dstate_blend_state_pointers }, + { "3DSTATE_CC_STATE_POINTERS", decode_3dstate_cc_state_pointers }, + { "3DSTATE_SCISSOR_STATE_POINTERS", decode_3dstate_scissor_state_pointers }, + { "MI_LOAD_REGISTER_IMM", decode_load_register_imm } +}; + +struct custom_decoder info_decoders[] = { + { "STATE_BASE_ADDRESS", handle_state_base_address }, +}; + +static inline uint64_t +get_address(struct gen_spec *spec, const uint32_t *p) +{ + /* Addresses are always guaranteed to be page-aligned and sometimes + * hardware packets have extra stuff stuffed in the bottom 12 bits. + */ + uint64_t addr = p[0] & ~0xfffu; + + if (gen_spec_get_gen(spec) >= gen_make_gen(8,0)) { + /* On Broadwell and above, we have 48-bit addresses which consume two + * dwords. Some packets require that these get stored in a "canonical + * form" which means that bit 47 is sign-extended through the upper + * bits. In order to correctly handle those aub dumps, we need to mask + * off the top 16 bits. + */ + addr |= ((uint64_t)p[1] & 0xffff) << 32; + } + + return addr; +} + +void +aub_viewer_render_batch(struct aub_viewer_decode_ctx *ctx, + const void *_batch, uint32_t batch_size, + uint64_t batch_addr) +{ + struct gen_group *inst; + const uint32_t *p, *batch = (const uint32_t *) _batch, *end = batch + batch_size; + int length; + + for (p = batch; p < end; p += length) { + inst = gen_spec_find_instruction(ctx->spec, p); + length = gen_group_get_length(inst, p); + assert(inst == NULL || length > 0); + length = MAX2(1, length); + + uint64_t offset = batch_addr + ((char *)p - (char *)batch); + + if (inst == NULL) { + ImGui::TextColored(ctx->cfg->error_color, + "x%08" PRIx64 ": unknown instruction %08x", + offset, p[0]); + continue; + } + + const char *inst_name = gen_group_get_name(inst); + + for (unsigned i = 0; i < ARRAY_SIZE(info_decoders); i++) { + if (strcmp(inst_name, info_decoders[i].cmd_name) == 0) { + info_decoders[i].decode(ctx, inst, p); + break; + } + } + + if (ctx->decode_cfg->command_filter.PassFilter(inst->name) && + ImGui::TreeNodeEx(p, + ImGuiTreeNodeFlags_Framed, + "0x%08" PRIx64 ": %s", + offset, inst->name)) { + aub_viewer_print_group(ctx, inst, offset, p); + + for (unsigned i = 0; i < ARRAY_SIZE(display_decoders); i++) { + if (strcmp(inst_name, display_decoders[i].cmd_name) == 0) { + display_decoders[i].decode(ctx, inst, p); + break; + } + } + + if (ctx->edit_address) { + if (ImGui::Button("Edit instruction")) + ctx->edit_address(ctx->user_data, offset, length * 4); + } + + ImGui::TreePop(); + } + + if (strcmp(inst_name, "MI_BATCH_BUFFER_START") == 0) { + struct gen_batch_decode_bo next_batch = {}; + bool second_level; + struct gen_field_iterator iter; + gen_field_iterator_init(&iter, inst, p, 0, false); + while (gen_field_iterator_next(&iter)) { + if (strcmp(iter.name, "Batch Buffer Start Address") == 0) { + next_batch = ctx_get_bo(ctx, iter.raw_value); + } else if (strcmp(iter.name, "Second Level Batch Buffer") == 0) { + second_level = iter.raw_value; + } + } + + if (next_batch.map == NULL) { + ImGui::TextColored(ctx->cfg->missing_color, + "Secondary batch at 0x%08" PRIx64 " unavailable", + next_batch.addr); + } else { + aub_viewer_render_batch(ctx, next_batch.map, next_batch.size, + next_batch.addr); + } + if (second_level) { + /* MI_BATCH_BUFFER_START with "2nd Level Batch Buffer" set acts + * like a subroutine call. Commands that come afterwards get + * processed once the 2nd level batch buffer returns with + * MI_BATCH_BUFFER_END. + */ + continue; + } else { + /* MI_BATCH_BUFFER_START with "2nd Level Batch Buffer" unset acts + * like a goto. Nothing after it will ever get processed. In + * order to prevent the recursion from growing, we just reset the + * loop and continue; + */ + break; + } + } else if (strcmp(inst_name, "MI_BATCH_BUFFER_END") == 0) { + break; + } + } +} diff --git a/src/intel/tools/imgui/imgui_memory_editor.h b/src/intel/tools/imgui/imgui_memory_editor.h new file mode 100644 index 00000000000..d1e54f0f4f0 --- /dev/null +++ b/src/intel/tools/imgui/imgui_memory_editor.h @@ -0,0 +1,704 @@ +// Mini memory editor for Dear ImGui (to embed in your game/tools) +// Animated GIF: https://twitter.com/ocornut/status/894242704317530112 +// Get latest version at http://www.github.com/ocornut/imgui_club +// +// Right-click anywhere to access the Options menu! +// You can adjust the keyboard repeat delay/rate in ImGuiIO. +// The code assume a mono-space font for simplicity! If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before caling this. +// +// Usage: +// static MemoryEditor mem_edit_1; // store your state somewhere +// mem_edit_1.DrawWindow("Memory Editor", mem_block, mem_block_size, 0x0000); // create a window and draw memory editor (if you already have a window, use DrawContents()) +// +// Usage: +// static MemoryEditor mem_edit_2; +// ImGui::Begin("MyWindow") +// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this); +// ImGui::End(); +// +// Changelog: +// - v0.10: initial version +// - v0.11: always refresh active text input with the latest byte from source memory if it's not being edited. +// - v0.12: added OptMidRowsCount to allow extra spacing every XX rows. +// - v0.13: added optional ReadFn/WriteFn handlers to access memory via a function. various warning fixes for 64-bits. +// - v0.14: added GotoAddr member, added GotoAddrAndHighlight() and highlighting. fixed minor scrollbar glitch when resizing. +// - v0.15: added maximum window width. minor optimization. +// - v0.16: added OptGreyOutZeroes option. various sizing fixes when resizing using the "Rows" drag. +// - v0.17: added HighlightFn handler for optional non-contiguous highlighting. +// - v0.18: fixes for displaying 64-bits addresses, fixed mouse click gaps introduced in recent changes, cursor tracking scrolling fixes. +// - v0.19: fixed auto-focus of next byte leaving WantCaptureKeyboard=false for one frame. we now capture the keyboard during that transition. +// - v0.20: added options menu. added OptShowAscii checkbox. added optional HexII display. split Draw() in DrawWindow()/DrawContents(). fixing glyph width. refactoring/cleaning code. +// - v0.21: fixes for using DrawContents() in our own window. fixed HexII to actually be useful and not on the wrong side. +// - v0.22: clicking Ascii view select the byte in the Hex view. Ascii view highlight selection. +// - v0.23: fixed right-arrow triggering a byte write. +// - v0.24: changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61). +// - v0.25: fixed wording: all occurrences of "Rows" renamed to "Columns". +// - v0.26: fixed clicking on hex region +// - v0.30: added data preview for common data types +// +// Todo/Bugs: +// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. +// - Using InputText() is awkward and maybe overkill here, consider implementing something custom. + +#pragma once +#include <stdio.h> // sprintf, scanf +#include <stdint.h> // uint8_t, etc. + +#ifdef _MSC_VER +#define _PRISizeT "IX" +#define snprintf _snprintf +#else +#define _PRISizeT "zX" +#endif + +struct MemoryEditor +{ + typedef unsigned char u8; + + enum DataType + { + DataType_S8, + DataType_U8, + DataType_S16, + DataType_U16, + DataType_S32, + DataType_U32, + DataType_S64, + DataType_U64, + DataType_Float, + DataType_Double, + DataType_COUNT + }; + + enum DataFormat + { + DataFormat_Bin = 0, + DataFormat_Dec = 1, + DataFormat_Hex = 2, + DataFormat_COUNT + }; + + // Settings + bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow + bool ReadOnly; // = false // set to true to disable any editing + int Cols; // = 16 // + bool OptShowDataPreview; // = false // + bool OptShowHexII; // = false // + bool OptShowAscii; // = true // + bool OptGreyOutZeroes; // = true // + int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols + int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr) + ImU32 HighlightColor; // // color of highlight + u8 (*ReadFn)(const u8* data, size_t off); // = NULL // optional handler to read bytes + void (*WriteFn)(u8* data, size_t off, u8 d); // = NULL // optional handler to write bytes + bool (*HighlightFn)(const u8* data, size_t off);//NULL // optional handler to return Highlight property (to support non-contiguous highlighting) + + // State/Internals + bool ContentsWidthChanged; + size_t DataPreviewAddr; + size_t DataEditingAddr; + bool DataEditingTakeFocus; + char DataInputBuf[32]; + char AddrInputBuf[32]; + size_t GotoAddr; + size_t HighlightMin, HighlightMax; + int PreviewEndianess; + DataType PreviewDataType; + + MemoryEditor() + { + // Settings + Open = true; + ReadOnly = false; + Cols = 16; + OptShowDataPreview = false; + OptShowHexII = false; + OptShowAscii = true; + OptGreyOutZeroes = true; + OptMidColsCount = 8; + OptAddrDigitsCount = 0; + HighlightColor = IM_COL32(255, 255, 255, 50); + ReadFn = NULL; + WriteFn = NULL; + HighlightFn = NULL; + + // State/Internals + ContentsWidthChanged = false; + DataPreviewAddr = DataEditingAddr = (size_t)-1; + DataEditingTakeFocus = false; + memset(DataInputBuf, 0, sizeof(DataInputBuf)); + memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); + GotoAddr = (size_t)-1; + HighlightMin = HighlightMax = (size_t)-1; + PreviewEndianess = 0; + PreviewDataType = DataType_S32; + } + + void GotoAddrAndHighlight(size_t addr_min, size_t addr_max) + { + GotoAddr = addr_min; + HighlightMin = addr_min; + HighlightMax = addr_max; + } + + struct Sizes + { + int AddrDigitsCount; + float LineHeight; + float GlyphWidth; + float HexCellWidth; + float SpacingBetweenMidCols; + float PosHexStart; + float PosHexEnd; + float PosAsciiStart; + float PosAsciiEnd; + float WindowWidth; + }; + + void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr) + { + ImGuiStyle& style = ImGui::GetStyle(); + s.AddrDigitsCount = OptAddrDigitsCount; + if (s.AddrDigitsCount == 0) + for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4) + s.AddrDigitsCount++; + s.LineHeight = ImGui::GetTextLineHeight(); + s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space + s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere + s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing + s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; + s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); + s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; + if (OptShowAscii) + { + s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; + if (OptMidColsCount > 0) + s.PosAsciiStart += ((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; + s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; + } + s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; + } + + // Standalone Memory Editor window + void DrawWindow(const char* title, u8* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) + { + Sizes s; + CalcSizes(s, mem_size, base_display_addr); + ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX)); + + Open = true; + if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) + { + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseClicked(1)) + ImGui::OpenPopup("context"); + DrawContents(mem_data, mem_size, base_display_addr); + if (ContentsWidthChanged) + { + CalcSizes(s, mem_size, base_display_addr); + ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y)); + } + } + ImGui::End(); + } + + // Memory Editor contents only + void DrawContents(u8* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) + { + Sizes s; + CalcSizes(s, mem_size, base_display_addr); + ImGuiStyle& style = ImGui::GetStyle(); + + // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. + // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move. + const float height_separator = style.ItemSpacing.y; + float footer_height = height_separator + ImGui::GetFrameHeightWithSpacing() * 1; + if (OptShowDataPreview) + footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; + ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + const int line_total_count = (int)((mem_size + Cols - 1) / Cols); + ImGuiListClipper clipper(line_total_count, s.LineHeight); + const size_t visible_start_addr = clipper.DisplayStart * Cols; + const size_t visible_end_addr = clipper.DisplayEnd * Cols; + + bool data_next = false; + + if (ReadOnly || DataEditingAddr >= mem_size) + DataEditingAddr = (size_t)-1; + if (DataPreviewAddr >= mem_size) + DataPreviewAddr = (size_t)-1; + + size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0; + + size_t data_editing_addr_backup = DataEditingAddr; + size_t data_editing_addr_next = (size_t)-1; + if (DataEditingAddr != (size_t)-1) + { + // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered) + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && DataEditingAddr >= (size_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; DataEditingTakeFocus = true; } + else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && DataEditingAddr < mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; DataEditingTakeFocus = true; } + else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && DataEditingAddr > 0) { data_editing_addr_next = DataEditingAddr - 1; DataEditingTakeFocus = true; } + else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; DataEditingTakeFocus = true; } + } + if (data_editing_addr_next != (size_t)-1 && (data_editing_addr_next / Cols) != (data_editing_addr_backup / Cols)) + { + // Track cursor movements + const int scroll_offset = ((int)(data_editing_addr_next / Cols) - (int)(data_editing_addr_backup / Cols)); + const bool scroll_desired = (scroll_offset < 0 && data_editing_addr_next < visible_start_addr + Cols * 2) || (scroll_offset > 0 && data_editing_addr_next > visible_end_addr - Cols * 2); + if (scroll_desired) + ImGui::SetScrollY(ImGui::GetScrollY() + scroll_offset * s.LineHeight); + } + + // Draw vertical separator + ImVec2 window_pos = ImGui::GetWindowPos(); + if (OptShowAscii) + draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border)); + + const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); + const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; + + for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines + { + size_t addr = (size_t)(line_i * Cols); + ImGui::Text("%0*" _PRISizeT ": ", s.AddrDigitsCount, base_display_addr + addr); + + // Draw Hexadecimal + for (int n = 0; n < Cols && addr < mem_size; n++, addr++) + { + float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; + if (OptMidColsCount > 0) + byte_pos_x += (n / OptMidColsCount) * s.SpacingBetweenMidCols; + ImGui::SameLine(byte_pos_x); + + // Draw highlight + bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); + bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); + bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); + if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) + { + ImVec2 pos = ImGui::GetCursorScreenPos(); + float highlight_width = s.GlyphWidth * 2; + bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); + if (is_next_byte_highlighted || (n + 1 == Cols)) + { + highlight_width = s.HexCellWidth; + if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) + highlight_width += s.SpacingBetweenMidCols; + } + draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); + } + + if (DataEditingAddr == addr) + { + // Display text input on current byte + bool data_write = false; + ImGui::PushID((void*)addr); + if (DataEditingTakeFocus) + { + ImGui::SetKeyboardFocusHere(); + ImGui::CaptureKeyboardFromApp(true); + sprintf(AddrInputBuf, "%0*" _PRISizeT, s.AddrDigitsCount, base_display_addr + addr); + sprintf(DataInputBuf, "%02X", ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + } + ImGui::PushItemWidth(s.GlyphWidth * 2); + struct UserData + { + // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. + static int Callback(ImGuiTextEditCallbackData* data) + { + UserData* user_data = (UserData*)data->UserData; + if (!data->HasSelection()) + user_data->CursorPos = data->CursorPos; + if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) + { + // When not editing a byte, always rewrite its content (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there) + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, user_data->CurrentBufOverwrite); + data->SelectionStart = 0; + data->SelectionEnd = data->CursorPos = 2; + } + return 0; + } + char CurrentBufOverwrite[3]; // Input + int CursorPos; // Output + }; + UserData user_data; + user_data.CursorPos = -1; + sprintf(user_data.CurrentBufOverwrite, "%02X", ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_AlwaysInsertMode | ImGuiInputTextFlags_CallbackAlways; + if (ImGui::InputText("##data", DataInputBuf, 32, flags, UserData::Callback, &user_data)) + data_write = data_next = true; + else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) + DataEditingAddr = data_editing_addr_next = (size_t)-1; + DataEditingTakeFocus = false; + ImGui::PopItemWidth(); + if (user_data.CursorPos >= 2) + data_write = data_next = true; + if (data_editing_addr_next != (size_t)-1) + data_write = data_next = false; + int data_input_value; + if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) + { + if (WriteFn) + WriteFn(mem_data, addr, (u8)data_input_value); + else + mem_data[addr] = (u8)data_input_value; + } + ImGui::PopID(); + } + else + { + // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. + u8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + + if (OptShowHexII) + { + if ((b >= 32 && b < 128)) + ImGui::Text(".%c ", b); + else if (b == 0xFF && OptGreyOutZeroes) + ImGui::TextDisabled("## "); + else if (b == 0x00) + ImGui::Text(" "); + else + ImGui::Text("%02X ", b); + } + else + { + if (b == 0 && OptGreyOutZeroes) + ImGui::TextDisabled("00 "); + else + ImGui::Text("%02X ", b); + } + if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) + { + DataEditingTakeFocus = true; + data_editing_addr_next = addr; + } + } + } + + if (OptShowAscii) + { + // Draw ASCII values + ImGui::SameLine(s.PosAsciiStart); + ImVec2 pos = ImGui::GetCursorScreenPos(); + addr = line_i * Cols; + ImGui::PushID(line_i); + if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) + { + DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); + DataEditingTakeFocus = true; + } + ImGui::PopID(); + for (int n = 0; n < Cols && addr < mem_size; n++, addr++) + { + if (addr == DataEditingAddr) + { + draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); + draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); + } + unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + char display_c = (c < 32 || c >= 128) ? '.' : c; + draw_list->AddText(pos, (display_c == '.') ? color_disabled : color_text, &display_c, &display_c + 1); + pos.x += s.GlyphWidth; + } + } + } + clipper.End(); + ImGui::PopStyleVar(2); + ImGui::EndChild(); + + if (data_next && DataEditingAddr < mem_size) + { + DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1; + DataEditingTakeFocus = true; + } + else if (data_editing_addr_next != (size_t)-1) + { + DataEditingAddr = DataPreviewAddr = data_editing_addr_next; + } + + ImGui::Separator(); + + // Options menu + + bool next_show_data_preview = OptShowDataPreview; + if (ImGui::Button("Options")) + ImGui::OpenPopup("context"); + if (ImGui::BeginPopup("context")) + { + ImGui::PushItemWidth(56); + if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; } + ImGui::PopItemWidth(); + ImGui::Checkbox("Show Data Preview", &next_show_data_preview); + ImGui::Checkbox("Show HexII", &OptShowHexII); + if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; } + ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes); + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + ImGui::Text("Range %0*" _PRISizeT "..%0*" _PRISizeT, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); + ImGui::SameLine(); + ImGui::PushItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f); + if (ImGui::InputText("##addr", AddrInputBuf, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) + { + size_t goto_addr; + if (sscanf(AddrInputBuf, "%" _PRISizeT, &goto_addr) == 1) + { + GotoAddr = goto_addr - base_display_addr; + HighlightMin = HighlightMax = (size_t)-1; + } + } + ImGui::PopItemWidth(); + + if (GotoAddr != (size_t)-1) + { + if (GotoAddr < mem_size) + { + ImGui::BeginChild("##scrolling"); + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight()); + ImGui::EndChild(); + DataEditingAddr = DataPreviewAddr = GotoAddr; + DataEditingTakeFocus = true; + } + GotoAddr = (size_t)-1; + } + + if (OptShowDataPreview) + { + ImGui::Separator(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Preview as:"); + ImGui::SameLine(); + ImGui::PushItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); + if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) + { + for (int n = 0; n < DataType_COUNT; n++) + if (ImGui::Selectable(DataTypeGetDesc((DataType)n), PreviewDataType == n)) + PreviewDataType = (DataType)n; + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::PushItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); + ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); + ImGui::PopItemWidth(); + + char buf[128]; + float x = s.GlyphWidth * 6.0f; + bool has_value = DataPreviewAddr != (size_t)-1; + if (has_value) + DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf)); + ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); + if (has_value) + DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf)); + ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); + if (has_value) + DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf)); + ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); + } + + OptShowDataPreview = next_show_data_preview; + + // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) + ImGui::SetCursorPosX(s.WindowWidth); + } + + // Utilities for Data Preview + const char* DataTypeGetDesc(DataType data_type) const + { + const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; + IM_ASSERT(data_type >= 0 && data_type < DataType_COUNT); + return descs[data_type]; + } + + size_t DataTypeGetSize(DataType data_type) const + { + const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, 4, 8 }; + IM_ASSERT(data_type >= 0 && data_type < DataType_COUNT); + return sizes[data_type]; + } + + const char* DataFormatGetDesc(DataFormat data_format) const + { + const char* descs[] = { "Bin", "Dec", "Hex" }; + IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT); + return descs[data_format]; + } + + bool IsBigEndian() const + { + uint16_t x = 1; + char c[2]; + memcpy(c, &x, 2); + return c[0] != 0; + } + + static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) + { + if (is_little_endian) + { + uint8_t* dst = (uint8_t*)_dst; + uint8_t* src = (uint8_t*)_src + s - 1; + for (int i = 0, n = (int)s; i < n; ++i) + memcpy(dst++, src--, 1); + return _dst; + } + else + { + return memcpy(_dst, _src, s); + } + } + + static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) + { + if (is_little_endian) + { + return memcpy(_dst, _src, s); + } + else + { + uint8_t* dst = (uint8_t*)_dst; + uint8_t* src = (uint8_t*)_src + s - 1; + for (int i = 0, n = (int)s; i < n; ++i) + memcpy(dst++, src--, 1); + return _dst; + } + } + + void* EndianessCopy(void *dst, void *src, size_t size) const + { + static void *(*fp)(void *, void *, size_t, int) = NULL; + if (fp == NULL) + fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; + return fp(dst, src, size, PreviewEndianess); + } + + const char* FormatBinary(const uint8_t* buf, int width) const + { + IM_ASSERT(width <= 64); + size_t out_n = 0; + static char out_buf[64 + 8 + 1]; + for (int j = 0, n = width / 8; j < n; ++j) + { + for (int i = 0; i < 8; ++i) + out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0'; + out_buf[out_n++] = ' '; + } + out_buf[out_n] = 0; + IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf)); + return out_buf; + } + + void DisplayPreviewData(size_t addr, const u8* mem_data, size_t mem_size, DataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const + { + uint8_t buf[8]; + int elem_size = DataTypeGetSize(data_type); + size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; + if (ReadFn) + for (int i = 0, n = (int)size; i < n; ++i) + buf[i] = ReadFn(mem_data, addr + i); + else + memcpy(buf, mem_data + addr, size); + + if (data_format == DataFormat_Bin) + { + snprintf(out_buf, out_buf_size, "%s", FormatBinary(buf, size * 8)); + return; + } + + out_buf[0] = 0; + switch (data_type) + { + case DataType_S8: + { + int8_t int8 = 0; + EndianessCopy(&int8, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%hhd", int8); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } + break; + } + case DataType_U8: + { + uint8_t uint8 = 0; + EndianessCopy(&uint8, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%hhu", uint8); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } + break; + } + case DataType_S16: + { + int16_t int16 = 0; + EndianessCopy(&int16, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%hd", int16); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } + break; + } + case DataType_U16: + { + uint16_t uint16 = 0; + EndianessCopy(&uint16, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%hu", uint16); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } + break; + } + case DataType_S32: + { + int32_t int32 = 0; + EndianessCopy(&int32, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%d", int32); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%08x", int32); return; } + break; + } + case DataType_U32: + { + uint32_t uint32 = 0; + EndianessCopy(&uint32, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%u", uint32); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } + break; + } + case DataType_S64: + { + int64_t int64 = 0; + EndianessCopy(&int64, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } + break; + } + case DataType_U64: + { + uint64_t uint64 = 0; + EndianessCopy(&uint64, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } + break; + } + case DataType_Float: + { + float float32 = 0.0f; + EndianessCopy(&float32, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%f", float32); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "%a", float32); return; } + break; + } + case DataType_Double: + { + double float64 = 0.0; + EndianessCopy(&float64, buf, size); + if (data_format == DataFormat_Dec) { snprintf(out_buf, out_buf_size, "%f", float64); return; } + if (data_format == DataFormat_Hex) { snprintf(out_buf, out_buf_size, "%a", float64); return; } + break; + } + } // Switch + IM_ASSERT(0); // Shouldn't reach + } +}; + +#undef _PRISizeT diff --git a/src/intel/tools/meson.build b/src/intel/tools/meson.build index 396c19ecfaa..0fe39fa4873 100644 --- a/src/intel/tools/meson.build +++ b/src/intel/tools/meson.build @@ -108,4 +108,16 @@ libintel_dump_gpu = shared_library( if with_tools.contains('intel-ui') subdir('imgui') + + aubinator_viewer = executable( + 'aubinator_viewer', + files('aubinator_viewer.cpp', 'aubinator_viewer_decoder.cpp'), + dependencies : [dep_zlib, dep_dl, dep_thread, dep_m, libintel_imgui_gtk_dep], + include_directories : [inc_common, inc_intel], + link_with : [libintel_common, libintel_compiler, libintel_dev, libmesa_util, libaub], + c_args : [c_vis_args, no_override_init_args], + cpp_args : [ '-fpermissive', '-std=c++11', '-Wno-parentheses', '-Wno-class-memaccess' ], + build_by_default : true, + install : true + ) endif |