1 // Copyright (c) Charles J. Cliffe
2 // SPDX-License-Identifier: GPL-2.0+
3 
4 #include "WaterfallPanel.h"
5 
WaterfallPanel()6 WaterfallPanel::WaterfallPanel() : GLPanel(), fft_size(0), waterfall_lines(0), waterfall_slice(nullptr), activeTheme(nullptr) {
7 	setFillColor(RGBA4f(0,0,0));
8     for (unsigned int & i : waterfall) {
9         i = 0;
10     }
11 }
12 
setup(unsigned int fft_size_in,int num_waterfall_lines_in)13 void WaterfallPanel::setup(unsigned int fft_size_in, int num_waterfall_lines_in) {
14     waterfall_lines = num_waterfall_lines_in;
15     fft_size = fft_size_in;
16     lines_buffered.store(0);
17 
18     if (points.size() != fft_size) {
19         points.resize(fft_size);
20     }
21 
22     texInitialized.store(false);
23     bufferInitialized.store(false);
24 }
25 
refreshTheme()26 void WaterfallPanel::refreshTheme() {
27     glEnable (GL_TEXTURE_2D);
28 
29     for (unsigned int i : waterfall) {
30         glBindTexture(GL_TEXTURE_2D, i);
31 
32         glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
33         glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 256, &(ThemeMgr::mgr.currentTheme->waterfallGradient.getRed())[0]);
34         glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 256, &(ThemeMgr::mgr.currentTheme->waterfallGradient.getGreen())[0]);
35         glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 256, &(ThemeMgr::mgr.currentTheme->waterfallGradient.getBlue())[0]);
36     }
37 }
38 
setPoints(std::vector<float> & points_in)39 void WaterfallPanel::setPoints(std::vector<float> &points_in) {
40     size_t halfPts = points_in.size() / 2;
41     if (halfPts == fft_size) {
42 
43         for (unsigned int i = 0; i < fft_size; i++) {
44             points[i] = points_in[i * 2 + 1];
45         }
46     } else {
47         points.assign(points_in.begin(), points_in.end());
48     }
49 }
50 
step()51 void WaterfallPanel::step() {
52     unsigned int half_fft_size = fft_size / 2;
53 
54     if (!bufferInitialized.load()) {
55         delete waterfall_slice;
56         waterfall_slice = new unsigned char[half_fft_size];
57         bufferInitialized.store(true);
58     }
59 
60     if (!texInitialized.load()) {
61         return;
62     }
63 
64     if (!points.empty() && points.size() == fft_size) {
65         for (int j = 0; j < 2; j++) {
66             for (unsigned int i = 0, iMax = half_fft_size; i < iMax; i++) {
67                 float v = points[j * half_fft_size + i];
68 
69                 float wv = v < 0 ? 0 : (v > 0.99 ? 0.99 : v);
70 
71                 waterfall_slice[i] = (unsigned char) floor(wv * 255.0);
72             }
73 
74             unsigned int newBufSize = (half_fft_size*lines_buffered.load()+half_fft_size);
75             if (lineBuffer[j].size() < newBufSize) {
76                 lineBuffer[j].resize(newBufSize);
77                 rLineBuffer[j].resize(newBufSize);
78             }
79             memcpy(&(lineBuffer[j][half_fft_size*lines_buffered.load()]), waterfall_slice, sizeof(unsigned char) * half_fft_size);
80         }
81         lines_buffered++;
82     }
83 }
84 
update()85 void WaterfallPanel::update() {
86     unsigned int half_fft_size = fft_size / 2;
87 
88     if (!bufferInitialized.load()) {
89         return;
90     }
91 
92     if (!texInitialized.load()) {
93         for (int i = 0; i < 2; i++) {
94             if (waterfall[i]) {
95                 glDeleteTextures(1, &waterfall[i]);
96                 waterfall[i] = 0;
97             }
98 
99             waterfall_ofs[i] = waterfall_lines - 1;
100         }
101 
102         glGenTextures(2, waterfall);
103 
104         unsigned char *waterfall_tex;
105 
106         //Creates 2x 2D textures into card memory.
107         //of size half_fft_size * waterfall_lines, which can be BIG.
108         //The limit of the size of Waterfall is the size of the maximum supported 2D texture
109         //by the graphic card. (half_fft_size * waterfall_lines, i.e DEFAULT_DEMOD_WATERFALL_LINES_NB * DEFAULT_FFT_SIZE/2)
110         waterfall_tex = new unsigned char[half_fft_size * waterfall_lines];
111         memset(waterfall_tex, 0, half_fft_size * waterfall_lines);
112 
113         for (unsigned int i : waterfall) {
114             glBindTexture(GL_TEXTURE_2D, i);
115             glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
116 
117             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
118             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
119             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
120             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
121 
122             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, half_fft_size, waterfall_lines, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) waterfall_tex);
123         }
124 
125         delete[] waterfall_tex;
126 
127         refreshTheme();
128 
129         texInitialized.store(true);
130     }
131 
132     for (int i = 0, iMax = lines_buffered.load(); i < iMax; i++) {
133         for (int j = 0; j < 2; j++) {
134             memcpy(&(rLineBuffer[j][i*half_fft_size]),
135                    &(lineBuffer[j][((iMax-1)*half_fft_size)-(i*half_fft_size)]), sizeof(unsigned char) * half_fft_size);
136         }
137     }
138 
139     unsigned int run_ofs = 0;
140     while (lines_buffered.load()) {
141         int run_lines = lines_buffered.load();
142         if (run_lines > waterfall_ofs[0]) {
143             run_lines = waterfall_ofs[0];
144         }
145         for (int j = 0; j < 2; j++) {
146             glBindTexture(GL_TEXTURE_2D, waterfall[j]);
147             glTexSubImage2D(GL_TEXTURE_2D, 0, 0, waterfall_ofs[j]-run_lines, half_fft_size, run_lines,
148                             GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) &(rLineBuffer[j][run_ofs]));
149 
150             waterfall_ofs[j]-=run_lines;
151 
152             if (waterfall_ofs[j] == 0) {
153                 waterfall_ofs[j] = waterfall_lines;
154             }
155         }
156         run_ofs += run_lines*half_fft_size;
157         lines_buffered.store(lines_buffered.load()-run_lines);
158     }
159 }
160 
drawPanelContents()161 void WaterfallPanel::drawPanelContents() {
162     if (!texInitialized.load()) {
163         return;
164     }
165 
166     unsigned int half_fft_size = fft_size / 2;
167 
168     glLoadMatrixf(transform.to_ptr());
169 
170     glEnable (GL_TEXTURE_2D);
171     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
172 
173     if (activeTheme != ThemeMgr::mgr.currentTheme) {
174         refreshTheme();
175         activeTheme = ThemeMgr::mgr.currentTheme;
176     }
177     glColor3f(1.0, 1.0, 1.0);
178 
179     GLint vp[4];
180     glGetIntegerv(GL_VIEWPORT, vp);
181 
182     float viewWidth = (float) vp[2];
183 
184     // some bias to prevent seams at odd scales
185     float half_pixel = 1.0 / viewWidth;
186     float half_texel = 1.0 / (float) half_fft_size;
187     float vtexel = 1.0 / (float) waterfall_lines;
188     float vofs = (float) (waterfall_ofs[0]) * vtexel;
189 
190     glBindTexture(GL_TEXTURE_2D, waterfall[0]);
191     glBegin (GL_QUADS);
192     glTexCoord2f(0.0 + half_texel, 1.0 + vofs);
193     glVertex3f(-1.0, -1.0, 0.0);
194     glTexCoord2f(1.0 - half_texel, 1.0 + vofs);
195     glVertex3f(0.0 + half_pixel, -1.0, 0.0);
196     glTexCoord2f(1.0 - half_texel, 0.0 + vofs);
197     glVertex3f(0.0 + half_pixel, 1.0, 0.0);
198     glTexCoord2f(0.0 + half_texel, 0.0 + vofs);
199     glVertex3f(-1.0, 1.0, 0.0);
200     glEnd();
201 
202     vofs = (float) (waterfall_ofs[1]) * vtexel;
203     glBindTexture(GL_TEXTURE_2D, waterfall[1]);
204     glBegin(GL_QUADS);
205     glTexCoord2f(0.0 + half_texel, 1.0 + vofs);
206     glVertex3f(0.0 - half_pixel, -1.0, 0.0);
207     glTexCoord2f(1.0 - half_texel, 1.0 + vofs);
208     glVertex3f(1.0, -1.0, 0.0);
209     glTexCoord2f(1.0 - half_texel, 0.0 + vofs);
210     glVertex3f(1.0, 1.0, 0.0);
211     glTexCoord2f(0.0 + half_texel, 0.0 + vofs);
212     glVertex3f(0.0 - half_pixel, 1.0, 0.0);
213     glEnd();
214 
215     glBindTexture(GL_TEXTURE_2D, 0);
216 
217     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
218     glDisable(GL_TEXTURE_2D);
219 }
220