2010-07-07 24 views
16

Tengo un programa que simula un sistema físico que cambia con el tiempo. Quiero, en intervalos predeterminados (digamos cada 10 segundos), mostrar una visualización del estado de la simulación en un archivo. Quiero hacerlo de tal manera que sea fácil "apagar la visualización" y no mostrar la visualización en absoluto.¿Cómo usar GLUT/OpenGL para renderizar en un archivo?

Estoy buscando en OpenGL y GLUT como herramientas gráficas para hacer la visualización. Sin embargo, el problema parece ser que, en primer lugar, parece que solo se envía a una ventana y no puede enviarse a un archivo. En segundo lugar, para generar la visualización, debe llamar a GLUTMainLoop y eso detiene la ejecución de la función principal: las únicas funciones que reciben llamadas a partir de ese momento son las llamadas de la GUI. Sin embargo, no quiero que sea una aplicación basada en la GUI. Quiero que sea solo una aplicación que ejecute desde la línea de comandos, y genera una serie de imágenes. ¿Hay alguna manera de hacer esto en GLUT/OpenGL? O es OpenGL la herramienta equivocada para esto completamente y debería usar algo más

+0

¿Qué tan grande es el estado de la simulación, si tuviera que escribir eso en lugar de la visualización? –

Respuesta

8

Es casi seguro que no quiere GLUT, sin tener en cuenta. Sus requisitos no se ajustan a lo que se pretende (incluso cuando sus requisitos hacen se ajustan a su propósito previsto, por lo general no lo desea).

Puede usar OpenGL. Para generar resultados en un archivo, básicamente configura OpenGL para renderizar una textura y luego lee la textura resultante en la memoria principal y la guarda en un archivo. Al menos en algunos sistemas (por ejemplo, Windows), estoy bastante seguro de que todavía tendrá que crear una ventana y asociar el contexto de representación con la ventana, aunque será probablemente bien si la ventana está siempre oculta.

1

No estoy seguro de que OpenGL sea la mejor solución.
Pero siempre se puede procesar en un búfer fuera de la pantalla.

La forma típica de escribir la salida OpenGL para un archivo es utilizar readPixels copiar la escena píxel a píxel resultante a un archivo de imagen

1

Puede usar SFML http://www.sfml-dev.org/. Puede usar la clase de imagen para guardar su resultado renderizado.

http://www.sfml-dev.org/documentation/1.6/classsf_1_1Image.htm

para obtener su resultado representado, se puede prestar a una textura o copiar su pantalla.

representación de una textura:

copia la salida de pantalla:

37

Ejecutable ejemplo PBO

El siguiente ejemplo genera ya sea:

  • uno ppm por trama a 200 FPS y sin dependencias adicionales,
  • uno png por cuadro a 600 FPS con libpng
  • uno mpg para todos los marcos en 1200 FPS con FFmpeg

en un ramfs. Cuanto mejor sea la compresión, mayor será el FPS, por lo que debemos estar vinculados a IO de memoria.

FPS es más grande que 200 en mi pantalla de 60 FPS, y todas las imágenes son diferentes, por lo que estoy seguro de que no se limita al FPS de la pantalla.

glReadPixels es la función clave de OpenGL que lee los píxeles de la pantalla. También eche un vistazo a la configuración bajo init().

glReadPixels lee la línea inferior de píxeles primero, a diferencia de la mayoría de los formatos de imagen, por lo que la conversión que generalmente se necesita.

TODO: encuentre una manera de hacerlo en una máquina sin GUI (por ejemplo, X11). Parece que OpenGL simplemente no está hecho para la representación fuera de pantalla, y que la lectura de píxeles de vuelta a la GPU se implementa en la interfaz con el sistema de ventanas (por ejemplo, GLX). Ver: OpenGL without X.org in linux

TODO: utilice una ventana de 1x1, agrúpela y ocultela para hacer las cosas más robustas. Si hago cualquiera de esos, la representación falla, vea los comentarios del código. La prevención del cambio de tamaño parece impossible in Glut, pero GLFW supports it. En cualquier caso, esos no importan mucho ya que mi FPS no está limitado por la frecuencia de actualización de la pantalla, incluso cuando offscreen está desactivado.

/* Turn output methods on and off. */ 
#define PPM 1 
#define LIBPNG 1 
#define FFMPEG 1 

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define GL_GLEXT_PROTOTYPES 1 
#include <GL/gl.h> 
#include <GL/glu.h> 
#include <GL/glut.h> 
#include <GL/glext.h> 

#if LIBPNG 
#include <png.h> 
#endif 

#if FFMPEG 
#include <libavcodec/avcodec.h> 
#include <libavutil/imgutils.h> 
#include <libavutil/opt.h> 
#include <libswscale/swscale.h> 
#endif 

enum Constants { SCREENSHOT_MAX_FILENAME = 256 }; 
static GLubyte *pixels = NULL; 
static GLuint fbo; 
static GLuint rbo_color; 
static GLuint rbo_depth; 
static const unsigned int HEIGHT = 100; 
static const unsigned int WIDTH = 100; 
static int offscreen = 1; 
static unsigned int max_nframes = 100; 
static unsigned int nframes = 0; 
static unsigned int time0; 

/* Model. */ 
static double angle; 
static double delta_angle; 

#if PPM 
/* 
Take screenshot with glReadPixels and save to a file in PPM format. 
- filename: file path to save to, without extension 
- width: screen width in pixels 
- height: screen height in pixels 
- pixels: intermediate buffer to avoid repeated mallocs across multiple calls. 
    Contents of this buffer do not matter. May be NULL, in which case it is initialized. 
    You must `free` it when you won't be calling this function anymore. 
*/ 
static void screenshot_ppm(const char *filename, unsigned int width, 
     unsigned int height, GLubyte **pixels) { 
    size_t i, j, k, cur; 
    const size_t format_nchannels = 3; 
    FILE *f = fopen(filename, "w"); 
    fprintf(f, "P3\n%d %d\n%d\n", width, height, 255); 
    *pixels = realloc(*pixels, format_nchannels * sizeof(GLubyte) * width * height); 
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, *pixels); 
    for (i = 0; i < height; i++) { 
     for (j = 0; j < width; j++) { 
      cur = format_nchannels * ((height - i - 1) * width + j); 
      fprintf(f, "%3d %3d %3d ", (*pixels)[cur], (*pixels)[cur + 1], (*pixels)[cur + 2]); 
     } 
     fprintf(f, "\n"); 
    } 
    fclose(f); 
} 
#endif 

#if LIBPNG 
/* Adapted from https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/png/open_manipulate_write.c */ 
static png_byte *png_bytes = NULL; 
static png_byte **png_rows = NULL; 
static void screenshot_png(const char *filename, unsigned int width, unsigned int height, 
     GLubyte **pixels, png_byte **png_bytes, png_byte ***png_rows) { 
    size_t i, nvals; 
    const size_t format_nchannels = 4; 
    FILE *f = fopen(filename, "wb"); 
    nvals = format_nchannels * width * height; 
    *pixels = realloc(*pixels, nvals * sizeof(GLubyte)); 
    *png_bytes = realloc(*png_bytes, nvals * sizeof(png_byte)); 
    *png_rows = realloc(*png_rows, height * sizeof(png_byte*)); 
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels); 
    for (i = 0; i < nvals; i++) 
     (*png_bytes)[i] = (*pixels)[i]; 
    for (i = 0; i < height; i++) 
     (*png_rows)[height - i - 1] = &(*png_bytes)[i * width * format_nchannels]; 
    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 
    if (!png) abort(); 
    png_infop info = png_create_info_struct(png); 
    if (!info) abort(); 
    if (setjmp(png_jmpbuf(png))) abort(); 
    png_init_io(png, f); 
    png_set_IHDR(
     png, 
     info, 
     width, 
     height, 
     8, 
     PNG_COLOR_TYPE_RGBA, 
     PNG_INTERLACE_NONE, 
     PNG_COMPRESSION_TYPE_DEFAULT, 
     PNG_FILTER_TYPE_DEFAULT 
    ); 
    png_write_info(png, info); 
    png_write_image(png, *png_rows); 
    png_write_end(png, NULL); 
    png_destroy_write_struct(&png, &info); 
    fclose(f); 
} 
#endif 

#if FFMPEG 
/* Adapted from: https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/ffmpeg/encode.c */ 

static AVCodecContext *c = NULL; 
static AVFrame *frame; 
static AVPacket pkt; 
static FILE *file; 
static struct SwsContext *sws_context = NULL; 
static uint8_t *rgb = NULL; 

static void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *rgb) { 
    const int in_linesize[1] = { 4 * c->width }; 
    sws_context = sws_getCachedContext(sws_context, 
      c->width, c->height, AV_PIX_FMT_RGB32, 
      c->width, c->height, AV_PIX_FMT_YUV420P, 
      0, NULL, NULL, NULL); 
    sws_scale(sws_context, (const uint8_t * const *)&rgb, in_linesize, 0, 
      c->height, frame->data, frame->linesize); 
} 

void ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height) { 
    AVCodec *codec; 
    int ret; 
    avcodec_register_all(); 
    codec = avcodec_find_encoder(codec_id); 
    if (!codec) { 
     fprintf(stderr, "Codec not found\n"); 
     exit(1); 
    } 
    c = avcodec_alloc_context3(codec); 
    if (!c) { 
     fprintf(stderr, "Could not allocate video codec context\n"); 
     exit(1); 
    } 
    c->bit_rate = 400000; 
    c->width = width; 
    c->height = height; 
    c->time_base.num = 1; 
    c->time_base.den = fps; 
    c->gop_size = 10; 
    c->max_b_frames = 1; 
    c->pix_fmt = AV_PIX_FMT_YUV420P; 
    if (codec_id == AV_CODEC_ID_H264) 
     av_opt_set(c->priv_data, "preset", "slow", 0); 
    if (avcodec_open2(c, codec, NULL) < 0) { 
     fprintf(stderr, "Could not open codec\n"); 
     exit(1); 
    } 
    file = fopen(filename, "wb"); 
    if (!file) { 
     fprintf(stderr, "Could not open %s\n", filename); 
     exit(1); 
    } 
    frame = av_frame_alloc(); 
    if (!frame) { 
     fprintf(stderr, "Could not allocate video frame\n"); 
     exit(1); 
    } 
    frame->format = c->pix_fmt; 
    frame->width = c->width; 
    frame->height = c->height; 
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32); 
    if (ret < 0) { 
     fprintf(stderr, "Could not allocate raw picture buffer\n"); 
     exit(1); 
    } 
} 

void ffmpeg_encoder_finish(void) { 
    uint8_t endcode[] = { 0, 0, 1, 0xb7 }; 
    int got_output, ret; 
    do { 
     fflush(stdout); 
     ret = avcodec_encode_video2(c, &pkt, NULL, &got_output); 
     if (ret < 0) { 
      fprintf(stderr, "Error encoding frame\n"); 
      exit(1); 
     } 
     if (got_output) { 
      fwrite(pkt.data, 1, pkt.size, file); 
      av_packet_unref(&pkt); 
     } 
    } while (got_output); 
    fwrite(endcode, 1, sizeof(endcode), file); 
    fclose(file); 
    avcodec_close(c); 
    av_free(c); 
    av_freep(&frame->data[0]); 
    av_frame_free(&frame); 
} 

void ffmpeg_encoder_encode_frame(uint8_t *rgb) { 
    int ret, got_output; 
    ffmpeg_encoder_set_frame_yuv_from_rgb(rgb); 
    av_init_packet(&pkt); 
    pkt.data = NULL; 
    pkt.size = 0; 
    ret = avcodec_encode_video2(c, &pkt, frame, &got_output); 
    if (ret < 0) { 
     fprintf(stderr, "Error encoding frame\n"); 
     exit(1); 
    } 
    if (got_output) { 
     fwrite(pkt.data, 1, pkt.size, file); 
     av_packet_unref(&pkt); 
    } 
} 

void ffmpeg_encoder_glread_rgb(uint8_t **rgb, GLubyte **pixels, unsigned int width, unsigned int height) { 
    size_t i, j, k, cur_gl, cur_rgb, nvals; 
    const size_t format_nchannels = 4; 
    nvals = format_nchannels * width * height; 
    *pixels = realloc(*pixels, nvals * sizeof(GLubyte)); 
    *rgb = realloc(*rgb, nvals * sizeof(uint8_t)); 
    /* Get RGBA to align to 32 bits instead of just 24 for RGB. May be faster for FFmpeg. */ 
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels); 
    for (i = 0; i < height; i++) { 
     for (j = 0; j < width; j++) { 
      cur_gl = format_nchannels * (width * (height - i - 1) + j); 
      cur_rgb = format_nchannels * (width * i + j); 
      for (k = 0; k < format_nchannels; k++) 
       (*rgb)[cur_rgb + k] = (*pixels)[cur_gl + k]; 
     } 
    } 
} 
#endif 

static int model_init(void) { 
    angle = 0; 
    delta_angle = 1; 
} 

static int model_update(void) { 
    angle += delta_angle; 
    return 0; 
} 

static int model_finished(void) { 
    return nframes >= max_nframes; 
} 

static void init(void) { 
    int glget; 

    if (offscreen) { 
     /* Framebuffer */ 
     glGenFramebuffers(1, &fbo); 
     glBindFramebuffer(GL_FRAMEBUFFER, fbo); 

     /* Color renderbuffer. */ 
     glGenRenderbuffers(1, &rbo_color); 
     glBindRenderbuffer(GL_RENDERBUFFER, rbo_color); 
     /* Storage must be one of: */ 
     /* GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8. */ 
     glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, WIDTH, HEIGHT); 
     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo_color); 

     /* Depth renderbuffer. */ 
     glGenRenderbuffers(1, &rbo_depth); 
     glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth); 
     glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, WIDTH, HEIGHT); 
     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth); 

     glReadBuffer(GL_COLOR_ATTACHMENT0); 

     /* Sanity check. */ 
     assert(glCheckFramebufferStatus(GL_FRAMEBUFFER)); 
     glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glget); 
     assert(WIDTH * HEIGHT < (unsigned int)glget); 
    } else { 
     glReadBuffer(GL_BACK); 
    } 

    glClearColor(0.0, 0.0, 0.0, 0.0); 
    glEnable(GL_DEPTH_TEST); 
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
    glViewport(0, 0, WIDTH, HEIGHT); 
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity(); 
    glMatrixMode(GL_MODELVIEW); 

    time0 = glutGet(GLUT_ELAPSED_TIME); 
    model_init(); 
#if FFMPEG 
    ffmpeg_encoder_start("tmp.mpg", AV_CODEC_ID_MPEG1VIDEO, 25, WIDTH, HEIGHT); 
#endif 
} 

static void deinit(void) { 
    printf("FPS = %f\n", 1000.0 * nframes/(double)(glutGet(GLUT_ELAPSED_TIME) - time0)); 
    free(pixels); 
#if LIBPNG 
    free(png_bytes); 
    free(png_rows); 
#endif 
#if FFMPEG 
    ffmpeg_encoder_finish(); 
    free(rgb); 
#endif 
    if (offscreen) { 
     glDeleteFramebuffers(1, &fbo); 
     glDeleteRenderbuffers(1, &rbo_color); 
     glDeleteRenderbuffers(1, &rbo_depth); 
    } 
} 

static void draw_scene(void) { 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    glLoadIdentity(); 
    glRotatef(angle, 0.0f, 0.0f, -1.0f); 
    glBegin(GL_TRIANGLES); 
    glColor3f(1.0f, 0.0f, 0.0f); 
    glVertex3f(0.0f, 0.5f, 0.0f); 
    glColor3f(0.0f, 1.0f, 0.0f); 
    glVertex3f(-0.5f, -0.5f, 0.0f); 
    glColor3f(0.0f, 0.0f, 1.0f); 
    glVertex3f(0.5f, -0.5f, 0.0f); 
    glEnd(); 
} 

static void display(void) { 
    char extension[SCREENSHOT_MAX_FILENAME]; 
    char filename[SCREENSHOT_MAX_FILENAME]; 
    draw_scene(); 
    if (offscreen) { 
     glFlush(); 
    } else { 
     glutSwapBuffers(); 
    } 
#if PPM 
    snprintf(filename, SCREENSHOT_MAX_FILENAME, "tmp%d.ppm", nframes); 
    screenshot_ppm(filename, WIDTH, HEIGHT, &pixels); 
#endif 
#if LIBPNG 
    snprintf(filename, SCREENSHOT_MAX_FILENAME, "tmp%d.png", nframes); 
    screenshot_png(filename, WIDTH, HEIGHT, &pixels, &png_bytes, &png_rows); 
#endif 
# if FFMPEG 
    frame->pts = nframes; 
    ffmpeg_encoder_glread_rgb(&rgb, &pixels, WIDTH, HEIGHT); 
    ffmpeg_encoder_encode_frame(rgb); 
#endif 
    nframes++; 
    if (model_finished()) 
     exit(EXIT_SUCCESS); 
} 

static void idle(void) { 
    while (model_update()); 
    glutPostRedisplay(); 
} 

int main(int argc, char **argv) { 
    GLint glut_display; 
    glutInit(&argc, argv); 
    if (argc > 1) 
     offscreen = 0; 
    if (offscreen) { 
     /* TODO: if we use anything smaller than the window, it only renders a smaller version of things. */ 
     /*glutInitWindowSize(50, 50);*/ 
     glutInitWindowSize(WIDTH, HEIGHT); 
     glut_display = GLUT_SINGLE; 
    } else { 
     glutInitWindowSize(WIDTH, HEIGHT); 
     glutInitWindowPosition(100, 100); 
     glut_display = GLUT_DOUBLE; 
    } 
    glutInitDisplayMode(glut_display | GLUT_RGBA | GLUT_DEPTH); 
    glutCreateWindow(argv[0]); 
    if (offscreen) { 
     /* TODO: if we hide the window the program blocks. */ 
     /*glutHideWindow();*/ 
    } 
    init(); 
    glutDisplayFunc(display); 
    glutIdleFunc(idle); 
    atexit(deinit); 
    glutMainLoop(); 
    return EXIT_SUCCESS; 
} 

On GitHub.

compilar con:

gcc main.c -lGL -lGLU -lglut #-lpng -lavcodec -lswscale -lavutil 

Ejecutar "fuera de la pantalla" (en su mayoría TODO, funciona pero no tiene ninguna ventaja):

./a.out 

Ejecutar en la pantalla (no limita mi FPS tampoco):

./a.out 1 

Probado en Ubuntu 15.10, OpenGL 4.4.0 NVIDIA 352.63, Lenovo Thinkpad T430.

otras opciones además de PBO

  • rendir a backbuffer (por defecto rendir lugar)
  • rinden a una textura
  • rendir a un objeto Pixelbuffer (PBO)

Framebuffer y Pixelbuffer son mejores que el backbuffer y la textura, ya que están hechos para que los datos se lean nuevamente a CP U, mientras que el backbuffer y las texturas están hechos para permanecer en la GPU y mostrarse en la pantalla.

PBO es para transferencias asíncronas, así que creo que no lo necesitamos, consulte: What are the differences between a Frame Buffer Object and a Pixel Buffer Object in OpenGL?,

Tal vez fuera de la pantalla Mesa es vale la pena analizar: http://www.mesa3d.org/osmesa.html

apiretrace

https://github.com/apitrace/apitrace

Simplemente funciona, y no requiere que modifique su código en absoluto:

git clone https://github.com/apitrace/apitrace 
cd apitrace 
git checkout 7.0 
mkdir build 
cd build 
cmake .. 
make 
# Creates opengl_executable.out.trace 
./apitrace /path/to/opengl_executable.out 
./apitrace dump-images opengl_executable.out.trace 

Ahora tiene un montón de capturas de pantalla nombradas como:

animation.out.<n>.png 

TODO: principio de funcionamiento.

Los documentos también sugieren esto para vídeo:

apitrace dump-images -o - application.trace \ 
    | ffmpeg -r 30 -f image2pipe -vcodec ppm -i pipe: -vcodec mpeg4 -y output.mp4 

Vulkan

Parece que Vulkan está diseñado para soportar fuera de la pantalla haciendo mejor que OpenGL.

Esto se menciona en esta descripción general de NVIDIA: https://developer.nvidia.com/transitioning-opengl-vulkan

No es un ejemplo ejecutable en: https://github.com/SaschaWillems/Vulkan/blob/0616eeff4e697e4cd23cb9c97f5dd83afb79d908/offscreen/offscreen.cpp pero no han logrado llegar Vulkan todavía en funcionamiento.1 kloc :-)

relacionadas: Is it possible to do offscreen rendering without Surface in Vulkan?

Bibliografía

OBF más grande que la ventana:

sin ventana/X11:

+1

Después de la línea 'png_write_end (png, NULL);' debe anular la planificación de las estructuras PNG con una línea como 'png_destroy_write_struct (y png, &info);' Gracias por el código práctico – mgmalheiros

+0

@mgmalheiros boa, Marcelo –

1

no tomar lejos de las otras respuestas excelentes, pero si quieres un ejemplo existentes que han estado haciendo Offscreen GL prestación durante unos años en OpenSCAD, como parte del Marco de test de renderizado a. png archivos de la línea de comandos. Los archivos relevantes están en https://github.com/openscad/openscad/tree/master/src en Offscreen * .cc

Funciona en OSX (CGL), Linux X11 (GLX), BSD (GLX) y Windows (WGL), con algunas peculiaridades debido a las diferencias de controladores. El truco básico es olvidarse de abrir una ventana (como, Douglas Adams dice que el truco para volar es olvidarse de tocar la tierra). Incluso se ejecuta en linux/bsd 'sin cabeza' si tiene un servidor X11 virtual ejecutándose como Xvfb o Xvnc. También existe la posibilidad de utilizar Software Rendering en Linux/BSD estableciendo la variable de entorno LIBGL_ALWAYS_SOFTWARE = ​​1 antes de ejecutar su programa, lo que puede ayudar en algunas situaciones.

Este no es el único sistema para hacer esto, creo que el sistema de imágenes VTK hace algo similar.

Este código es un poco viejo en sus métodos, (arranqué el código GLX de los glxgears de Brian Paul), especialmente a medida que aparecen nuevos sistemas, OSMesa, Mir, Wayland, EGL, Android, Vulkan, etc. los nombres de archivo OffscreenXXX.cc donde XXX es el subsistema de contexto GL, en teoría puede ser portado a diferentes generadores de contexto.

+1

extracto A.!! ejemplo mínimo ejecutable y tengo mi voto popular :-) –

Cuestiones relacionadas