gr-fosphor/lib/fosphor/gl.c

785 lines
18 KiB
C

/*
* gl.c
*
* OpenGL part of fosphor
*
* Copyright (C) 2013-2014 Sylvain Munaut
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*! \addtogroup gl
* @{
*/
/*! \file gl.c
* \brief OpenGL part of fosphor
*/
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gl_platform.h"
#include "axis.h"
#include "fosphor.h"
#include "gl.h"
#include "gl_cmap.h"
#include "gl_cmap_gen.h"
#include "gl_font.h"
#include "private.h"
#include "resource.h"
struct fosphor_gl_state
{
int init_complete;
struct gl_font *font;
struct fosphor_gl_cmap_ctx *cmap_ctx;
GLuint cmap_waterfall;
GLuint cmap_histogram;
GLuint tex_waterfall;
GLuint tex_histogram;
GLuint vbo_spectrum;
};
/* -------------------------------------------------------------------------- */
/* Helpers / Internal API */
/* -------------------------------------------------------------------------- */
static int
gl_check_extension(const char *ext_name)
{
const char *ext_str;
const char *p;
int l = strlen(ext_name);
ext_str = (const char *)glGetString(GL_EXTENSIONS);
if (!ext_str) {
fprintf(stderr, "[w] Failed to retrieve GL extension list.\n");
return 0;
}
for (p=ext_str; (p=strstr(p, ext_name)) != NULL; p++)
{
if ((p != ext_str) && (p[-1] != ' '))
continue;
if ((p[l] != 0x00) && (p[l] != ' '))
continue;
return 1;
}
return 0;
}
#if 0
static void
gl_tex2d_float_clear(GLuint tex_id, int width, int height)
{
float buf[16*16];
int x, y, cw, ch;
memset(buf, 0x00, sizeof(buf));
glBindTexture(GL_TEXTURE_2D, tex_id);
for (y=0; y<height; y+=16) {
for (x=0; x<width; x+=16) {
cw = ((x+16) > width ) ? (width - x) : 16;
ch = ((y+16) > height) ? (height - y) : 16;
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, cw, ch, GL_RED, GL_FLOAT, buf);
}
}
}
#endif
static void
gl_tex2d_write(GLuint tex_id, float *src, int width, int height)
{
glBindTexture(GL_TEXTURE_2D, tex_id);
glTexSubImage2D(
GL_TEXTURE_2D, 0,
0, 0, width, height,
GL_RED, GL_FLOAT,
src
);
}
#if 0
static void
gl_vbo_clear(GLuint vbo_id, int size)
{
void *ptr;
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
if (!ptr)
abort();
memset(ptr, 0x00, size);
glUnmapBuffer(GL_ARRAY_BUFFER);
}
static void
gl_vbo_read(GLuint vbo_id, void *dst, int size)
{
void *ptr;
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
if (!ptr)
abort();
memcpy(dst, ptr, size);
glUnmapBuffer(GL_ARRAY_BUFFER);
}
#endif
static void
gl_vbo_write(GLuint vbo_id, void *src, int size)
{
#if 0
void *ptr;
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
if (!ptr)
abort();
memcpy(ptr, src, size);
glUnmapBuffer(GL_ARRAY_BUFFER);
#else
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
glBufferData(GL_ARRAY_BUFFER, size, src, GL_DYNAMIC_DRAW);
#endif
}
static void
gl_deferred_init(struct fosphor *self)
{
struct fosphor_gl_state *gl = self->gl;
GLint tex_fmt;
int len;
/* Prevent double init */
if (gl->init_complete)
return;
gl->init_complete = 1;
/* Select texture format */
tex_fmt = gl_check_extension("GL_ARB_texture_rg") ?
GL_R32F :
GL_LUMINANCE32F_ARB;
/* Waterfall texture (FFT_LEN * 1024) */
glGenTextures(1, &gl->tex_waterfall);
glBindTexture(GL_TEXTURE_2D, gl->tex_waterfall);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, tex_fmt, FOSPHOR_FFT_LEN, 1024, 0, GL_RED, GL_FLOAT, NULL);
/* Histogram texture (FFT_LEN * 128) */
glGenTextures(1, &gl->tex_histogram);
glBindTexture(GL_TEXTURE_2D, gl->tex_histogram);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, tex_fmt, FOSPHOR_FFT_LEN, 128, 0, GL_RED, GL_FLOAT, NULL);
/* Spectrum VBO (2 * FFT_LEN, half for live, half for 'hold') */
glGenBuffers(1, &gl->vbo_spectrum);
glBindBuffer(GL_ARRAY_BUFFER, gl->vbo_spectrum);
len = 2 * sizeof(float) * 2 * FOSPHOR_FFT_LEN;
glBufferData(GL_ARRAY_BUFFER, len, NULL, GL_DYNAMIC_DRAW);
}
/* -------------------------------------------------------------------------- */
/* Exposed API */
/* -------------------------------------------------------------------------- */
int
fosphor_gl_init(struct fosphor *self)
{
struct fosphor_gl_state *gl;
const void *font_data;
int len, rv;
/* Allocate structure */
gl = malloc(sizeof(struct fosphor_gl_state));
if (!gl)
return -ENOMEM;
self->gl = gl;
memset(gl, 0, sizeof(struct fosphor_gl_state));
/* Font */
gl->font = glf_alloc(8, GLF_FLG_LCD);
if (!gl->font) {
rv = -ENOMEM;
goto error;
}
font_data = resource_get("DroidSansMonoDotted.ttf", &len);
if (!font_data) {
rv = -ENOENT;
goto error;
}
rv = glf_load_face_mem(gl->font, font_data, len);
if (rv)
goto error;
/* Color mapping */
gl->cmap_ctx = fosphor_gl_cmap_init();
rv = (gl->cmap_ctx == NULL);
rv |= fosphor_gl_cmap_generate(&gl->cmap_waterfall,
fosphor_gl_cmap_waterfall, NULL, 256);
rv |= fosphor_gl_cmap_generate(&gl->cmap_histogram,
fosphor_gl_cmap_histogram, NULL, 256);
if (rv)
goto error;
/* Done */
return 0;
error:
fosphor_gl_release(self);
return rv;
}
void
fosphor_gl_release(struct fosphor *self)
{
struct fosphor_gl_state *gl = self->gl;
/* Safety */
if (!gl)
return;
/* Release all */
glDeleteBuffers(1, &gl->vbo_spectrum);
glDeleteTextures(1, &gl->tex_histogram);
glDeleteTextures(1, &gl->tex_waterfall);
glDeleteTextures(1, &gl->cmap_histogram);
glDeleteTextures(1, &gl->cmap_waterfall);
fosphor_gl_cmap_release(gl->cmap_ctx);
glf_free(gl->font);
/* Release structure */
free(gl);
/* Nothing left */
self->gl = NULL;
}
GLuint
fosphor_gl_get_shared_id(struct fosphor *self,
enum fosphor_gl_id id)
{
struct fosphor_gl_state *gl = self->gl;
/* CL is now sufficiently booted to complete the GL init
* in a CL context */
gl_deferred_init(self);
/* Select ID to return */
switch (id) {
case GL_ID_TEX_WATERFALL:
return gl->tex_waterfall;
case GL_ID_TEX_HISTOGRAM:
return gl->tex_histogram;
case GL_ID_VBO_SPECTRUM:
return gl->vbo_spectrum;
}
return 0;
}
void
fosphor_gl_refresh(struct fosphor *self)
{
struct fosphor_gl_state *gl = self->gl;
if (self->flags & FLG_FOSPHOR_USE_CLGL_SHARING)
return;
gl_deferred_init(self);
gl_tex2d_write(gl->tex_waterfall, self->img_waterfall, FOSPHOR_FFT_LEN, 1024);
gl_tex2d_write(gl->tex_histogram, self->img_histogram, FOSPHOR_FFT_LEN, 128);
gl_vbo_write(gl->vbo_spectrum, self->buf_spectrum, 2 * 2 * sizeof(float) * FOSPHOR_FFT_LEN);
}
void
fosphor_gl_draw(struct fosphor *self, struct fosphor_render *render)
{
struct fosphor_gl_state *gl = self->gl;
struct freq_axis freq_axis;
float x[2], y[2], u[2], v[2];
float tw, bw;
int i;
/* Utils */
tw = 1.0f / (float)FOSPHOR_FFT_LEN; /* Texel width */
bw = 1.0f / (float)(FOSPHOR_FFT_LEN-1); /* Bin width (displayed) */
/* Texture mapping notes:
*
* - The texture have the "DC" bin at texel 0, however we want it to
* be displayed centered and to do so, texture coordinates are used.
* - One of the bin is not displayed (the one at u=0.5f) because
* it is neither positive freq, nor negative ones, but both. To
* compensate for this, the (1.0f - tw) factor is used.
*
* Vertex mapping notes:
*
* - We want the vertex to appear at the center of the displayed bins
* - The vertex 'X' coordinates are filled in by the display kernel as
* ((bin #) ^ (N >> 1)) / (N >> 1) - 1
* - So the DC bin is 0.0f and the undisplayed bin is -1. The others
* are spread between [ -1+2*tw to 1-2*tw ]
* - For display, that range is first remapped to [0 to 1], then to
* [ bw/2 to 1-bw/2 ] (where bw is normalized displayed bin width)
* so that each point maps to the center of the bin on the textures.
* - Finally the zoom is applied and then the transform to map on the
* requested screen area
*/
/* Draw waterfall */
if (render->options & FRO_WATERFALL)
{
x[0] = render->_x[0];
x[1] = render->_x[1];
y[0] = render->_y_wf[0];
y[1] = render->_y_wf[1];
u[0] = 0.5f + tw + ((1.0f - tw) * render->freq_start);
u[1] = 0.5f + tw + ((1.0f - tw) * render->freq_stop);
v[1] = (float)render->_wf_pos / 1024.0f;
v[0] = v[1] - render->wf_span;
fosphor_gl_cmap_enable(gl->cmap_ctx,
gl->tex_waterfall, gl->cmap_waterfall,
self->power.scale, self->power.offset,
GL_CMAP_MODE_BILINEAR);
glBegin( GL_QUADS );
glTexCoord2f(u[0], v[0]); glVertex2f(x[0], y[0]);
glTexCoord2f(u[1], v[0]); glVertex2f(x[1], y[0]);
glTexCoord2f(u[1], v[1]); glVertex2f(x[1], y[1]);
glTexCoord2f(u[0], v[1]); glVertex2f(x[0], y[1]);
glEnd();
fosphor_gl_cmap_disable();
if (render->options & FRO_COLOR_SCALE)
fosphor_gl_cmap_draw_scale(gl->cmap_waterfall,
x[1]+2.0f, x[1]+10.0f, y[0], y[1]);
}
/* Draw histogram */
if (render->options & FRO_HISTO)
{
x[0] = render->_x[0];
x[1] = render->_x[1];
y[0] = render->_y_histo[0];
y[1] = render->_y_histo[1];
u[0] = 0.5f + tw + ((1.0f - tw) * render->freq_start);
u[1] = 0.5f + tw + ((1.0f - tw) * render->freq_stop);
v[0] = 0.0f;
v[1] = 1.0f;
fosphor_gl_cmap_enable(gl->cmap_ctx,
gl->tex_histogram, gl->cmap_histogram,
1.1f, 0.0f, GL_CMAP_MODE_BILINEAR);
glBegin( GL_QUADS );
glTexCoord2f(u[0], v[0]); glVertex2f(x[0], y[0]);
glTexCoord2f(u[1], v[0]); glVertex2f(x[1], y[0]);
glTexCoord2f(u[1], v[1]); glVertex2f(x[1], y[1]);
glTexCoord2f(u[0], v[1]); glVertex2f(x[0], y[1]);
glEnd();
fosphor_gl_cmap_disable();
if (render->options & FRO_COLOR_SCALE)
fosphor_gl_cmap_draw_scale(gl->cmap_histogram,
x[1]+2.0f, x[1]+10.0f, y[0], y[1]);
}
else if (render->options & (FRO_LIVE | FRO_MAX_HOLD))
{
x[0] = render->_x[0];
x[1] = render->_x[1];
y[0] = render->_y_histo[0];
y[1] = render->_y_histo[1];
glColor3f(0.0f, 0.0f, 0.1f);
glBegin( GL_QUADS );
glVertex2f(x[0], y[0]);
glVertex2f(x[1], y[0]);
glVertex2f(x[1], y[1]);
glVertex2f(x[0], y[1]);
glEnd();
}
/* Draw spectrum */
if (render->options & (FRO_LIVE | FRO_MAX_HOLD))
{
int idx[2], len;
/* Select end-points */
idx[0] = 1 + (int)ceilf (render->freq_start * (float)(FOSPHOR_FFT_LEN - 1) - 0.5f);
idx[1] = 1 + (int)floorf(render->freq_stop * (float)(FOSPHOR_FFT_LEN - 1) - 0.5f);
len = idx[1] - idx[0] + 1;
/* Setup */
glPushMatrix();
/* Screen position scaling */
glTranslatef(
render->_x[0],
render->_y_histo[0],
0.0f
);
glScalef(
render->_x[1] - render->_x[0],
render->_y_histo[1] - render->_y_histo[0],
1.0f
);
/* Power offset / scaling */
glScalef(1.0f, self->power.scale, 1.0f);
glTranslatef(0.0f, self->power.offset, 0.0f);
/* Spectrum range selection */
glScalef(1.0f / (render->freq_stop - render->freq_start), 1.0f, 1.0f);
glTranslatef(-render->freq_start, 0.0f, 0.0f);
/* Map the center of each N-1 bins */
glTranslatef(0.5f * bw, 0.0f, 0.0f);
glScalef(1.0f - bw, 1.0f, 1.0f);
/* Spectrum x scaling to [0.0 -> 1.0] range */
glTranslatef(0.5f, 0.0f, 0.0f);
glScalef(0.5f / (1.0f - 2.0f * tw), 1.0f, 1.0f);
/* GL state setup */
glBindBuffer(GL_ARRAY_BUFFER, gl->vbo_spectrum);
glVertexPointer(2, GL_FLOAT, 0, 0);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
/* Live */
if (render->options & FRO_LIVE)
{
glColor4f(1.0f, 1.0f, 1.0f, 0.75f);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_LINE_STRIP, idx[0], len);
glDisableClientState(GL_VERTEX_ARRAY);
}
/* Max hold */
if (render->options & FRO_MAX_HOLD)
{
glColor4f(1.0f, 0.0f, 0.0f, 0.75f);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_LINE_STRIP, idx[0] + FOSPHOR_FFT_LEN, len);
glDisableClientState(GL_VERTEX_ARRAY);
}
/* Cleanup */
glDisable(GL_BLEND);
glPopMatrix();
}
/* Setup frequency axis */
if (render->freq_start != 0.0f || render->freq_stop != 1.0f)
{
/* The freq_{start,stop} have some imprecisions due to the floating
* point nature. To avoid this crapping the display of the axis, we
* try to 'round' them */
double freq_start = round(1e7 * render->freq_start) / 1e7;
double freq_stop = round(1e7 * render->freq_stop) / 1e7;
double rel_center = ((freq_stop + freq_start) / 2.0) - 0.5;
double rel_span = (freq_stop - freq_start);
freq_axis_build(&freq_axis,
self->frequency.center + rel_center * self->frequency.span,
self->frequency.span * rel_span,
render->freq_n_div
);
}
else
{
/* Use the straight number we were provider without math to
* avoid any imprecisions */
freq_axis_build(&freq_axis,
self->frequency.center,
self->frequency.span,
render->freq_n_div
);
}
/* Draw grid */
if (render->options & (FRO_LIVE | FRO_MAX_HOLD | FRO_HISTO))
{
for (i=0; i<11; i++)
{
float fg_color[3] = { 1.00f, 1.00f, 0.33f };
float yv;
yv = render->_y_histo[0] + i * render->_y_histo_div;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
glBegin(GL_LINES);
glVertex2f(render->_x[0] + 0.5f, yv + 0.5f);
glVertex2f(render->_x[1] - 0.5f, yv + 0.5f);
glEnd();
glDisable(GL_BLEND);
if (render->options & FRO_LABEL_PWR)
{
glf_begin(gl->font, fg_color);
glf_printf(gl->font,
render->_x_label, GLF_RIGHT,
yv, GLF_CENTER,
"%d", self->power.db_ref - (10-i) * self->power.db_per_div
);
glf_end();
}
}
for (i=0; i<=render->freq_n_div; i++)
{
float fg_color[3] = { 1.00f, 1.00f, 0.33f };
float xv, xv_ofs, xv_ofs_total;
char buf[32];
xv = render->_x[0] + i * render->_x_div;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
glBegin(GL_LINES);
glVertex2f(xv + 0.5f, render->_y_histo[0]+0.5f);
glVertex2f(xv + 0.5f, render->_y_histo[1]-0.5f);
glEnd();
glDisable(GL_BLEND);
freq_axis_render(&freq_axis, buf, (render->freq_n_div / 2));
xv_ofs_total = glf_width_str(gl->font, buf);
freq_axis_render(&freq_axis, buf, -(render->freq_n_div / 2));
xv_ofs_total += glf_width_str(gl->font, buf);
xv_ofs_total /= 2.0f;
if (render->options & FRO_LABEL_FREQ)
{
int ib = i - (render->freq_n_div / 2);
glf_begin(gl->font, fg_color);
freq_axis_render(&freq_axis, buf, ib);
xv_ofs = floor((- xv_ofs_total * ib) / render->freq_n_div);
glf_printf(gl->font,
xv + xv_ofs, GLF_CENTER,
render->_y_label, GLF_CENTER,
"%s", buf
);
glf_end();
}
}
}
/* Draw channels */
if (render->options & FRO_CHANNELS)
{
struct {
int dir;
float pos;
} pt[2*FOSPHOR_MAX_CHANNELS+2], tpt;
int i, j, n;
/* Generate the points from the channels */
n = 2;
pt[0].dir = -1; pt[0].pos = 0.0f;
pt[1].dir = 1; pt[1].pos = 1.0f;
for (i=0; i<FOSPHOR_MAX_CHANNELS; i++)
{
float f;
if (!render->channels[i].enabled)
continue;
f = render->channels[i].center
- render->channels[i].width / 2.0f;
pt[n].dir = 1;
pt[n].pos = (f > 0.0f) ? (f < 1.0f ? f : 1.0f) : 0.0f;
n++;
f = render->channels[i].center
+ render->channels[i].width / 2.0f;
pt[n].dir = -1;
pt[n].pos = (f > 0.0f) ? (f < 1.0f ? f : 1.0f) : 0.0f;
n++;
}
/* Only if there is something to do ... */
if (n > 2)
{
int l = pt[0].dir;
/* GL setup */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPushMatrix();
glTranslatef(render->_x[0], 0.0f, 0.0f);
glScalef(render->_x[1] - render->_x[0], 1.0f, 1.0f);
/* Sort and draw at the same time */
for (i=1; i<n; i++)
{
int mi = i;
/* Find min index */
for (j=i+1; j<n; j++) {
if (pt[j].pos < pt[mi].pos)
mi = j;
}
/* Swap */
tpt = pt[i];
pt[i] = pt[mi];
pt[mi] = tpt;
/* Draw */
if ((pt[i-1].pos != pt[i].pos) && (l != 0))
{
if (l < 0)
glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
else
glColor4f(1.0f, 1.0f, 1.0f, 0.2f - 0.2f / (1 + l));
if (render->options & FRO_WATERFALL) {
glBegin( GL_QUADS );
glVertex2f(pt[i ].pos, render->_y_histo[0]);
glVertex2f(pt[i-1].pos, render->_y_histo[0]);
glVertex2f(pt[i-1].pos, render->_y_histo[1]);
glVertex2f(pt[i ].pos, render->_y_histo[1]);
glEnd();
}
if (render->options & (FRO_LIVE | FRO_MAX_HOLD | FRO_HISTO)) {
glBegin( GL_QUADS );
glVertex2f(pt[i ].pos, render->_y_wf[0]);
glVertex2f(pt[i-1].pos, render->_y_wf[0]);
glVertex2f(pt[i-1].pos, render->_y_wf[1]);
glVertex2f(pt[i ].pos, render->_y_wf[1]);
glEnd();
}
}
l += pt[i].dir;
}
/* GL cleanup */
glPopMatrix();
glDisable(GL_BLEND);
}
}
/* Ensure GL is done */
/* Make this optional. If after the draw we do a swap buffer, we _know_
that GL will be done after it
Also, if we do multiple draw, then this is completely useless
*/
/* glFinish(); */
}
/*! @} */