mirror of
https://github.com/libretro/cpp-cheat.git
synced 2025-04-16 02:00:01 +00:00
400 lines
12 KiB
C
400 lines
12 KiB
C
/*
|
|
http://stackoverflow.com/questions/3191978/how-to-use-glut-opengl-to-render-to-a-file/14324292#14324292
|
|
*/
|
|
|
|
/* 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;
|
|
}
|