Вопрос или проблема
Я хочу создать свою собственную библиотеку рендеринга примитивным способом, не используя OpenGL или что-то подобное. Но я не хочу связываться с управлением окнами/вводом, специфичным для платформы. Поэтому мне нужна библиотека, подобная GLFW. Но, насколько я знаю, GLFW не может рендерить сырые пиксельные данные.
</div><div class="s-prose js-post-body" itemprop="text">
Tsoding использует SDL для этого:
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
{
if (SDL_Init(SDL_INIT_VIDEO) < 0) return_defer(1);
window = SDL_CreateWindow("Olivec", 0, 0, 0, 0, 0);
if (window == NULL) return_defer(1);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) return_defer(1);
Uint32 prev = SDL_GetTicks();
bool pause = false;
for (;;) {
// Вычислить время между кадрами
Uint32 curr = SDL_GetTicks();
float dt = (curr - prev)/1000.f;
prev = curr;
// Обработать события
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT: {
return_defer(0);
} break;
case SDL_KEYDOWN: {
if (event.key.keysym.sym == SDLK_SPACE) pause = !pause;
} break;
}
}
SDL_Rect window_rect = {0, 0, vc_sdl_actual_width, vc_sdl_actual_height};
if (!pause) {
// Отрисовать текстуру
Olivec_Canvas oc_src = vc_render(dt);
if (oc_src.width != vc_sdl_actual_width || oc_src.height != vc_sdl_actual_height) {
if (!vc_sdl_resize_texture(renderer, oc_src.width, oc_src.height)) return_defer(1);
SDL_SetWindowSize(window, vc_sdl_actual_width, vc_sdl_actual_height);
}
void *pixels_dst;
int pitch;
if (SDL_LockTexture(vc_sdl_texture, &window_rect, &pixels_dst, &pitch) < 0) return_defer(1);
for (size_t y = 0; y < vc_sdl_actual_height; ++y) {
// TODO: было бы здорово, если бы Olivec_Canvas поддерживал шаг в байтах вместо пикселей
// Это было бы более гибким, и мы могли бы рисовать непосредственно в заблокированную память текстуры
memcpy((char*)pixels_dst + y*pitch, oc_src.pixels + y*vc_sdl_actual_width, vc_sdl_actual_width*sizeof(uint32_t));
}
SDL_UnlockTexture(vc_sdl_texture);
}
// Отобразить текстуру
if (SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0) < 0) return_defer(1);
if (SDL_RenderClear(renderer) < 0) return_defer(1);
if (SDL_RenderCopy(renderer, vc_sdl_texture, &window_rect, &window_rect) < 0) return_defer(1);
SDL_RenderPresent(renderer);
}
}
</div>.
Ответ или решение
Для создания простой кроссплатформенной библиотеки для рендеринга массива пикселей без использования OpenGL имеется несколько подходящих вариантов, не прибегая к управлению окнами и вводом на уровне платформы. Рассмотрим подход с использованием библиотеки SDL, которая широко используется и поддерживает работу с графикой, вводом и звуком.
Зачем использовать SDL?
SDL (Simple DirectMedia Layer) представляет собой мощную и универсальную библиотеку, которая обеспечивает кроссплатформенные функции для работы с графикой, звуком и вводом. Она отлично подходит для создания собственных рендеринг-решений, позволяя вам сосредоточиться на обработке данных пикселей, не заботясь о низкоуровневом управлении окнами и событиями. SDL позволяет легко отображать массивы пикселей, а также обрабатывать события ввода от пользователя.
Пример реализации рендеринга пикселей с использованием SDL
Ниже приведен пример кода, который демонстрирует, как с помощью SDL создать окно и рендерить массив пикселей.
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
void render_pixels(SDL_Renderer *renderer, uint32_t *pixels, int width, int height) {
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
if (!texture) {
fprintf(stderr, "Unable to create texture: %s\n", SDL_GetError());
return;
}
void *texture_pixels;
int pitch;
if (SDL_LockTexture(texture, NULL, &texture_pixels, &pitch) < 0) {
fprintf(stderr, "Unable to lock texture: %s\n", SDL_GetError());
return;
}
memcpy(texture_pixels, pixels, height * pitch);
SDL_UnlockTexture(texture);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_DestroyTexture(texture);
}
int main(int argc, char *argv[]) {
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Could not initialize SDL: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Pixel Renderer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN);
if (!window) {
fprintf(stderr, "Could not create window: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
fprintf(stderr, "Could not create renderer: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
uint32_t pixels[800 * 600]; // массив для хранения пикселей
memset(pixels, 0, sizeof(pixels)); // инициализация черным цветом
// Основной цикл
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}
}
render_pixels(renderer, pixels, 800, 600);
SDL_RenderPresent(renderer);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Объяснение кода
- Инициализация SDL: Код начинается с инициализации библиотеки SDL.
- Создание окна и рендерера: Мы создаем окно и рендерер, которые необходимы для отображения графики.
- Рендеринг пикселей: В функции
render_pixels
мы создаем текстуру, на которую будем записывать данные о пикселях. Мы используемSDL_LockTexture
для получения прямого доступа к памяти текстуры и копируем туда массив пикселей. - Основной цикл: В основном цикле приложения обрабатываются события (например, закрытие окна), и вызывается функция рендеринга.
Заключение
Использование библиотеки SDL для создания собственного рендеринга пикселей представляет собой мощный и простой способ, позволяющий избежать сложностей платформозависимого управления окнами и вводом. Этот пример демонстрирует основные концепции, которые вы можете развивать и адаптировать для более сложных задач. SDL предоставляет надежное решение и достаточно гибкости для реализации различных графических проектов, соответствующих вашим требованиям.