Come usare GLUT / OpenGL per eseguire il rendering su un file?

Ho un programma che simula un sistema fisico che cambia nel tempo. Voglio, a intervalli prestabiliti (diciamo ogni 10 secondi), generare una visualizzazione dello stato della simulazione su un file. Voglio farlo in modo tale che sia facile “distriggersre la visualizzazione” e non produrre affatto la visualizzazione.

Sto guardando OpenGL e GLUT come strumenti grafici per fare la visualizzazione. Tuttavia, il problema sembra essere che, prima di tutto, sembra che venga stampato solo su una finestra e non possa essere stampato su un file. In secondo luogo, per generare la visualizzazione devi chiamare GLUTMainLoop e questo interrompe l’esecuzione della funzione principale – le uniche funzioni che vengono chiamate da quel momento in poi sono chiamate dalla GUI. Comunque non voglio che questa sia un’applicazione basata su GUI – Voglio che sia solo un’applicazione che si esegue dalla riga di comando, e genera una serie di immagini. C’è un modo per farlo in GLUT / OpenGL? O è OpenGL lo strumento sbagliato per questo completamente e dovrei usare qualcos’altro

Quasi certamente non vuoi GLUT, a prescindere. I tuoi requisiti non si adattano a ciò che è destinato a fare (e anche quando i tuoi requisiti sono adatti allo scopo previsto, di solito non lo vuoi comunque).

Puoi usare OpenGL. Per generare output in un file, in pratica si imposta OpenGL per il rendering su una texture, quindi si legge la trama risultante nella memoria principale e si salva in un file. Almeno su alcuni sistemi (ad es. Windows), sono abbastanza sicuro che dovrai ancora creare una finestra e associare il contesto di rendering alla finestra, anche se probabilmente andrà bene se la finestra è sempre nascosta.

Esempio PBO eseguibile

L’esempio seguente genera:

  • un ppm per frame a 200 FPS e nessuna dipendenza extra,
  • un png per frame a 600 FPS con libpng
  • un mpg per tutti i frame a 1200 FPS con FFmpeg

su un ramfs. Migliore è la compressione, più grande è l’FPS, quindi dobbiamo essere legati all’IO della memoria.

FPS è più grande di 200 sul mio schermo 60 FPS, e tutte le immagini sono diverse, quindi sono sicuro che non è limitato all’FPS dello schermo.

glReadPixels è la funzione OpenGL chiave che legge i pixel dallo schermo. Dai anche un’occhiata alla configurazione sotto init() .

glReadPixels legge per prima cosa la riga inferiore dei pixel, diversamente dalla maggior parte dei formati di immagine, quindi la conversione di solito è necessaria.

TODO: trova un modo per farlo su una macchina senza GUI (es. X11). Sembra che OpenGL non sia fatto per il rendering fuori schermo e che la lettura dei pixel sulla GPU sia implementata sull’interfaccia con il sistema di windows (ad es. GLX ). Vedi: OpenGL senza X.org in linux

TODO: usa una finestra 1×1, rendila non ridimensionabile e nascondi per rendere le cose più robuste. Se faccio uno di questi, il rendering fallisce, vedi i commenti al codice. Evitare di ridimensionare sembra imansible in Glut , ma GLFW lo supporta . In ogni caso, quelli non contano molto come il mio FPS non è limitato dalla frequenza di aggiornamento dello schermo, anche quando spento è spento.

 /* Turn output methods on and off. */ #define PPM 1 #define LIBPNG 1 #define FFMPEG 1 #include  #include  #include  #include  #define GL_GLEXT_PROTOTYPES 1 #include  #include  #include  #include  #if LIBPNG #include  #endif #if FFMPEG #include  #include  #include  #include  #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; } 

Su GitHub .

Compilare con:

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

Esegui “offscreen” (principalmente TODO, funziona ma non ha alcun vantaggio):

 ./a.out 

Esegui su schermo (non limita neanche il mio FPS):

 ./a.out 1 

Testato su Ubuntu 15.10, OpenGL 4.4.0 NVIDIA 352.63, Lenovo Thinkpad T430.

Altre opzioni oltre al PBO

  • render to backbuffer (luogo rendering predefinito)
  • rendere a una trama
  • renderizzare su un object Pixelbuffer (PBO)

Pixelbuffer e Pixelbuffer sono migliori del backbuffer e della texture poiché sono fatti per i dati da riacquisire alla CPU, mentre il backbuffer e le trame sono fatti per rimanere sulla GPU e mostrare sullo schermo.

PBO è per i trasferimenti asincroni, quindi penso che non ne abbiamo bisogno, vedere: Quali sono le differenze tra un object Frame Buffer e un object Buffel Pixel in OpenGL? ,

Forse la Mesa fuori schermo merita di essere esaminata: http://www.mesa3d.org/osmesa.html

apiretrace

https://github.com/apitrace/apitrace

Funziona e non richiede affatto di modificare il codice:

 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 

Ora hai un sacco di schermate denominate come:

 animation.out..png 

TODO: principio di funzionamento.

I documenti suggeriscono anche questo per il video:

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

Vulkan

Sembra che Vulkan sia progettato per supportare il rendering offscreen meglio di OpenGL.

Questo è menzionato in questa panoramica di NVIDIA: https://developer.nvidia.com/transitioning-opengl-vulkan

Esiste un esempio eseguibile all’indirizzo: https://github.com/SaschaWillems/Vulkan/blob/0616eeff4e697e4cd23cb9c97f5dd83afb79d908/offscreen/offscreen.cpp ma non sono ancora riuscito a far funzionare Vulkan. 1 kloc 🙂

Correlati: È ansible eseguire il rendering offscreen senza Surface in Vulkan?

Bibliografia

FBO più grande della finestra:

  • OpenGL come creare e renderizzare un framebuffer più grande della finestra?
  • FBO lwjgl più grande della dimensione dello schermo – Cosa sto facendo male?
  • Renderbuffer più grandi delle dimensioni della finestra – OpenGL
  • problema di salvataggio FBO openGL più grande della finestra

Nessuna finestra / X11:

  • OpenGL senza X.org in linux
  • Puoi creare il contesto OpenGL senza aprire una finestra?
  • Utilizzo di OpenGL senza sistema X-Window

Non per togliere le altre risposte eccellenti, ma se vuoi un esempio esistente abbiamo eseguito il rendering Offscreen GL per alcuni anni in OpenSCAD, come parte del rendering di Test Framework in file .png dalla riga di comando. I file rilevanti sono disponibili su https://github.com/openscad/openscad/tree/master/src sotto la schermata di avvio * .cc

Funziona su OSX (CGL), Linux X11 (GLX), BSD (GLX) e Windows (WGL), con alcune stranezze dovute a differenze di driver. Il trucco di base è quello di dimenticare di aprire una finestra (come, Douglas Adams dice che il trucco per volare è dimenticare di colpire il terreno). Funziona anche con linux / bsd “senza testa” se si ha un server X11 virtuale in esecuzione come Xvfb o Xvnc. C’è anche la possibilità di utilizzare il rendering del software su Linux / BSD impostando la variabile di ambiente LIBGL_ALWAYS_SOFTWARE = ​​1 prima di eseguire il programma, il che può aiutare in alcune situazioni.

Questo non è l’unico sistema per farlo, credo che il sistema di imaging VTK faccia qualcosa di simile.

Questo codice è un po ‘vecchio nei suoi metodi, (ho strappato il codice GLX dagli glxgears di Brian Paul), specialmente quando arrivano nuovi sistemi, OSMesa, Mir, Wayland, EGL, Android, Vulkan, ecc., Ma notate l’OffscreenXXX.cc nomi di file in cui XXX è il sottosistema di contesto GL, può in teoria essere portato su diversi generatori di contesto.

Non sono sicuro che OpenGL sia la soluzione migliore.
Ma puoi sempre eseguire il rendering su un buffer fuori campo.

Il modo tipico per scrivere l’output openGL in un file consiste nell’utilizzare readPixels per copiare il pixel-pixel della scena risultante in un file immagine

Potresti usare SFML http://www.sfml-dev.org/ . È ansible utilizzare la class di immagini per salvare l’output renderizzato.

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

Per ottenere l’output renderizzato, è ansible eseguire il rendering su una texture o copiare lo schermo.

Rendering per una trama:

Copia dell’output dello schermo: