Как с помощью трех открытых проектов написать диплом

ga2d8ma5qni3cswhowvmzsqrrjc.png Не секрет, что в у нас в проекте используют обучают студентов. Точнее, студенты на базе проекта осваивают практические аспекты системного программирования: пишут дипломы, курсовые, занимаются исследовательской деятельностью и так далее. Вот об одном дипломе, успешно защищённом прошлым летом, и пойдет речь в данной статье.

Автором является Александра Бутрова AleksandraButrova, тема «Разработка графической подсистемы для встроенных операционных систем». При написании диплома были использованы три открытых проекта: Embox, Nuklear и stb. Последний использовался только для загрузки картинок, а вот Nuklear являлся, по сути, виновником торжества. Можно сказать, что работа свелась к интеграции Nuklear и Embox. Первый предоставлял лёгкую графическую библиотеку, а Embox отвечал за встроенные системы.

До данной работы графические приложения для Embox могли разрабатываться только на фреймворке Qt, который, безусловно, является замечательным, поскольку он:

  • Кросс-платформенный
  • Содержит в себе много всего полезного
  • Открытый и хорошо отлаженный


Но в то же время Qt не всегда подходит для встроенных систем, поскольку:

  • Очень большой
  • Требовательный по ресурсам
  • Написан на С++, а не C


Кроме того, есть нюансы с лицензией. Короче, мы в проекте давно задумывались над портированием чего-нибудь легковесного и пристально смотрели в сторону уже упомянутого Храбровым Дмитрием DeXPeriX проекта Nuklear. Нам понравилось использование чистого С и маленькое количество кода (по сути, один заголовочный файл). Плюс прекрасная лицензия:
This software is dual-licensed to the public domain and under the following license: you are granted a perpetual, irrevocable license to copy, modify, publish and distribute this file as you see fit.

В общем, Nuklear прекрасно подходит для интеграции с другими проектами.

Конечно, поскольку это диплом, задача была не просто использовать библиотеку, которая понравилась научнику. Было рассмотрено 6 библиотек и выявлено два подхода к построению графических примитивов: retained и immediate. Кроме самих библиотек рассматривались и общие модели построения графических подсистем, начиная, конечно, с легендарной X11. Но поскольку основной акцент в работе был сделан на ограниченность ресурсов, то лучшим был признан своеобразный аналог directFB, присутствующий в Embox.

Возвращаясь к Nuklear, который по странному стечению обстоятельств всё-таки был выбран в качестве графической библиотеки, нужно отметить, что он имеет несколько вариантов рендереринга (наборов функций для отрисовки примитивов) под разные платформы, приведены примеры использования для X11, sdl и OpenGL. Для того, чтобы запустить его на другой платформе, необходимо реализовать собственный рендеринг. Для Embox рендеринга, естественно, не было. Первой практической задачей стала модификация существующего примера из репозитория Nuklear, чтобы он хоть как-то собрался и запустился на Embox. Для этого был выбран наиболее простой пример — canvas, который, по сути, демонстрирует вывод графических примитивов.

Приведу для сравнения код функции main

из оригинального примера
int main(int argc, char *argv[])
{
    /* Platform */
    static GLFWwindow *win;
    int width = 0, height = 0;
    /* GUI */
    struct device device;
    struct nk_font_atlas atlas;
    struct nk_context ctx;
    /* GLFW */
    glfwSetErrorCallback(error_callback);
    if (!glfwInit()) {
        fprintf(stdout, "[GFLW] failed to init!\n");
        exit(1);
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    win = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Demo", NULL, NULL);
    glfwMakeContextCurrent(win);
    glfwSetWindowUserPointer(win, &ctx);
    glfwSetCharCallback(win, text_input);
    glfwSetScrollCallback(win, scroll_input);
    glfwGetWindowSize(win, &width, &height);
    /* OpenGL */
    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    glewExperimental = 1;
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to setup GLEW\n");
        exit(1);
    }
    /* GUI */
    {device_init(&device);
    {const void *image; int w, h;
    struct nk_font *font;
    nk_font_atlas_init_default(&atlas);
    nk_font_atlas_begin(&atlas);
    font = nk_font_atlas_add_default(&atlas, 13, 0);
    image = nk_font_atlas_bake(&atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
    device_upload_atlas(&device, image, w, h);
    nk_font_atlas_end(&atlas, nk_handle_id((int)device.font_tex), &device.null);
    nk_init_default(&ctx, &font->handle);
    glEnable(GL_TEXTURE_2D);
    while (!glfwWindowShouldClose(win))
    {
        /* input */
        pump_input(&ctx, win);
        /* draw */
        {struct nk_canvas canvas;
        canvas_begin(&ctx, &canvas, 0, 0, 0, width, height, nk_rgb(250,250,250));
        {
            nk_fill_rect(canvas.painter, nk_rect(15,15,210,210), 5, nk_rgb(247, 230, 154));
            nk_fill_rect(canvas.painter, nk_rect(20,20,200,200), 5, nk_rgb(188, 174, 118));
            nk_draw_text(canvas.painter, nk_rect(30, 30, 150, 20), "Text to draw", 12, &font->handle, nk_rgb(188,174,118), nk_rgb(0,0,0));
            nk_fill_rect(canvas.painter, nk_rect(250,20,100,100), 0, nk_rgb(0,0,255));
            nk_fill_circle(canvas.painter, nk_rect(20,250,100,100), nk_rgb(255,0,0));
            nk_fill_triangle(canvas.painter, 250, 250, 350, 250, 300, 350, nk_rgb(0,255,0));
            nk_fill_arc(canvas.painter, 300, 180, 50, 0, 3.141592654f * 3.0f / 4.0f, nk_rgb(255,255,0));
            {float points[12];
            points[0] = 200; points[1] = 250;
            points[2] = 250; points[3] = 350;
            points[4] = 225; points[5] = 350;
            points[6] = 200; points[7] = 300;
            points[8] = 175; points[9] = 350;
            points[10] = 150; points[11] = 350;
            nk_fill_polygon(canvas.painter, points, 6, nk_rgb(0,0,0));}
            nk_stroke_line(canvas.painter, 15, 10, 200, 10, 2.0f, nk_rgb(189,45,75));
            nk_stroke_rect(canvas.painter, nk_rect(370, 20, 100, 100), 10, 3, nk_rgb(0,0,255));
            nk_stroke_curve(canvas.painter, 380, 200, 405, 270, 455, 120, 480, 200, 2, nk_rgb(0,150,220));
            nk_stroke_circle(canvas.painter, nk_rect(20, 370, 100, 100), 5, nk_rgb(0,255,120));
            nk_stroke_triangle(canvas.painter, 370, 250, 470, 250, 420, 350, 6, nk_rgb(255,0,143));
        }
        canvas_end(&ctx, &canvas);}
        /* Draw */
        glfwGetWindowSize(win, &width, &height);
        glViewport(0, 0, width, height);
        glClear(GL_COLOR_BUFFER_BIT);
        glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
        device_draw(&device, &ctx, width, height, NK_ANTI_ALIASING_ON);
        glfwSwapBuffers(win);
    }}}
    nk_font_atlas_clear(&atlas);
    nk_free(&ctx);
    device_shutdown(&device);
    glfwTerminate();
    return 0;
}


и модифицированного примера
int main(int argc, char *argv[]) {
    long int screensize = 0;
    uint8_t *fbp = 0;
    struct fb_info *fb_info;
    struct nk_color rgb_white = { .a = 0xff, .r = 0xff, .g = 0xff, .b = 0xff};
    fb_info = fb_lookup(0);
    printf("%dx%d, %dbpp\n", fb_info->var.xres, fb_info->var.yres,
            fb_info->var.bits_per_pixel);
    /* Figure out the size of the screen in bytes */
    screensize = fb_info->var.xres * fb_info->var.yres
            * fb_info->var.bits_per_pixel / 8;
    /* Map the device to memory */
    fbp = (uint8_t *) mmap_device_memory((void *) fb_info->screen_base,
            screensize, PROT_READ | PROT_WRITE, MAP_SHARED,
            (uint64_t) ((uintptr_t) fb_info->screen_base));
    if ((int) fbp == -1) {
        perror("Error: failed to map framebuffer device to memory");
        exit(4);
    }
    printf("The framebuffer device was mapped to memory successfully.\n");
    struct fb_fillrect rect;
    rect.dx = 0;
    rect.dy = 0;
    rect.width = fb_info->var.xres;
    rect.height = fb_info->var.yres;
    rect.rop = ROP_COPY;
    rect.color = rgba_to_device_color(fb_info, &rgb_white);
    fb_fillrect(fb_info, &rect);
    /* GUI */
    static struct nk_context ctx;
    static struct nk_canvas canvas;
    uint32_t width = 0, height = 0;
    static struct nk_user_font font;
    font.userdata.ptr = (void *) font_vga_8x8.data;
    font.height = font_vga_8x8.height;
    font.width = your_text_width_calculation;
    nk_init_default(&ctx, &font);
    width = fb_info->var.xres;
    height = fb_info->var.yres;
    /* Draw */
    while (1) {
        /* what to draw */
        canvas_begin(&ctx, &canvas, 0, 0, 0, width, height,
                nk_rgb(100, 100, 100));
        {
            canvas.painter->use_clipping = NK_CLIPPING_OFF;
            nk_fill_rect(canvas.painter, nk_rect(15, 15, 140, 140), 5,
                    nk_rgb(247, 230, 154));
            nk_fill_rect(canvas.painter, nk_rect(20, 20, 135, 135), 5,
                    nk_rgb(188, 174, 118));
            nk_draw_text(canvas.painter, nk_rect(30, 30, 100, 20),
                    "Text to draw", 12, &font, nk_rgb(188, 174, 118),
                    nk_rgb(0, 0, 0));
            nk_fill_rect(canvas.painter, nk_rect(160, 20, 70, 70), 0,
                    nk_rgb(0, 0, 255));
            nk_fill_circle(canvas.painter, nk_rect(20, 160, 60, 60),
                    nk_rgb(255, 0, 0));
            nk_fill_triangle(canvas.painter, 160, 160, 230, 160, 195, 220,
                    nk_rgb(0, 255, 0));
            nk_fill_arc(canvas.painter, 195, 120, 30, 0,
                    3.141592654f * 3.0f / 4.0f, nk_rgb(255, 255, 0));
            nk_stroke_line(canvas.painter, 15, 10, 100, 10, 2.0f,
                    nk_rgb(189, 45, 75));
            nk_stroke_rect(canvas.painter, nk_rect(235, 20, 70, 70), 10, 3,
                    nk_rgb(0, 0, 255));
            nk_stroke_curve(canvas.painter, 235, 130, 252, 170, 288, 80, 305,
                    130, 1, nk_rgb(0, 150, 220));
            nk_stroke_triangle(canvas.painter, 235, 160, 305, 160, 270, 220, 10,
                    nk_rgb(255, 0, 143));
            nk_stroke_circle(canvas.painter, nk_rect(90, 160, 60, 60), 2,
                    nk_rgb(0, 255, 120));
            {
                struct nk_image im;
                int w, h, format;
                struct nk_rect r;
                im.handle.ptr = stbi_load("SPBGU_logo.png", &w, &h, &format, 0);
                r = nk_rect(320, 10, w, h);
                im.w = w;
                im.h = h;
                im.region[0] = (unsigned short) 0;
                im.region[1] = (unsigned short) 0;
                im.region[2] = (unsigned short) r.w;
                im.region[3] = (unsigned short) r.h;
                printf("load %p, %d, %d, %d\n", im.handle.ptr, w, h, format);
                nk_draw_image(canvas.painter, r, &im, nk_rgb(100, 0, 0));
                stbi_image_free(im.handle.ptr);
            }
        }
        canvas_end(&ctx, &canvas);
        /* Draw each element */
        draw(fb_info, &ctx, width, height);
    }
    nk_free(&ctx);
    printf("\nEnd of program.\nIf you see it then something goes wrong.\n");
    return 0;
}


Код работы с библиотекой почти не претерпел изменений. Изменения касались загрузки своих шрифтов, различного функционала openGL и других специфичных платформенных частей.
Самая важная платформо-зависимая часть — это, конечно, отрисовка: функции device_draw и draw соответственно. Собственно, это вызов того самого рендеринга. Так как Nuklear по типу отрисовки относиться к imediate, то присутствует цикл, в котором постоянно отрисовывается сцена путем вызова этой функции. Сам код отрисовки следующий:

для openGL
static void device_draw(struct device *dev, struct nk_context *ctx, int width, int height,
    enum nk_anti_aliasing AA)
{
    GLfloat ortho[4][4] = {
        {2.0f, 0.0f, 0.0f, 0.0f},
        {0.0f,-2.0f, 0.0f, 0.0f},
        {0.0f, 0.0f,-1.0f, 0.0f},
        {-1.0f,1.0f, 0.0f, 1.0f},
    };
    ortho[0][0] /= (GLfloat)width;
    ortho[1][1] /= (GLfloat)height;
    /* setup global state */
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_SCISSOR_TEST);
    glActiveTexture(GL_TEXTURE0);
    /* setup program */
    glUseProgram(dev->prog);
    glUniform1i(dev->uniform_tex, 0);
    glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]);
    {
        /* convert from command queue into draw list and draw to screen */
        const struct nk_draw_command *cmd;
        void *vertices, *elements;
        const nk_draw_index *offset = NULL;
        /* allocate vertex and element buffer */
        glBindVertexArray(dev->vao);
        glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
        glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW);
        /* load draw vertices & elements directly into vertex + element buffer */
        vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
        {
            /* fill convert configuration */
            struct nk_convert_config config;
            static const struct nk_draw_vertex_layout_element vertex_layout[] = {
                {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_glfw_vertex, position)},
                {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_glfw_vertex, uv)},
                {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_glfw_vertex, col)},
                {NK_VERTEX_LAYOUT_END}
            };
            NK_MEMSET(&config, 0, sizeof(config));
            config.vertex_layout = vertex_layout;
            config.vertex_size = sizeof(struct nk_glfw_vertex);
            config.vertex_alignment = NK_ALIGNOF(struct nk_glfw_vertex);
            config.null = dev->null;
            config.circle_segment_count = 22;
            config.curve_segment_count = 22;
            config.arc_segment_count = 22;
            config.global_alpha = 1.0f;
            config.shape_AA = AA;
            config.line_AA = AA;
            /* setup buffers to load vertices and elements */
            {struct nk_buffer vbuf, ebuf;
            nk_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY);
            nk_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY);
            nk_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config);}
        }
        glUnmapBuffer(GL_ARRAY_BUFFER);
        glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
      /* iterate over and execute each draw command */
        nk_draw_foreach(cmd, ctx, &dev->cmds)
        {
            if (!cmd->elem_count) continue;
            glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
            glScissor(
                (GLint)(cmd->clip_rect.x),
                (GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h))),
                (GLint)(cmd->clip_rect.w),
                (GLint)(cmd->clip_rect.h));
            glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
            offset += cmd->elem_count;
        }
        nk_clear(ctx);
    }
    /* default OpenGL state */
    glUseProgram(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    glDisable(GL_BLEND);
    glDisable(GL_SCISSOR_TEST);
}


Как видно, здесь присутствует еще один цикл

/* iterate over and execute each draw command */
nk_draw_foreach(cmd, ctx, &dev->cmds)


Как нетрудно догадаться, этот цикл проходит по всем командам для отрисовки сцены, и рисует их средствами платформы.

Поэтому аналогичный код функции draw для Embox тоже содержит этот цикл
static inline void draw(struct fb_info *fb, struct nk_context *ctx, int width, int height) {
    assert(fb);
    assert(ctx);
    const struct nk_command *cmd;
    /* iterate over and execute each draw command */
    nk_foreach(cmd, ctx)
    {
        switch (cmd->type) {
        case NK_COMMAND_NOP:
            break;
        case NK_COMMAND_LINE: {
            const struct nk_command_line *c =
                    (const struct nk_command_line*) cmd;
            embox_stroke_line( fb, c->begin.x, c->begin.y, c->end.x, c->end.y,
                    &c->color, c->line_thickness);
        }
            break;
        case NK_COMMAND_CURVE: {
            const struct nk_command_curve *c =
                    (const struct nk_command_curve*) cmd;
            int x[4];
            int y[4];
            x[0] = c->begin.x;
            x[1] = c->ctrl[0].x;
            x[2] = c->ctrl[1].x;
            x[3] = c->end.x;
            y[0] = c->begin.y;
            y[1] = c->ctrl[0].y;
            y[2] = c->ctrl[1].y;
            y[3] = c->end.y;
            embox_stroke_curve( fb, x, y, &c->color, c->line_thickness);
        }
            break;
        case NK_COMMAND_RECT: {
            const struct nk_command_rect *c =
                    (const struct nk_command_rect*) cmd;
            embox_stroke_rect( fb, c->x, c->y, c->w, c->h, &c->color,
                    (float) c->rounding, c->line_thickness);
        }
            break;
        case NK_COMMAND_RECT_FILLED: {
            const struct nk_command_rect_filled *c =
                    (const struct nk_command_rect_filled*) cmd;
            embox_fill_rect( fb, c->x, c->y, c->w, c->h, &c->color);
        }
            break;
        case NK_COMMAND_CIRCLE: {
            const struct nk_command_circle *c =
                    (const struct nk_command_circle*) cmd;
            embox_stroke_circle( fb, (float) c->x + (float) c->w / 2,
                    (float) c->y + (float) c->h / 2, (float) c->w / 2,
                    &c->color, c->line_thickness);
        }
            break;
        case NK_COMMAND_CIRCLE_FILLED: {
            const struct nk_command_circle_filled *c =
                    (const struct nk_command_circle_filled *) cmd;
            embox_fill_circle( fb, c->x + c->w / 2, c->y + c->h / 2, c->w / 2,
                    &c->color);
        }
            break;
        case NK_COMMAND_ARC: {
            const struct nk_command_arc *c = (const struct nk_command_arc*) cmd;
            embox_stroke_arc( fb, c->cx, c->cy, c->r, c->a[0], c->a[1], &c->color,
                    c->line_thickness);
        }
            break;
        case NK_COMMAND_ARC_FILLED: {
            const struct nk_command_arc_filled *c =
                    (const struct nk_command_arc_filled*) cmd;
            embox_fill_arc( fb, c->cx, c->cy, c->r, c->a[0], c->a[1], &c->color);
        }
            break;
        case NK_COMMAND_TRIANGLE: {
            const struct nk_command_triangle *c =
                    (const struct nk_command_triangle*) cmd;
            embox_stroke_triangle( fb, c->a.x, c->a.y, c->b.x, c->b.y, c->c.x,
                    c->c.y, &c->color, c->line_thickness);
        }
            break;
        case NK_COMMAND_TRIANGLE_FILLED: {
            const struct nk_command_triangle_filled *c =
                    (const struct nk_command_triangle_filled*) cmd;
            embox_fill_triangle( fb, c->a.x, c->a.y, c->b.x, c->b.y, c->c.x,
                    c->c.y, &c->color);
        }
            break;
        case NK_COMMAND_TEXT: {
            const struct nk_command_text *c =
                    (const struct nk_command_text*) cmd;
            embox_add_text( fb, ctx, c->x, c->y, &c->foreground, &c->background, c->string,
                    c->length);
        }
            break;
        case NK_COMMAND_IMAGE: {
            const struct nk_command_image *c =
                    (const struct nk_command_image*) cmd;
            int color = rgba_to_device_color( fb, &c->col);
            embox_add_image( fb, c->img, c->x, c->y, c->w, c->h, color);
        }
            break;
            /* unrealized primitives */
            /*
             case NK_COMMAND_SCISSOR: {
             const struct nk_command_scissor *s = (const struct nk_command_scissor*)cmd;
             nk_draw_list_add_clip(&ctx->draw_list, nk_rect(s->x, s->y, s->w, s->h));
             } break;
             case NK_COMMAND_POLYGON: {
             int i;
             const struct nk_command_polygon*p = (const struct nk_command_polygon*)cmd;
             for (i = 0; i < p->point_count; ++i) {
             struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y);
             nk_draw_list_path_line_to(&ctx->draw_list, pnt);
             }
             nk_draw_list_path_stroke(&ctx->draw_list, p->color, NK_STROKE_CLOSED, p->line_thickness);
             } break;
             case NK_COMMAND_POLYGON_FILLED: {
             int i;
             const struct nk_command_polygon_filled *p = (const struct nk_command_polygon_filled*)cmd;
             for (i = 0; i < p->point_count; ++i) {
             struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y);
             nk_draw_list_path_line_to(&ctx->draw_list, pnt);
             }
             nk_draw_list_path_fill(&ctx->draw_list, p->color);
             } break;
             case NK_COMMAND_POLYLINE: {
             int i;
             const struct nk_command_polyline *p = (const struct nk_command_polyline*)cmd;
             for (i = 0; i < p->point_count; ++i) {
             struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y);
             nk_draw_list_path_line_to(&ctx->draw_list, pnt);
             }
             nk_draw_list_path_stroke(&ctx->draw_list, p->color, NK_STROKE_OPEN, p->line_thickness);
             } break;
             case NK_COMMAND_RECT_MULTI_COLOR: {
             const struct nk_command_rect_multi_color *r = (const struct nk_command_rect_multi_color*)cmd;
             nk_draw_list_fill_rect_multi_color(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h),
             r->left, r->top, r->right, r->bottom);
             } break; */
        default:
            break;
        }
    }
    nk_clear(ctx);
}


Тело цикла выглядит сильно по-другому, поскольку необходимо реализовать собственные примитивы, которые уже реализованы в OpenGL.

После реализации этих примитивов сцена стала отрисовываться корректно

xl96zvidtgl3qtjehrkjijonzuu.png
Герб СПбГУ, конечно, добавлен для диплома и не присутствует в оригинальном примере от nuklear:)

Конечно, не всё было так просто. Первой проблемой, которая мешала скомпилировать заголовочный файл в его оригинальном виде, стало то, что nuklear ориентирован на стандарт c89, где нет ключевого слова inline, но нет и предупреждений (warnings) на статические функции которые не используются. У нас по умолчанию используется c99, точнее gnu-расширение, и нам пришлось сделать PR в оригинальный nuklear для поддержки c99.

Ещё одной проблемой, напрямую не связанной с дипломом, было то, что формат пикселей бывает различный, и формат изображения в памяти может не совпадать с аппаратным форматом. До этого мы использовали простое преобразование из обычного RGB с 8 битами на каждый канал в тот или иной аппаратный формат в драйвере конкретного графического устройства, пришлось добавить прослойку для полноценного преобразования для разных форматов.

Всего было опробовано 3 платформы:

  • QEMU/x86 (графика boch)
  • QEMU/ARM (графика pl110)
  • STM32F7Discovery (встроенная графика)


С последней платформой возникло максимальное количество неприятностей: и проблема с выравниванием структур, и нехватка памяти для загрузки картинки, и книжная ориентация экрана (т.е. ширина изображения меньше высоты). Но в результате со всеми этими задачами Саша справилась, и захотелось запустить уже «настоящий» пример. Им стал тоже стандартный пример из nuklear skinning.

Внешний вид при запуске на QEMU/ARM

xgkgrvbpbd806q0fiqmxs2smjqi.png

Ну и фотографии с платой STM32F7Discovery

t-wgyz5dtewtdksnsiovhwh3uhg.jpeg
canvas

qtce_yw_vbddspcdnstzk3wraze.jpeg
skinning

Я не хочу пересказывать диплом, полный текст можно скачать тут. В завершение хочу отметить, что автор при написании диплома поучаствовала в нескольких живых проектах, получила практический опыт в реальной работе с распределёнными современными проектами. И не так важно, что авторов одного из этих проектов она знает лично, так всё-таки проще входить в проект. Ведь, как я уже сказал, данный проект не единственный, который использовался при написаний диплома. И на мой взгляд, дипломных работ на основе открытых действующих проектов должно быть как можно больше. Ведь это самый эффективный способ стать полноценным специалистом.

© Habrahabr.ru