Не секрет, что в у нас в проекте Автором является Александра Бутрова 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, который
Приведу для сравнения код функции 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.
После реализации этих примитивов сцена стала отрисовываться корректно

Герб СПбГУ, конечно, добавлен для диплома и не присутствует в оригинальном примере от nuklear :)
Конечно, не всё было так просто. Первой проблемой, которая мешала скомпилировать заголовочный файл в его оригинальном виде, стало то, что nuklear ориентирован на стандарт c89, где нет ключевого слова inline, но нет и предупреждений (warnings) на статические функции которые не используются. У нас по умолчанию используется c99, точнее gnu-расширение, и нам пришлось сделать PR в оригинальный nuklear для поддержки c99.
Ещё одной проблемой, напрямую не связанной с дипломом, было то, что формат пикселей бывает различный, и формат изображения в памяти может не совпадать с аппаратным форматом. До этого мы использовали простое преобразование из обычного RGB с 8 битами на каждый канал в тот или иной аппаратный формат в драйвере конкретного графического устройства, пришлось добавить прослойку для полноценного преобразования для разных форматов.
Всего было опробовано 3 платформы:
- QEMU/x86 (графика boch)
 - QEMU/ARM (графика pl110)
 - STM32F7Discovery (встроенная графика)
 
С последней платформой возникло максимальное количество неприятностей: и проблема с выравниванием структур, и нехватка памяти для загрузки картинки, и книжная ориентация экрана (т.е. ширина изображения меньше высоты). Но в результате со всеми этими задачами Саша справилась, и захотелось запустить уже “настоящий” пример. Им стал тоже стандартный пример из nuklear skinning.
Внешний вид при запуске на QEMU/ARM

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

canvas

skinning
Я не хочу пересказывать диплом, полный текст можно скачать тут. В завершение хочу отметить, что автор при написании диплома поучаствовала в нескольких живых проектах, получила практический опыт в реальной работе с распределёнными современными проектами. И не так важно, что авторов одного из этих проектов она знает лично, так всё-таки проще входить в проект. Ведь, как я уже сказал, данный проект не единственный, который использовался при написаний диплома. И на мой взгляд, дипломных работ на основе открытых действующих проектов должно быть как можно больше. Ведь это самый эффективный способ стать полноценным специалистом.
          
 
Lucidus
и
riky
кажется красный и синий каналы перепутаны
riky
кажется красный и синий каналы перепутаны prntscr.com/i461oy
abondarev Автор
Спасибо!
Сначала подумал, что Вы починили в коде, потом заметил синий стол :)
Поправленная фотка ниже.
abondarev Автор
Спасибо.
Может и тест, только я его в спешке не прошел :) Код взят из нашего мастера и запущен, про цвета пропустил.
Как отметили выше, дело в перепутанных каналах. В статье я это отметил, когда говорил про форматы. То есть stm-ка поддерживает не RGB формат BGR. Сейчас поправлю.
abondarev Автор
Еще раз спасибо, поправили.

DeXPeriX
Nuklear — прикольная штука. Как я понял, автор изначально сделал эту библиотеку для упрощения создания пользовательского интерфейса внутри игры. Так сказать, для главного меню и экрана опций. А в итоге вон что из библиотеки получается. Спасибо за интересное применение!
starsnet
А такой GUI www.microwindows.org для embox не подошел бы?
Сам искал для embox какой-нибудь gui ))
abondarev Автор
Конечно подошло бы. По хорошему, чем больше выбор тех или иных средств тем лучше для пользователя.
Nano-X рассматривали. И мы раньше и Саша при написании диплома. Ей он показался немного более тяжелым. Но проект очень интересный, так что если затащите будет очень круто!
starsnet
1. Ну, в вашем случае, случае Nano-X, скорее всего, будет тяжеловат.
2. Я правильно понимаю, что Nano-X'ом можно полноценно заменить X.org? (вопрос производительности — технический)
з.ы.
#офтоп
Nano-X как сокращенно — NaX (НаХ)? ;))) Напомнило первоначальное название DEMOS'a — UNAS (vs UNIX)
abondarev Автор
Ну для случая который рассматривался в дипломе, да, показался тяжеловат. Но случаи разные бывают, для некоторых вариантов он очень даже подходит.
Ну полную замену иксов вряд-ли кроме исков может обеспечить. Но для многих задач как раз не нужна вся полнота функциональности.
Например, автор wayland прямо пишет: «Wayland — новый графический сервер, который выполняет только крошечную часть функций Х, которые мы действительно используем, когда запускаем композитный рабочий стол.».
На wayland мы тоже облизывались :)