mesa/src/intel/tools/intel_hang_viewer.cpp
Antonio Ospite ddf2aa3a4d build: avoid redefining unreachable() which is standard in C23
In the C23 standard unreachable() is now a predefined function-like
macro in <stddef.h>

See https://android.googlesource.com/platform/bionic/+/HEAD/docs/c23.md#is-now-a-predefined-function_like-macro-in

And this causes build errors when building for C23:

-----------------------------------------------------------------------
In file included from ../src/util/log.h:30,
                 from ../src/util/log.c:30:
../src/util/macros.h:123:9: warning: "unreachable" redefined
  123 | #define unreachable(str)    \
      |         ^~~~~~~~~~~
In file included from ../src/util/macros.h:31:
/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h:456:9: note: this is the location of the previous definition
  456 | #define unreachable() (__builtin_unreachable ())
      |         ^~~~~~~~~~~
-----------------------------------------------------------------------

So don't redefine it with the same name, but use the name UNREACHABLE()
to also signify it's a macro.

Using a different name also makes sense because the behavior of the
macro was extending the one of __builtin_unreachable() anyway, and it
also had a different signature, accepting one argument, compared to the
standard unreachable() with no arguments.

This change improves the chances of building mesa with the C23 standard,
which for instance is the default in recent AOSP versions.

All the instances of the macro, including the definition, were updated
with the following command line:

  git grep -l '[^_]unreachable(' -- "src/**" | sort | uniq | \
  while read file; \
  do \
    sed -e 's/\([^_]\)unreachable(/\1UNREACHABLE(/g' -i "$file"; \
  done && \
  sed -e 's/#undef unreachable/#undef UNREACHABLE/g' -i src/intel/isl/isl_aux_info.c

Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36437>
2025-07-31 17:49:42 +00:00

794 lines
23 KiB
C++

/*
* Copyright © 2023 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 <memory>
#include <string>
#include <vector>
#include "util/list.h"
#include "util/macros.h"
#include "common/intel_hang_dump.h"
#include "intel_tools.h"
/* Data */
struct hang_bo {
void *map = NULL;
uint64_t offset = 0;
uint64_t size = 0;
};
struct hang_map {
uint64_t offset = 0;
uint64_t size = 0;
};
struct hang_exec {
uint64_t offset = 0;
};
/* UI */
#include <epoxy/gl.h>
#include "imgui/imgui.h"
#include "imgui/imgui_memory_editor.h"
#include "imgui_impl_gtk3.h"
#include "imgui_impl_opengl3.h"
#include "aubinator_viewer.h"
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);
}
class window {
public:
virtual void display() = 0;
virtual void destroy() = 0;
virtual ~window() {}
const char *name() const { return m_name; }
bool m_opened = true;
ImVec2 m_position = ImVec2(-1, -1);
ImVec2 m_size = ImVec2(700, 300);
protected:
window() {}
char m_name[128];
};
static struct Context {
/* Hang file descriptor */
int file_fd = -1;
void *file_map = NULL;
/* Map hang file in RW for edition */
bool edit = false;
/* AUX-TT */
uint64_t aux_tt_addr = 0;
struct intel_device_info devinfo;
struct intel_spec *spec = NULL;
/* Result of parsing the hang file */
std::vector<hang_bo> bos;
std::vector<hang_map> maps;
std::vector<hang_exec> execs;
hang_bo hw_image;
GtkWidget *gtk_window;
/* UI state*/
bool show_commands_window;
bool show_registers_window;
struct aub_viewer_cfg cfg;
std::vector<std::shared_ptr<window>> windows;
} context;
thread_local ImGuiContext* __MesaImGui;
hang_bo *find_bo(uint64_t addr)
{
for (auto &bo : context.bos) {
if (addr >= bo.offset && addr < (bo.offset + bo.size))
return &bo;
}
return NULL;
}
/**/
static uint8_t
read_edit_window(const uint8_t *data, size_t off)
{
return data[off];
}
static void
write_edit_window(uint8_t *data, size_t off, uint8_t d)
{
data[off] = d;
}
class edit_window : public window {
public:
struct hang_bo m_bo;
struct intel_batch_decode_bo m_aub_bo;
uint64_t m_aub_offset;
struct intel_batch_decode_bo m_gtt_bo;
uint64_t m_gtt_offset;
struct MemoryEditor m_editor;
edit_window(const struct hang_bo &bo)
: m_bo(bo) {
m_editor.OptShowDataPreview = true;
m_editor.OptShowAscii = false;
m_editor.ReadFn = read_edit_window;
m_editor.WriteFn = write_edit_window;
snprintf(m_name, sizeof(m_name), "Memory view 0x%016" PRIx64 "##%p",
bo.offset, this);
}
void display() {
if (m_bo.map) {
ImGui::BeginChild(ImGui::GetID("##block"));
m_editor.DrawContents((uint8_t *) m_bo.map, m_bo.size, m_bo.offset);
ImGui::EndChild();
} else {
ImGui::Text("Memory view at 0x%" PRIx64 " not available", m_bo.offset);
}
}
void destroy() {}
};
class shader_window : public window {
public:
std::string m_description;
uint64_t m_address;
std::string m_shader;
shader_window(const char *description, uint64_t address)
: m_description(description)
, m_address(address) {
snprintf(m_name, sizeof(m_name),
"%s (0x%" PRIx64 ")##%p", m_description.c_str(), m_address, this);
hang_bo *bo = find_bo(address);
if (bo != NULL) {
char *shader_txt = NULL;
size_t shader_txt_size = 0;
FILE *f = open_memstream(&shader_txt, &shader_txt_size);
if (f) {
intel_disassemble(&context.devinfo,
(const uint8_t *) bo->map +
(address - bo->offset), 0, f);
fclose(f);
}
m_shader = std::string(shader_txt);
}
}
void display() {
ImGui::InputTextMultiline("Assembly",
(char *) m_shader.c_str(), m_shader.size(),
ImGui::GetContentRegionAvail(),
ImGuiInputTextFlags_ReadOnly);
}
void destroy() {}
};
class aux_tt_window : public window {
public:
aux_tt_window(uint64_t l3_addr)
: m_l3_addr(l3_addr)
, m_bo(find_bo(l3_addr)) {
snprintf(m_name, sizeof(m_name), "AUX TT##%p", this);
}
void display() {
ImGui::BeginChild(ImGui::GetID("##toplevel"));
if (m_bo != NULL)
display_level(3, m_l3_addr, 0);
else
ImGui::Text("AUX table buffer not found: 0x%" PRIx64,
m_l3_addr);
ImGui::EndChild();
}
void destroy() {}
private:
void display_level(int level, uint64_t table_addr, uint64_t base_addr) {
assert(level >= 1 && level <= 3);
const hang_bo *bo =
table_addr == m_l3_addr ? m_bo : find_bo(table_addr);
if (bo == NULL) {
ImGui::Text("level %u not found addr=0x%016" PRIx64,
level, table_addr);
return;
}
static struct {
uint32_t top;
uint32_t bottom;
} levels[4] = {
{ 0, 0, },
{ 23, 16, },
{ 35, 24, },
{ 47, 36, },
};
const uint64_t *entries =
(const uint64_t *)((const uint8_t *)bo->map + (table_addr - bo->offset));
if (level == 1) {
uint32_t n_entries = context.devinfo.verx10 == 125 ? 16 : 256;
for (uint32_t i = 0; i < n_entries; i++) {
uint64_t addr = entries[i] & 0xffffffffff00ull;
ImGui::Text("entry%04u: addr=0x%012" PRIx64 " entry=0x%012" PRIx64
" range=0x%012" PRIx64 "-0x%012" PRIx64,
i, addr, entries[i],
base_addr + (uint64_t)i << levels[level].bottom,
base_addr + (uint64_t)i << levels[level].bottom);
}
} else {
for (uint32_t i = 0; i < 4096; i++) {
uint64_t entry_addr = base_addr + (uint64_t)i << levels[level].bottom;
uint64_t addr = entries[i] & 0xffffffff8000ull;
bool valid = (entries[i] & 0x1) != 0;
if (valid &&
ImGui::TreeNodeEx(
(void *)&entries[i],
ImGuiTreeNodeFlags_Framed,
"entry%04u: addr=0x%012" PRIx64 " entry=0x%012" PRIx64
" range=0x%012" PRIx64 "-0x%012" PRIx64,
i, addr, entries[i],
entry_addr, entry_addr)) {
if (valid)
display_level(level - 1, addr, entry_addr);
ImGui::TreePop();
}
}
}
}
uint64_t m_l3_addr;
const hang_bo *m_bo;
};
static struct intel_batch_decode_bo
batch_get_bo(void *user_data, bool ppgtt, uint64_t address)
{
intel_batch_decode_bo ret_bo;
ret_bo.map = NULL;
ret_bo.addr = 0;
if (!ppgtt)
return ret_bo;
for (const auto &bo : context.bos) {
if (address >= bo.offset &&
address < (bo.offset + bo.size)) {
ret_bo.map = bo.map;
ret_bo.addr = bo.offset;
ret_bo.size = bo.size;
}
}
return ret_bo;
}
static void
batch_display_shader(void *user_data, const char *shader_desc, uint64_t address)
{
context.windows.push_back(std::shared_ptr<window>(new shader_window(shader_desc, address)));
}
class batch_window : public window {
public:
batch_window(const struct hang_bo &bo)
: m_bo(bo)
, m_collapsed(true) {
aub_viewer_decode_ctx_init(&m_decode_ctx,
&context.cfg,
&m_decode_cfg,
&context.devinfo,
context.spec,
batch_get_bo,
NULL,
NULL);
m_decode_ctx.display_shader = batch_display_shader;
// window->decode_ctx.display_urb = batch_display_urb;
// window->decode_ctx.edit_address = batch_edit_address;
snprintf(m_name, sizeof(m_name), "Batch view 0x%016" PRIx64 "##%p",
bo.offset, this);
}
~batch_window() {}
void display() {
ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() / (2 * 2));
decode_options();
if (ImGui::Button("Edit commands"))
context.windows.push_back(std::shared_ptr<window>(new edit_window(m_bo)));
ImGui::PopItemWidth();
ImGui::BeginChild(ImGui::GetID("##block"));
aub_viewer_render_batch(&m_decode_ctx,
m_bo.map,
m_bo.size,
m_bo.offset,
false /* from_ring */);
ImGui::EndChild();
}
void destroy() {}
private:
void decode_options() {
char name[40];
snprintf(name, sizeof(name), "command filter##%p", &m_decode_cfg.command_filter);
m_decode_cfg.command_filter.Draw(name); ImGui::SameLine();
snprintf(name, sizeof(name), "field filter##%p", &m_decode_cfg.field_filter);
m_decode_cfg.field_filter.Draw(name); ImGui::SameLine();
if (ImGui::Button("Dwords")) m_decode_cfg.show_dwords ^= 1;
}
struct hang_bo m_bo;
bool m_collapsed;
struct aub_viewer_decode_cfg m_decode_cfg;
struct aub_viewer_decode_ctx m_decode_ctx;
char edit_address[20];
};
/* 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_hang_stats()
{
ImGui::Begin("Hang stats");
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();
ImGui::ColorEdit3("booleans", (float *)&cfg->boolean_color, cflags); ImGui::SameLine();
if (ImGui::Button("Help") || has_ctrl_key('h')) { ImGui::OpenPopup("Help"); }
ImGui::Text("BOs: %zu", context.bos.size());
ImGui::Text("Execs %zu", context.execs.size());
ImGui::Text("Maps: %zu", context.maps.size());
ImGui::Text("PCI ID: 0x%x", context.devinfo.pci_device_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("%s", texts[i]); ImGui::SameLine(align); ImGui::Text("%s", texts[i + 1]);
}
if (ImGui::Button("Done") || ImGui::IsKeyPressed(ImGuiKey_Escape))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
uint64_t exec_buf_addr = 0;
if (!context.execs.empty())
exec_buf_addr = context.execs.front().offset;
ImGui::BeginChild(ImGui::GetID("BO list:"));
for (const auto &bo : context.bos) {
char bo_name[80];
snprintf(bo_name, sizeof(bo_name),
"BO 0x%012" PRIx64 "-0x%012" PRIx64 " size=%" PRIu64 "(%s) %s",
bo.offset, bo.offset + bo.size - 1, bo.size, human_size(bo.size),
bo.offset == exec_buf_addr ? "BATCH BUFFER" : "");
if (ImGui::Selectable(bo_name, false))
context.windows.push_back(std::shared_ptr<window>(new batch_window(bo)));
}
if (context.hw_image.size != 0 && ImGui::Selectable("HW IMAGE", false))
context.windows.push_back(std::shared_ptr<window>(new batch_window(context.hw_image)));
if (context.aux_tt_addr != 0 && ImGui::Selectable("AUX-TT", false))
context.windows.push_back(std::shared_ptr<window>(new aux_tt_window(context.aux_tt_addr)));
ImGui::EndChild();
ImGui::End();
}
/* Main redrawing */
static void
display_windows(void)
{
display_hang_stats();
/* 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.
*/
auto it = context.windows.begin();
while (it != context.windows.end()) {
if (!(*it)->m_opened) {
(*it)->destroy();
it = context.windows.erase(it);
} else {
it++;
}
}
for (uint32_t i = 0; i < context.windows.size(); i++) {
std::shared_ptr<window> window = context.windows[i];
ImGui::SetNextWindowPos(window->m_position, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(window->m_size, ImGuiCond_FirstUseEver);
if (ImGui::Begin(window->name(), &window->m_opened)) {
window->display();
window->m_position = ImGui::GetWindowPos();
window->m_size = ImGui::GetWindowSize();
}
if (window_has_ctrl_key('w'))
window->m_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");
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
size_allocate_area(GtkGLArea *area,
GdkRectangle *allocation,
gpointer user_data)
{
if (!gtk_widget_get_realized(GTK_WIDGET(area)))
return;
/* We want to catch only initial size allocate. */
g_signal_handlers_disconnect_by_func(area,
(gpointer) size_allocate_area,
user_data);
// TODO
}
static void
print_help(const char *progname, FILE *file)
{
fprintf(file,
"Usage: %s -p platform HANG_FILE\n"
"\n"
" -p, --platform platform platform to use for decoding\n"
" -e, --edit map the hang file read/write for edition\n"
" -x, --aux-tt map the hang file read/write for edition\n"
, progname);
}
static void
add_bo(void *map, uint64_t addr, uint64_t size)
{
hang_bo bo;
bo.map = map;
bo.offset = addr;
bo.size = size;
context.bos.push_back(bo);
}
static void
add_map(uint64_t addr, uint64_t size)
{
hang_map map;
map.offset = addr;
map.size = size;
context.maps.push_back(map);
}
static void
add_exec(uint64_t addr)
{
hang_exec exec;
exec.offset = addr;
context.execs.push_back(exec);
}
static size_t
get_block_size(uint32_t type)
{
switch (type) {
case INTEL_HANG_DUMP_BLOCK_TYPE_HEADER: return sizeof(struct intel_hang_dump_block_header);
case INTEL_HANG_DUMP_BLOCK_TYPE_BO: return sizeof(struct intel_hang_dump_block_bo);
case INTEL_HANG_DUMP_BLOCK_TYPE_MAP: return sizeof(struct intel_hang_dump_block_map);
case INTEL_HANG_DUMP_BLOCK_TYPE_EXEC: return sizeof(struct intel_hang_dump_block_exec);
case INTEL_HANG_DUMP_BLOCK_TYPE_HW_IMAGE: return sizeof(struct intel_hang_dump_block_hw_image);
default: UNREACHABLE("invalid block");
}
}
static void
parse_hang_file(const char *filename)
{
context.file_fd = open(filename, context.edit ? O_RDWR : O_RDONLY);
if (context.file_fd < 0)
exit(EXIT_FAILURE);
struct stat file_stats;
if (fstat(context.file_fd, &file_stats) != 0)
exit(EXIT_FAILURE);
context.file_map = mmap(NULL, file_stats.st_size,
PROT_READ | PROT_WRITE,
context.edit ? MAP_SHARED : MAP_PRIVATE,
context.file_fd, 0);
if (context.file_map == MAP_FAILED)
exit(EXIT_FAILURE);
uint8_t *current_file_ptr = (uint8_t *) context.file_map;
uint8_t *last_file_ptr = current_file_ptr + file_stats.st_size;
while (current_file_ptr < last_file_ptr) {
union intel_hang_dump_block_all *block_header =
(union intel_hang_dump_block_all *)current_file_ptr;
size_t block_size = get_block_size(block_header->base.type);
switch (block_header->base.type) {
case INTEL_HANG_DUMP_BLOCK_TYPE_HEADER:
assert(block_header->header.magic == INTEL_HANG_DUMP_MAGIC);
assert(block_header->header.version == INTEL_HANG_DUMP_VERSION);
break;
case INTEL_HANG_DUMP_BLOCK_TYPE_BO: {
add_bo((uint8_t *) current_file_ptr + block_size,
block_header->bo.offset,
block_header->bo.size);
current_file_ptr = (uint8_t *) current_file_ptr + block_size + block_header->bo.size;
break;
}
case INTEL_HANG_DUMP_BLOCK_TYPE_HW_IMAGE: {
context.hw_image.offset = block_header->bo.offset;
context.hw_image.size = block_header->hw_img.size;
context.hw_image.map = (uint8_t *) current_file_ptr + block_size;
current_file_ptr = (uint8_t *) current_file_ptr + block_size + block_header->hw_img.size;
break;
}
case INTEL_HANG_DUMP_BLOCK_TYPE_MAP: {
add_map(block_header->map.offset,
block_header->map.size);
current_file_ptr = (uint8_t *) current_file_ptr + block_size;
break;
}
case INTEL_HANG_DUMP_BLOCK_TYPE_EXEC: {
add_exec(block_header->exec.offset);
current_file_ptr = (uint8_t *) current_file_ptr + block_size;
break;
}
default:
UNREACHABLE("Invalid block type");
}
}
}
int
main(int argc, char *argv[])
{
int c, i;
bool help = false;
const char *platform = NULL;
const struct option aubinator_opts[] = {
{ "platform", required_argument, NULL, 'p' },
{ "aux-tt", required_argument, NULL, 'x' },
{ "edit", no_argument, NULL, 'e' },
{ "help", no_argument, (int *) &help, true },
{ NULL, 0, NULL, 0 },
};
context = {};
i = 0;
while ((c = getopt_long(argc, argv, "p:ex:", aubinator_opts, &i)) != -1) {
switch (c) {
case 'p':
platform = optarg;
break;
case 'e':
context.edit = true;
break;
case 'x':
context.aux_tt_addr = strtoll(optarg, NULL, 16);
break;
default:
break;
}
}
const char *filename = NULL;
if (optind < argc)
filename = argv[optind];
if (help || !platform || !filename) {
print_help(argv[0], stderr);
exit(0);
}
intel_get_device_info_from_pci_id(
intel_device_name_to_pci_device_id(platform),
&context.devinfo);
context.spec = intel_spec_load(&context.devinfo);
parse_hang_file(filename);
gtk_init(NULL, NULL);
context.gtk_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(context.gtk_window), "Hang 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);
g_signal_connect(gl_area, "size_allocate", G_CALLBACK(size_allocate_area), NULL);
gtk_container_add(GTK_CONTAINER(context.gtk_window), gl_area);
gtk_widget_show_all(context.gtk_window);
gtk_main();
return EXIT_SUCCESS;
}