Не секрет, что в у нас в проекте используют обучают студентов. Точнее, студенты на базе проекта осваивают практические аспекты системного программирования: пишут дипломы, курсовые, занимаются исследовательской деятельностью и так далее. Вот об одном дипломе, успешно защищённом прошлым летом, и пойдет речь в данной статье.
Автором является Александра Бутрова AleksandraButrova, тема “Разработка графической подсистемы для встроенных операционных систем”. При написании диплома были использованы три открытых проекта: Embox, Nuklear и stb. Последний использовался только для загрузки картинок, а вот Nuklear являлся, по сути, виновником торжества. Можно сказать, что работа свелась к интеграции Nuklear и Embox. Первый предоставлял лёгкую графическую библиотеку, а Embox отвечал за встроенные системы.
До данной работы графические приложения для Embox могли разрабатываться только на фреймворке Qt, который, безусловно, является замечательным, поскольку он:
Но в то же время Qt не всегда подходит для встроенных систем, поскольку:
Кроме того, есть нюансы с лицензией. Короче, мы в проекте давно задумывались над портированием чего-нибудь легковесного и пристально смотрели в сторону уже упомянутого Храбровым Дмитрием 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
Код работы с библиотекой почти не претерпел изменений. Изменения касались загрузки своих шрифтов, различного функционала openGL и других специфичных платформенных частей.
Самая важная платформо-зависимая часть — это, конечно, отрисовка: функции device_draw и draw соответственно. Собственно, это вызов того самого рендеринга. Так как Nuklear по типу отрисовки относиться к imediate, то присутствует цикл, в котором постоянно отрисовывается сцена путем вызова этой функции. Сам код отрисовки следующий:
Как видно, здесь присутствует еще один цикл
Как нетрудно догадаться, этот цикл проходит по всем командам для отрисовки сцены, и рисует их средствами платформы.
Тело цикла выглядит сильно по-другому, поскольку необходимо реализовать собственные примитивы, которые уже реализованы в OpenGL.
После реализации этих примитивов сцена стала отрисовываться корректно
Герб СПбГУ, конечно, добавлен для диплома и не присутствует в оригинальном примере от nuklear :)
Конечно, не всё было так просто. Первой проблемой, которая мешала скомпилировать заголовочный файл в его оригинальном виде, стало то, что nuklear ориентирован на стандарт c89, где нет ключевого слова inline, но нет и предупреждений (warnings) на статические функции которые не используются. У нас по умолчанию используется c99, точнее gnu-расширение, и нам пришлось сделать PR в оригинальный nuklear для поддержки c99.
Ещё одной проблемой, напрямую не связанной с дипломом, было то, что формат пикселей бывает различный, и формат изображения в памяти может не совпадать с аппаратным форматом. До этого мы использовали простое преобразование из обычного RGB с 8 битами на каждый канал в тот или иной аппаратный формат в драйвере конкретного графического устройства, пришлось добавить прослойку для полноценного преобразования для разных форматов.
Всего было опробовано 3 платформы:
С последней платформой возникло максимальное количество неприятностей: и проблема с выравниванием структур, и нехватка памяти для загрузки картинки, и книжная ориентация экрана (т.е. ширина изображения меньше высоты). Но в результате со всеми этими задачами Саша справилась, и захотелось запустить уже “настоящий” пример. Им стал тоже стандартный пример из nuklear skinning.
Внешний вид при запуске на QEMU/ARM
Ну и фотографии с платой STM32F7Discovery
canvas
skinning
Я не хочу пересказывать диплом, полный текст можно скачать тут. В завершение хочу отметить, что автор при написании диплома поучаствовала в нескольких живых проектах, получила практический опыт в реальной работе с распределёнными современными проектами. И не так важно, что авторов одного из этих проектов она знает лично, так всё-таки проще входить в проект. Ведь, как я уже сказал, данный проект не единственный, который использовался при написаний диплома. И на мой взгляд, дипломных работ на основе открытых действующих проектов должно быть как можно больше. Ведь это самый эффективный способ стать полноценным специалистом.
Автором является Александра Бутрова 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 мы тоже облизывались :)