shadermeh/shadermeh.c

404 lines
9.8 KiB
C

/* SPDX-License-Identifier: ISC */
/*
* shadermeh.c
*
* Copyright (C) 2022 David Oberhollenzer <goliath@infraroot.at>
*/
#include "shadermeh.h"
#include <fcntl.h>
#include <errno.h>
#define HAVE_ARCH_STRUCT_FLOCK
#include <linux/fcntl.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;
}else if(errno == EAGAIN){
perror("Output pipe probably has O_NONBLOCK "
"set! Removing setting");
ret = fcntl(STDOUT_FILENO, F_SETFL,
fcntl(STDOUT_FILENO,F_GETFL)
&(~O_NONBLOCK));
if(ret == -1){
perror("Failed to set STDOUT blocking");
return -1;
}
continue;
}else{
perror("write");
}
return -1;
}
if (ret == 0)
return -1;
size -= ret;
buffer = (const char *)buffer + ret;
}
return 0;
}
#define SND_BUFFER_SIZE 512
static int read_audio_buffers(int fd, float buffer[SND_BUFFER_SIZE],
unsigned int sampling_rate){
return 0;
}
static void normalize_audio_buffers(float buffer_in[SND_BUFFER_SIZE],
float buffer_out[SND_BUFFER_SIZE]){
if(buffer_in == NULL || buffer_out == NULL){
return;
}
for(size_t i = 0; i < SND_BUFFER_SIZE; i++){
buffer_out[i] = (buffer_in[i]+1.0)/2.0;
}
return;
}
static const struct option long_opts[] = {
{ "width", required_argument, NULL, 'w' },
{ "height", required_argument, NULL, 'h' },
{ "shader", required_argument, NULL, 's' },
{ "sampling-rate", required_argument, NULL, 'r' },
{ "to-stdout", no_argument, NULL, 'S' },
{ NULL, 0, NULL, 0 },
};
static const char *short_opts = "r:w:h:s:S";
static const char *usage_str =
"shadermeh OPTIONS...\n"
"\n"
"Possible options:\n"
"\n"
" --width, -w <pixels>\n"
" --height, -h <pixels>\n"
" --sampling-rate, -r <sampling rate[Hz]>\n"
"\n"
" --to-stdout, -S\n"
"\n"
" --shader, -s <shader file>\n"
"\n";
int main(int argc, char **argv)
{
GLuint u_iResolution, u_iTime, u_iTimeDelta, u_iFrame;
GLfloat u_iSampleRate;
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, ret;
unsigned int sampling_rate = 0; /* Leaving this at 0 disables audio
* input
*/
float in_samples[SND_BUFFER_SIZE]; /* Raw input floats from -1...1 */
float norm_samples[SND_BUFFER_SIZE]; /* Normalized samples from 0...1 */
/******************** 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 'r':
sampling_rate = strtol(optarg, NULL, 10);
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;
}
if(!isatty(STDOUT_FILENO)){
long old_pipe_size = fcntl(STDOUT_FILENO, F_GETPIPE_SZ);
if(old_pipe_size < 0){
perror("Failed to get stdout pipe size");
}else{
int psz = getpagesize();
size_t frame_size = (width * height * 3);
size_t frame_cnt = psz / frame_size;
if((psz % frame_size) != 0){
frame_cnt++;
}
size_t new_pipe_size = frame_size * frame_cnt;
int fin_psz = fcntl(STDOUT_FILENO, F_SETPIPE_SZ,
new_pipe_size);
if(fin_psz < 0){
fprintf(stderr, "Failed to set STDOUT "
"pipe size to %zu: %s\n",
new_pipe_size, strerror(errno));
}else{
fprintf(stderr, "stdout pipe size "
"change from %li to %zu "
"resulted in %i\n",
old_pipe_size,
new_pipe_size,
fin_psz);
}
/* A failure to change pipe size is not
* catastrophic, beacause the application will
* still perform as needed, just with a bigger
* or smaller buffer. */
}
}
}
if (sampling_rate != 0 && !isatty(STDIN_FILENO)){
fputs("Sampling rate specified and STDIN not a tty! "
"You habe been warnded!\n", stderr);
}
if(sampling_rate != 0){
ret = fcntl(STDIN_FILENO, F_SETFL,
fcntl(STDIN_FILENO,F_GETFL)
|(O_NONBLOCK));
if(ret == -1){
perror("Failed to set STDIN_FILENO to non-blocking "
"mode! This will likely mean 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);
fprintf(stderr,"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);
}
glUseProgram(prog);
/* uniforms */
u_iResolution = glGetUniformLocation(prog, "iResolution");
u_iTime = glGetUniformLocation(prog, "iTime");
u_iTimeDelta = glGetUniformLocation(prog, "iTimeDelta");
u_iFrame = glGetUniformLocation(prog, "iFrame;");
u_iSampleRate = glGetUniformLocation(prog, "iSampleRate");
glUniform3f(u_iResolution, width, height, 0.0f);
glUniform1f(u_iSampleRate, sampling_rate);
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);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
fbo_tex, 0);
} else {
window_show(wnd);
}
/******************** drawing loop ********************/
while (to_stdout || window_handle_events()) {
/* render image to FBO */
clock_gettime(CLOCK_MONOTONIC_RAW, &frame_start);
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) {
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA,
GL_UNSIGNED_BYTE, fb32);
convert_for_ffmpeg(fb32, fb24, width, height);
if (write_retry(STDOUT_FILENO, fb24,
width * height * 3)) {
break;
}
}
if(sampling_rate != 0){
if(read_audio_buffers(STDIN_FILENO, in_samples,
sampling_rate) < 0){
break;
}
normalize_audio_buffers(in_samples, norm_samples);
}
/* 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 ********************/
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &vbo);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
glDeleteProgram(prog);
if (to_stdout) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
glDeleteTextures(1, &fbo_tex);
}
window_make_current(NULL);
free(fb32);
free(fb24);
window_destroy(wnd);
return EXIT_SUCCESS;
}