/* SPDX-License-Identifier: ISC */ /* * shadermeh.c * * Copyright (C) 2022 David Oberhollenzer */ #include "shadermeh.h" static GLfloat vertex_buffer[] = { -1.0f, -1.0f, 0.0f, /* lower left corner */ 1.0f, 1.0f, 1.0f, +1.0f, -1.0f, 0.0f, /* lower right corner */ 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, /* uper left corner */ 1.0f, 1.0f, 1.0f, +1.0f, +1.0f, 0.0f, /* upper right corner */ 1.0f, 1.0f, 1.0f, }; static double diff_timespec(const struct timespec *time1, const struct timespec *time0) { return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; } static void convert_for_ffmpeg(const uint8_t *in, uint8_t *out, size_t width, size_t height) { size_t x, y; for (y = 0; y < height; ++y) { const uint8_t *src = in + y * width * 4; uint8_t *dst = out + (height - 1 - y) * width * 3; for (x = 0; x < width; ++x) { *(dst++) = *(src++); *(dst++) = *(src++); *(dst++) = *(src++); ++src; } } } static int write_retry(int fd, const void *buffer, size_t size) { while (size > 0) { int ret = write(fd, buffer, size); if (ret < 0) { if (errno == EINTR) continue; perror("write"); return -1; } if (ret == 0) return -1; size -= ret; buffer = (const char *)buffer + ret; } return 0; } static const struct option long_opts[] = { { "width", required_argument, NULL, 'w' }, { "height", required_argument, NULL, 'h' }, { "shader", required_argument, NULL, 's' }, { "to-stdout", no_argument, NULL, 'S' }, { NULL, 0, NULL, 0 }, }; static const char *short_opts = "w:h:s:S"; static const char *usage_str = "shadermeh OPTIONS...\n" "\n" "Possible options:\n" "\n" " --width, -w \n" " --height, -h \n" "\n" " --to-stdout, -S\n" "\n" " --shader, -s \n" "\n"; int main(int argc, char **argv) { GLuint u_iResolution, u_iTime, u_iTimeDelta, u_iFrame; struct timespec start, frame_start, frame_end; unsigned int width, height, iFrame = 0; void *fb32 = NULL, *fb24 = NULL; const char *shader_file = NULL; GLuint vao, vbo, fbo, fbo_tex; GLint major, minor, prog; float iTime, iTimeDelta; bool to_stdout = false; window *wnd; int i; /******************** parse options ************************/ width = 800; height = 450; for (;;) { i = getopt_long(argc, argv, short_opts, long_opts, NULL); if (i == -1) break; switch (i) { case 'w': width = strtol(optarg, NULL, 10); break; case 'h': height = strtol(optarg, NULL, 10); break; case 's': shader_file = optarg; break; case 'S': to_stdout = true; break; default: fputs(usage_str, stderr); return EXIT_FAILURE; } } if (!shader_file) { fputs(usage_str, stderr); fputs("No shader file specified!\n", stderr); return EXIT_FAILURE; } if (to_stdout) { fb32 = calloc(1, width * height * 4); if (!fb32) { perror("allocating scratch framebuffer"); return EXIT_FAILURE; } fb24 = calloc(1, width * height * 3); if (!fb24) { perror("allocating scratch framebuffer"); free(fb32); return EXIT_FAILURE; } } /********** create window and make context current **********/ wnd = window_create(width, height, "shader meh..."); if (!wnd) { fputs("failed to create window", stderr); free(fb32); free(fb24); return EXIT_FAILURE; } window_make_current(wnd); window_set_vsync(wnd, 1); /******************** load entry points ********************/ glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { fputs("glewInit() error", stderr); window_destroy(wnd); free(fb32); free(fb24); return EXIT_FAILURE; } glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); if (!to_stdout) printf("OpenGL version %d.%d\n", major, minor); /******************** initialization ********************/ glViewport(0, 0, width, height); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); /* vertex buffer object & vertex array object */ glGenVertexArrays(1, &vao); glBindVertexArray(vao); glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_buffer), vertex_buffer, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLvoid *)(3 * sizeof(GLfloat))); /* shader */ prog = shader_program_load(shader_file); if (!shader_program_get_build_status(prog)) { shader_program_print_info_log(prog); goto fail_vao; } glUseProgram(prog); /* uniforms */ u_iResolution = glGetUniformLocation(prog, "iResolution"); u_iTime = glGetUniformLocation(prog, "iTime"); u_iTimeDelta = glGetUniformLocation(prog, "iTimeDelta"); u_iFrame = glGetUniformLocation(prog, "iFrame;"); glUniform3f(u_iResolution, width, height, 0.0f); clock_gettime(CLOCK_MONOTONIC_RAW, &start); /******************** framebuffer object ********************/ if (to_stdout) { glGenFramebuffers(1, &fbo); glGenTextures(1, &fbo_tex); glBindTexture(GL_TEXTURE_2D, fbo_tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, fbo_tex, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } else { window_show(wnd); } /******************** drawing loop ********************/ while (to_stdout || window_handle_events()) { /* render image to FBO */ clock_gettime(CLOCK_MONOTONIC_RAW, &frame_start); if (to_stdout) glBindFramebuffer(GL_FRAMEBUFFER, fbo); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); if (to_stdout) { glFlush(); } else { window_swap_buffers(wnd); } /* get image from FBO, dump to stdout */ if (to_stdout) { glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, fbo_tex); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, fb32); glBindTexture(GL_TEXTURE_2D, 0); convert_for_ffmpeg(fb32, fb24, width, height); if (write_retry(STDOUT_FILENO, fb24, width * height * 3)) { break; } } /* update timers */ clock_gettime(CLOCK_MONOTONIC_RAW, &frame_end); iFrame += 1; iTimeDelta = diff_timespec(&frame_end, &frame_start); iTime = diff_timespec(&frame_end, &start); glUniform1f(u_iTimeDelta, iTimeDelta); glUniform1f(u_iTime, iTime); glUniform1ui(u_iFrame, iFrame); } /******************** cleanup ********************/ if (to_stdout) { glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &fbo_tex); } glUseProgram(0); glDeleteProgram(prog); fail_vao: glBindBuffer(GL_ARRAY_BUFFER, 0); glDeleteBuffers(1, &vbo); glBindVertexArray(0); glDeleteVertexArrays(1, &vao); window_make_current(NULL); free(fb32); free(fb24); window_destroy(wnd); return EXIT_SUCCESS; }