gr-fosphor/lib/base_sink_c_impl.cc

486 lines
10 KiB
C++

/* -*- c++ -*- */
/*
* Copyright 2013-2021 Sylvain Munaut <tnt@246tNt.com>
*
* This file is part of gr-fosphor
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdio.h>
#include <gnuradio/io_signature.h>
#include <gnuradio/thread/thread.h>
#include "fifo.h"
#include "base_sink_c_impl.h"
#ifdef ENABLE_GLEW
# include <GL/glew.h>
#endif
extern "C" {
#include "fosphor/fosphor.h"
#include "fosphor/gl_platform.h"
}
namespace gr {
namespace fosphor {
base_sink_c::base_sink_c(const char *name)
: gr::sync_block(name,
gr::io_signature::make(1, 1, sizeof(gr_complex)),
gr::io_signature::make(0, 0, 0))
{
/* Register message ports */
message_port_register_out(pmt::mp("freq"));
}
gr::thread::mutex base_sink_c_impl::s_boot_mutex;
const int base_sink_c_impl::k_db_per_div[] = {1, 2, 5, 10, 20};
base_sink_c_impl::base_sink_c_impl()
: d_db_ref(0), d_db_per_div_idx(3),
d_zoom_enabled(false), d_zoom_center(0.5), d_zoom_width(0.2),
d_ratio(0.35f), d_frozen(false), d_active(false), d_visible(false),
d_frequency(), d_fft_window(gr::fft::window::WIN_BLACKMAN_hARRIS)
{
/* Init FIFO */
this->d_fifo = new fifo(2 * 1024 * 1024);
/* Init render options */
this->d_render_main = new fosphor_render();
fosphor_render_defaults(this->d_render_main);
this->d_render_zoom = new fosphor_render();
fosphor_render_defaults(this->d_render_zoom);
this->d_render_zoom->options &= ~(FRO_LABEL_PWR | FRO_LABEL_TIME);
}
base_sink_c_impl::~base_sink_c_impl()
{
delete this->d_render_zoom;
delete this->d_render_main;
delete this->d_fifo;
}
void base_sink_c_impl::worker()
{
/* Zero-init */
this->d_fosphor = NULL;
/* Init GL context */
this->glctx_init();
#ifdef ENABLE_GLEW
GLenum glew_err = glewInit();
if (glew_err != GLEW_OK) {
GR_LOG_ERROR(d_logger, boost::format("GLEW initialization error : %s") % glewGetErrorString(glew_err));
goto error;
}
#endif
/* Init fosphor */
{
/* (prevent // init of multiple instance to be gentle on the OpenCL
* implementations that don't like this) */
gr::thread::scoped_lock guard(s_boot_mutex);
this->d_fosphor = fosphor_init();
if (!this->d_fosphor) {
GR_LOG_ERROR(d_logger, "Failed to initialize fosphor");
goto error;
}
}
this->settings_apply(~SETTING_DIMENSIONS);
/* Main loop */
while (this->d_active)
{
this->render();
this->glctx_poll();
}
error:
/* Cleanup fosphor */
if (this->d_fosphor)
fosphor_release(this->d_fosphor);
/* And GL context */
this->glctx_fini();
}
void base_sink_c_impl::_worker(base_sink_c_impl *obj)
{
obj->worker();
}
void
base_sink_c_impl::render(void)
{
const int fft_len = 1024;
const int batch_mult = 16;
const int batch_max = 1024;
const int max_iter = 8;
int i, tot_len;
/* Handle pending settings */
this->settings_apply(this->settings_get_and_reset_changed());
/* Process as much we can */
tot_len = this->d_fifo->used();
for (i=0; i<max_iter && tot_len; i++)
{
gr_complex *data;
int len;
/* How much can we get from FIFO in one block */
len = tot_len;
if (len > this->d_fifo->read_max_size())
len = this->d_fifo->read_max_size();
/* Adapt to valid size for fosphor */
len &= ~((batch_mult * fft_len) - 1);
if (len > (batch_max * fft_len))
len = batch_max * fft_len;
/* Is it worth it ? */
tot_len -= len;
if (!len)
break;
/* Send to process (if not frozen) */
if (!this->d_frozen) {
data = this->d_fifo->read_peek(len, false);
fosphor_process(this->d_fosphor, data, len);
}
/* Discard */
this->d_fifo->read_discard(len);
}
/* Are we visible ? */
{
gr::thread::scoped_lock guard(this->d_render_mutex);
if (this->d_visible) {
/* Clear everything */
glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
glClear(GL_COLOR_BUFFER_BIT);
/* Draw */
fosphor_draw(this->d_fosphor, this->d_render_main);
if (this->d_zoom_enabled)
fosphor_draw(this->d_fosphor, this->d_render_zoom);
/* Done, swap buffer */
this->glctx_swap();
}
}
if (!this->d_visible) {
/* If hidden, we can't draw or swap buffer, so just wait a bit */
boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
}
}
void
base_sink_c_impl::settings_mark_changed(uint32_t setting)
{
gr::thread::scoped_lock lock(this->d_settings_mutex);
this->d_settings_changed |= setting;
}
uint32_t
base_sink_c_impl::settings_get_and_reset_changed(void)
{
gr::thread::scoped_lock lock(this->d_settings_mutex);
uint32_t v = this->d_settings_changed;
this->d_settings_changed = 0;
return v;
}
void
base_sink_c_impl::settings_apply(uint32_t settings)
{
if (settings & SETTING_DIMENSIONS)
{
this->glctx_update();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (double)this->d_width, 0.0, (double)this->d_height, -1.0, 1.0);
glViewport(0, 0, this->d_width, this->d_height);
}
if (settings & SETTING_POWER_RANGE) {
fosphor_set_power_range(this->d_fosphor,
this->d_db_ref,
this->k_db_per_div[this->d_db_per_div_idx]
);
}
if (settings & SETTING_FREQUENCY_RANGE) {
fosphor_set_frequency_range(this->d_fosphor,
this->d_frequency.center,
this->d_frequency.span
);
}
if (settings & SETTING_FFT_WINDOW) {
std::vector<float> window =
gr::fft::window::build(this->d_fft_window, 1024, 6.76);
fosphor_set_fft_window(this->d_fosphor, window.data());
}
if (settings & (SETTING_DIMENSIONS | SETTING_RENDER_OPTIONS))
{
float f;
if (this->d_zoom_enabled) {
int a = (int)(this->d_width * 0.65f);
this->d_render_main->width = a;
this->d_render_main->options |= FRO_CHANNELS;
this->d_render_main->options &= ~FRO_COLOR_SCALE;
this->d_render_zoom->pos_x = a - 10;
this->d_render_zoom->width = this->d_width - a + 10;
} else {
this->d_render_main->width = this->d_width;
this->d_render_main->options &= ~FRO_CHANNELS;
this->d_render_main->options |= FRO_COLOR_SCALE;
}
this->d_render_main->height = this->d_height;
this->d_render_zoom->height = this->d_height;
this->d_render_main->histo_wf_ratio = this->d_ratio;
this->d_render_zoom->histo_wf_ratio = this->d_ratio;
this->d_render_main->channels[0].enabled = this->d_zoom_enabled;
this->d_render_main->channels[0].center = (float)this->d_zoom_center;
this->d_render_main->channels[0].width = (float)this->d_zoom_width;
this->d_render_zoom->freq_center = (float)this->d_zoom_center;
this->d_render_zoom->freq_span = (float)this->d_zoom_width;
fosphor_render_refresh(this->d_render_main);
fosphor_render_refresh(this->d_render_zoom);
}
}
void
base_sink_c_impl::cb_reshape(int width, int height)
{
this->d_width = width;
this->d_height = height;
this->settings_mark_changed(SETTING_DIMENSIONS);
}
void
base_sink_c_impl::cb_visibility(bool visible)
{
gr::thread::scoped_lock guard(this->d_render_mutex);
this->d_visible = visible;
}
void
base_sink_c_impl::execute_ui_action(enum ui_action_t action)
{
switch (action) {
case DB_PER_DIV_UP:
if (this->d_db_per_div_idx < 4)
this->d_db_per_div_idx++;
break;
case DB_PER_DIV_DOWN:
if (this->d_db_per_div_idx > 0)
this->d_db_per_div_idx--;
break;
case REF_UP:
this->d_db_ref += k_db_per_div[this->d_db_per_div_idx];
break;
case REF_DOWN:
this->d_db_ref -= k_db_per_div[this->d_db_per_div_idx];
break;
case ZOOM_TOGGLE:
this->d_zoom_enabled ^= 1;
break;
case ZOOM_WIDTH_UP:
if (this->d_zoom_enabled)
this->d_zoom_width *= 2.0;
break;
case ZOOM_WIDTH_DOWN:
if (this->d_zoom_enabled)
this->d_zoom_width /= 2.0;
break;
case ZOOM_CENTER_UP:
if (this->d_zoom_enabled)
this->d_zoom_center += this->d_zoom_width / 8.0;
break;
case ZOOM_CENTER_DOWN:
if (this->d_zoom_enabled)
this->d_zoom_center -= this->d_zoom_width / 8.0;
break;
case RATIO_UP:
if (this->d_ratio < 0.8f)
this->d_ratio += 0.05f;
break;
case RATIO_DOWN:
if (this->d_ratio > 0.2f)
this->d_ratio -= 0.05f;
break;
case FREEZE_TOGGLE:
this->d_frozen ^= 1;
break;
}
this->settings_mark_changed(
SETTING_POWER_RANGE |
SETTING_RENDER_OPTIONS
);
}
void
base_sink_c_impl::execute_mouse_action(enum mouse_action_t action, int x, int y)
{
if (action != CLICK)
return;
/* Identify position */
int in_main = fosphor_render_pos_inside(this->d_render_main, x, y);
int in_zoom = this->d_zoom_enabled ? fosphor_render_pos_inside(this->d_render_zoom, x, y) : 0;
/* Send frequency */
if (in_main & 1)
{
double freq = fosphor_pos2freq(this->d_fosphor, this->d_render_main, x);
message_port_pub(pmt::mp("freq"), pmt::cons(pmt::mp("freq"), pmt::from_double(freq)));
}
else if (in_zoom & 1)
{
double freq = fosphor_pos2freq(this->d_fosphor, this->d_render_zoom, x);
message_port_pub(pmt::mp("freq"), pmt::cons(pmt::mp("freq"), pmt::from_double(freq)));
}
}
void
base_sink_c_impl::set_frequency_range(const double center, const double span)
{
this->d_frequency.center = center;
this->d_frequency.span = span;
this->settings_mark_changed(SETTING_FREQUENCY_RANGE);
}
void
base_sink_c_impl::set_frequency_center(const double center)
{
this->d_frequency.center = center;
this->settings_mark_changed(SETTING_FREQUENCY_RANGE);
}
void
base_sink_c_impl::set_frequency_span(const double span)
{
this->d_frequency.span = span;
this->settings_mark_changed(SETTING_FREQUENCY_RANGE);
}
void
base_sink_c_impl::set_fft_window(const gr::fft::window::win_type win)
{
if (win == this->d_fft_window) /* Reloading FFT window takes time */
return; /* avoid doing it if possible */
this->d_fft_window = win;
this->settings_mark_changed(SETTING_FFT_WINDOW);
}
int
base_sink_c_impl::work(
int noutput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
const gr_complex *in = (const gr_complex *) input_items[0];
gr_complex *dst;
int l, mw;
/* How much can we hope to write */
l = noutput_items;
mw = this->d_fifo->write_max_size();
if (l > mw)
l = mw;
if (!l)
return 0;
/* Get a pointer */
dst = this->d_fifo->write_prepare(l, true);
if (!dst)
return 0;
/* Do the copy */
memcpy(dst, in, sizeof(gr_complex) * l);
this->d_fifo->write_commit(l);
/* Report what we took */
return l;
}
bool base_sink_c_impl::start()
{
bool rv = base_sink_c::start();
if (!this->d_active) {
this->d_active = true;
this->d_worker = gr::thread::thread(_worker, this);
}
return rv;
}
bool base_sink_c_impl::stop()
{
bool rv = base_sink_c::stop();
if (this->d_active) {
this->d_active = false;
this->d_worker.join();
}
return rv;
}
} /* namespace fosphor */
} /* namespace gr */