1 /*
2  *  Copyright (C) 2005-2020 Team Kodi (https://kodi.tv)
3  *
4  *  SPDX-License-Identifier: GPL-2.0-or-later
5  *  See LICENSE.md for more information.
6  */
7 
8 #include "main.h"
9 #include "lodepng.h"
10 
11 #define _USE_MATH_DEFINES
12 #include <algorithm>
13 #include <chrono>
14 #include <math.h>
15 
16 #define SMOOTHING_TIME_CONSTANT (0.8)
17 #define MIN_DECIBELS (-100.0)
18 #define MAX_DECIBELS (-30.0)
19 
20 #define AUDIO_BUFFER (1024)
21 #define NUM_BANDS (AUDIO_BUFFER / 2)
22 
23 // Override GL_RED if not present with GL_LUMINANCE, e.g. on Android GLES
24 #ifndef GL_RED
25 #define GL_RED GL_LUMINANCE
26 #endif
27 
28 struct Preset
29 {
30   std::string name;
31   std::string file;
32   int channel[4];
33 };
34 
35 // NOTE: With "#if defined(HAS_GL)" the use of some shaders is avoided
36 //       as they can cause problems on weaker systems.
37 const std::vector<Preset> g_presets =
38 {
39    {"2D LED Spectrum by un1versal",             "2Dspectrum.frag.glsl",             99, -1, -1, -1},
40    {"Audio Eclipse by airtight",                "audioeclipse.frag.glsl",           99, -1, -1, -1},
41    {"Audio Reaktive by choard1895",             "audioreaktive.frag.glsl",          99, -1, -1, -1},
42    {"AudioVisual by Passion",                   "audiovisual.frag.glsl",            99, -1, -1, -1},
43    {"Beating Circles by Phoenix72",             "beatingcircles.frag.glsl",         99, -1, -1, -1},
44    {"BPM by iq",                                "bpm.frag.glsl",                    99, -1, -1, -1},
45    {"Circle Wave by TekF",                      "circlewave.frag.glsl",             99, -1, -1, -1},
46 #if defined(HAS_GL)
47    {"Circuits by Kali",                         "circuits.frag.glsl",               99,  7, -1, -1},
48    {"Colored Bars by novalis",                  "coloredbars.frag.glsl",            99, -1, -1, -1},
49    {"Cubescape by iq",                          "cubescape.frag.glsl",              99,  5, -1, -1},
50 #endif
51    {"Dancing Metalights by Danguafare",         "dancingmetalights.frag.glsl",      99, -1, -1, -1},
52    {"The Disco Tunnel by poljere",              "discotunnel.frag.glsl",             2, 13, 99, -1},
53    {"Electric pulse by un1versal",              "electricpulse.frag.glsl",          99, -1, -1, -1},
54 #if defined(HAS_GL)
55    {"Fractal Land by Kali",                     "fractalland.frag.glsl",             2, 13, 99, -1},
56 #endif
57    {"Gameboy by iq",                            "gameboy.frag.glsl",                99, -1, -1, -1},
58    {"Input Sound by iq",                        "input.frag.glsl",                  99, -1, -1, -1},
59 #if defined(HAS_GL)
60    {"I/O by movAX13h",                          "io.frag.glsl",                     99, -1, -1, -1},
61 #endif
62    {"Kaleidoscope Visualizer by Qqwy",          "kaleidoscopevisualizer.frag.glsl", 99, 15, -1, -1},
63    {"LED spectrum by simesgreen",               "ledspectrum.frag.glsl",            99, -1, -1, -1},
64    {"Polar Beats by sauj123",                   "polarbeats.frag.glsl",             99, -1, -1, -1},
65    {"Simplicity Galaxy by CBS",                 "simplicitygalaxy.frag.glsl",       99, -1, -1, -1},
66    {"Sound Flower by iq",                       "soundflower.frag.glsl",            99, -1, -1, -1},
67    {"Sound sinus wave by Eitraz",               "soundsinuswave.frag.glsl",         99, -1, -1, -1},
68    {"Spectrometer by jaba",                     "spectrometer.frag.glsl",           99, -1, -1, -1},
69    {"symmetrical sound visualiser by thelinked","symmetricalsound.frag.glsl",       99, -1, -1, -1},
70    {"Twisted Rings by poljere",                 "twistedrings.frag.glsl",           99, -1, -1, -1},
71    {"Undulant Spectre by mafik",                "undulantspectre.frag.glsl",        99, -1, -1, -1},
72 #if defined(HAS_GL)
73    {"Demo - Volumetric Lines by iq",            "volumetriclines.frag.glsl",        99, -1, -1, -1},
74 #endif
75    {"Waves Remix by ADOB",                      "wavesremix.frag.glsl",             99, -1, -1, -1}
76 };
77 
78 const std::vector<std::string> g_fileTextures =
79 {
80   "tex00.png",
81   "tex01.png",
82   "tex02.png",
83   "tex03.png",
84   "tex04.png",
85   "tex05.png",
86   "tex06.png",
87   "tex07.png",
88   "tex08.png",
89   "tex09.png",
90   "tex10.png",
91   "tex11.png",
92   "tex12.png",
93   "tex15.png",
94   "tex16.png",
95   "tex14.png",
96 };
97 
98 #if defined(HAS_GL)
99 
100 std::string fsHeader =
101 R"shader(#version 150
102 
103 #extension GL_OES_standard_derivatives : enable
104 
105 uniform vec3 iResolution;
106 uniform float iGlobalTime;
107 uniform float iChannelTime[4];
108 uniform vec4 iMouse;
109 uniform vec4 iDate;
110 uniform float iSampleRate;
111 uniform vec3 iChannelResolution[4];
112 uniform sampler2D iChannel0;
113 uniform sampler2D iChannel1;
114 uniform sampler2D iChannel2;
115 uniform sampler2D iChannel3;
116 
117 out vec4 FragColor;
118 
119 #define iTime iGlobalTime
120 
121 #ifndef texture2D
122 #define texture2D texture
123 #endif
124 )shader";
125 
126 std::string fsFooter =
127 R"shader(
128 void main(void)
129 {
130   vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
131   mainImage(color, gl_FragCoord.xy);
132   color.w = 1.0;
133   FragColor = color;
134 }
135 )shader";
136 
137 #else
138 
139 std::string fsHeader =
140 R"shader(#version 100
141 
142 #extension GL_OES_standard_derivatives : enable
143 
144 precision mediump float;
145 precision mediump int;
146 
147 uniform vec3 iResolution;
148 uniform float iGlobalTime;
149 uniform float iChannelTime[4];
150 uniform vec4 iMouse;
151 uniform vec4 iDate;
152 uniform float iSampleRate;
153 uniform vec3 iChannelResolution[4];
154 uniform sampler2D iChannel0;
155 uniform sampler2D iChannel1;
156 uniform sampler2D iChannel2;
157 uniform sampler2D iChannel3;
158 
159 #define iTime iGlobalTime
160 #ifndef texture
161 #define texture texture2D
162 #endif
163 
164 #ifndef textureLod
165 vec4 textureLod(sampler2D sampler, vec2 uv, float lod)
166 {
167   return texture2D(sampler, uv, lod);
168 }
169 #endif
170 )shader";
171 
172 std::string fsFooter =
173 R"shader(
174 void main(void)
175 {
176   vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
177   mainImage(color, gl_FragCoord.xy);
178   color.w = 1.0;
179   gl_FragColor = color;
180 }
181 )shader";
182 
183 #endif
184 
CVisualizationShadertoy()185 CVisualizationShadertoy::CVisualizationShadertoy()
186   : m_kissCfg(kiss_fft_alloc(AUDIO_BUFFER, 0, nullptr, nullptr)),
187     m_audioData(new GLubyte[AUDIO_BUFFER]()),
188     m_magnitudeBuffer(new float[NUM_BANDS]()),
189     m_pcm(new float[AUDIO_BUFFER]())
190 {
191   m_settingsUseOwnshader = kodi::GetSettingBoolean("ownshader");
192   if (m_settingsUseOwnshader)
193     m_currentPreset = -1;
194   else
195     m_currentPreset = kodi::GetSettingInt("lastpresetidx");
196 }
197 
~CVisualizationShadertoy()198 CVisualizationShadertoy::~CVisualizationShadertoy()
199 {
200   delete [] m_audioData;
201   delete [] m_magnitudeBuffer;
202   delete [] m_pcm;
203   free(m_kissCfg);
204 }
205 
206 //-- Render -------------------------------------------------------------------
207 // Called once per frame. Do all rendering here.
208 //-----------------------------------------------------------------------------
Render()209 void CVisualizationShadertoy::Render()
210 {
211   if (m_initialized)
212   {
213     if (m_state.fbwidth && m_state.fbheight)
214     {
215       RenderTo(m_shadertoyShader.ProgramHandle(), m_state.effect_fb);
216       RenderTo(m_displayShader.ProgramHandle(), 0);
217     }
218     else
219     {
220       RenderTo(m_shadertoyShader.ProgramHandle(), 0);
221     }
222   }
223 }
224 
Start(int iChannels,int iSamplesPerSec,int iBitsPerSample,std::string szSongName)225 bool CVisualizationShadertoy::Start(int iChannels, int iSamplesPerSec, int iBitsPerSample, std::string szSongName)
226 {
227 #ifdef DEBUG_PRINT
228   printf("Start %i %i %i %s\n", iChannels, iSamplesPerSec, iBitsPerSample, szSongName.c_str());
229 #endif
230 
231   static const GLfloat vertex_data[] =
232   {
233     -1.0, 1.0, 1.0, 1.0,
234      1.0, 1.0, 1.0, 1.0,
235      1.0,-1.0, 1.0, 1.0,
236     -1.0,-1.0, 1.0, 1.0,
237   };
238 
239   // Upload vertex data to a buffer
240   glGenBuffers(1, &m_state.vertex_buffer);
241   glBindBuffer(GL_ARRAY_BUFFER, m_state.vertex_buffer);
242   glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
243 
244   m_samplesPerSec = iSamplesPerSec;
245   Launch(m_currentPreset);
246   m_initialized = true;
247 
248   return true;
249 }
250 
Stop()251 void CVisualizationShadertoy::Stop()
252 {
253   m_initialized = false;
254 #ifdef DEBUG_PRINT
255   printf("Stop\n");
256 #endif
257 
258   UnloadPreset();
259   UnloadTextures();
260 
261   glDeleteBuffers(1, &m_state.vertex_buffer);
262 }
263 
264 
AudioData(const float * pAudioData,int iAudioDataLength,float * pFreqData,int iFreqDataLength)265 void CVisualizationShadertoy::AudioData(const float* pAudioData, int iAudioDataLength, float* pFreqData, int iFreqDataLength)
266 {
267   WriteToBuffer(pAudioData, iAudioDataLength, 2);
268 
269   kiss_fft_cpx in[AUDIO_BUFFER], out[AUDIO_BUFFER];
270   for (unsigned int i = 0; i < AUDIO_BUFFER; i++)
271   {
272     in[i].r = BlackmanWindow(m_pcm[i], i, AUDIO_BUFFER);
273     in[i].i = 0;
274   }
275 
276   kiss_fft(m_kissCfg, in, out);
277 
278   out[0].i = 0;
279 
280   SmoothingOverTime(m_magnitudeBuffer, m_magnitudeBuffer, out, NUM_BANDS, SMOOTHING_TIME_CONSTANT, AUDIO_BUFFER);
281 
282   const double rangeScaleFactor = MAX_DECIBELS == MIN_DECIBELS ? 1 : (1.0 / (MAX_DECIBELS - MIN_DECIBELS));
283   for (unsigned int i = 0; i < NUM_BANDS; i++)
284   {
285     float linearValue = m_magnitudeBuffer[i];
286     double dbMag = !linearValue ? MIN_DECIBELS : LinearToDecibels(linearValue);
287     double scaledValue = UCHAR_MAX * (dbMag - MIN_DECIBELS) * rangeScaleFactor;
288 
289     m_audioData[i] = std::max(std::min((int)scaledValue, UCHAR_MAX), 0);
290   }
291 
292   for (unsigned int i = 0; i < NUM_BANDS; i++)
293   {
294     float v = (m_pcm[i] + 1.0f) * 128.0f;
295     m_audioData[i + NUM_BANDS] = std::max(std::min((int)v, UCHAR_MAX), 0);
296   }
297 
298   m_needsUpload = true;
299 }
300 
301 //-- OnAction -----------------------------------------------------------------
302 // Handle Kodi actions such as next preset, lock preset, album art changed etc
303 //-----------------------------------------------------------------------------
NextPreset()304 bool CVisualizationShadertoy::NextPreset()
305 {
306   if (!m_settingsUseOwnshader)
307   {
308     m_currentPreset = (m_currentPreset + 1) % g_presets.size();
309     Launch(m_currentPreset);
310     kodi::SetSettingInt("lastpresetidx", m_currentPreset);
311   }
312   return true;
313 }
314 
PrevPreset()315 bool CVisualizationShadertoy::PrevPreset()
316 {
317   if (!m_settingsUseOwnshader)
318   {
319     m_currentPreset = (m_currentPreset - 1) % g_presets.size();
320     Launch(m_currentPreset);
321     kodi::SetSettingInt("lastpresetidx", m_currentPreset);
322   }
323   return true;
324 }
325 
LoadPreset(int select)326 bool CVisualizationShadertoy::LoadPreset(int select)
327 {
328   if (!m_settingsUseOwnshader)
329   {
330     m_currentPreset = select % g_presets.size();
331     Launch(m_currentPreset);
332     kodi::SetSettingInt("lastpresetidx", m_currentPreset);
333   }
334   return true;
335 }
336 
RandomPreset()337 bool CVisualizationShadertoy::RandomPreset()
338 {
339   if (!m_settingsUseOwnshader)
340   {
341     m_currentPreset = (int)((std::rand() / (float)RAND_MAX) * g_presets.size());
342     Launch(m_currentPreset);
343     kodi::SetSettingInt("lastpresetidx", m_currentPreset);
344   }
345   return true;
346 }
347 
348 //-- GetPresets ---------------------------------------------------------------
349 // Return a list of presets to Kodi for display
350 //-----------------------------------------------------------------------------
GetPresets(std::vector<std::string> & presets)351 bool CVisualizationShadertoy::GetPresets(std::vector<std::string>& presets)
352 {
353   if (!m_settingsUseOwnshader)
354   {
355     for (auto preset : g_presets)
356       presets.push_back(preset.name);
357   }
358   return true;
359 }
360 
361 //-- GetPreset ----------------------------------------------------------------
362 // Return the index of the current playing preset
363 //-----------------------------------------------------------------------------
GetActivePreset()364 int CVisualizationShadertoy::GetActivePreset()
365 {
366   return m_currentPreset;
367 }
368 
RenderTo(GLuint shader,GLuint effect_fb)369 void CVisualizationShadertoy::RenderTo(GLuint shader, GLuint effect_fb)
370 {
371   glUseProgram(shader);
372 
373   if (shader == m_shadertoyShader.ProgramHandle())
374   {
375     GLuint w = Width();
376     GLuint h = Height();
377     if (m_state.fbwidth && m_state.fbheight)
378       w = m_state.fbwidth, h = m_state.fbheight;
379     int64_t intt = static_cast<int64_t>(std::chrono::duration<double>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0) - m_initialTime;
380     if (m_bitsPrecision)
381       intt &= (1<<m_bitsPrecision)-1;
382 
383     if (m_needsUpload)
384     {
385       for (int i = 0; i < 4; i++)
386       {
387         if (m_shaderTextures[i].audio)
388         {
389           glActiveTexture(GL_TEXTURE0 + i);
390           glBindTexture(GL_TEXTURE_2D, m_channelTextures[i]);
391           glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, NUM_BANDS, 2, 0, GL_RED, GL_UNSIGNED_BYTE, m_audioData);
392         }
393       }
394       m_needsUpload = false;
395     }
396 
397     float t = intt / 1000.0f;
398     GLfloat tv[] = { t, t, t, t };
399 
400     glUniform3f(m_attrResolutionLoc, w, h, 0.0f);
401     glUniform1f(m_attrGlobalTimeLoc, t);
402     glUniform1f(m_attrSampleRateLoc, m_samplesPerSec);
403     glUniform1fv(m_attrChannelTimeLoc, 4, tv);
404     glUniform2f(m_state.uScale, static_cast<GLfloat>(Width()) / m_state.fbwidth, static_cast<GLfloat>(Height()) /m_state.fbheight);
405 
406     time_t now = time(NULL);
407     tm *ltm = localtime(&now);
408 
409     float year = 1900 + ltm->tm_year;
410     float month = ltm->tm_mon;
411     float day = ltm->tm_mday;
412     float sec = (ltm->tm_hour * 60 * 60) + (ltm->tm_min * 60) + ltm->tm_sec;
413 
414     glUniform4f(m_attrDateLoc, year, month, day, sec);
415 
416     for (int i = 0; i < 4; i++)
417     {
418       glActiveTexture(GL_TEXTURE0 + i);
419       glUniform1i(m_attrChannelLoc[i], i);
420       glBindTexture(GL_TEXTURE_2D, m_channelTextures[i]);
421     }
422   }
423   else
424   {
425     glActiveTexture(GL_TEXTURE0);
426     glBindTexture(GL_TEXTURE_2D, m_state.framebuffer_texture);
427     glUniform1i(m_state.uTexture, 0); // first currently bound texture "GL_TEXTURE0"
428   }
429 
430   // Draw the effect to a texture or direct to framebuffer
431   glBindFramebuffer(GL_FRAMEBUFFER, effect_fb);
432 
433   GLuint attr_vertex = shader == m_shadertoyShader.ProgramHandle() ? m_state.attr_vertex_e : m_state.attr_vertex_r;
434   glBindBuffer(GL_ARRAY_BUFFER, m_state.vertex_buffer);
435   glVertexAttribPointer(attr_vertex, 4, GL_FLOAT, 0, 16, 0);
436   glEnableVertexAttribArray(attr_vertex);
437   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
438   glDisableVertexAttribArray(attr_vertex);
439   glBindBuffer(GL_ARRAY_BUFFER, 0);
440 
441   for (int i = 0; i < 4; i++)
442   {
443     glActiveTexture(GL_TEXTURE0 + i);
444     glBindTexture(GL_TEXTURE_2D, 0);
445   }
446 
447   glUseProgram(0);
448 }
449 
Mix(float * destination,const float * source,size_t frames,size_t channels)450 void CVisualizationShadertoy::Mix(float* destination, const float* source, size_t frames, size_t channels)
451 {
452   size_t length = frames * channels;
453   for (unsigned int i = 0; i < length; i += channels)
454   {
455     float v = 0.0f;
456     for (size_t j = 0; j < channels; j++)
457     {
458       v += source[i + j];
459     }
460 
461     destination[(i / 2)] = v / (float)channels;
462   }
463 }
464 
WriteToBuffer(const float * input,size_t length,size_t channels)465 void CVisualizationShadertoy::WriteToBuffer(const float* input, size_t length, size_t channels)
466 {
467   size_t frames = length / channels;
468 
469   if (frames >= AUDIO_BUFFER)
470   {
471     size_t offset = frames - AUDIO_BUFFER;
472 
473     Mix(m_pcm, input + offset, AUDIO_BUFFER, channels);
474   }
475   else
476   {
477     size_t keep = AUDIO_BUFFER - frames;
478     memmove(m_pcm, m_pcm + frames, keep * sizeof(float));
479 
480     Mix(m_pcm + keep, input, frames, channels);
481   }
482 }
483 
Launch(int preset)484 void CVisualizationShadertoy::Launch(int preset)
485 {
486   m_bitsPrecision = DetermineBitsPrecision();
487   // mali-400 has only 10 bits which means milliseond timer wraps after ~1 second.
488   // we'll fudge that up a bit as having a larger range is more important than ms accuracy
489   m_bitsPrecision = std::max(m_bitsPrecision, 13);
490 #ifdef DEBUG_PRINT
491   printf("bits=%d\n", m_bitsPrecision);
492 #endif
493 
494   UnloadTextures();
495 
496   if (preset < 0)
497   {
498     m_usedShaderFile = kodi::GetSettingString("shader");
499     m_shaderTextures[0].audio = kodi::GetSettingBoolean("texture0-sound");
500     m_shaderTextures[0].texture = kodi::GetSettingString("texture0");
501     m_shaderTextures[1].audio = kodi::GetSettingBoolean("texture1-sound");
502     m_shaderTextures[1].texture = kodi::GetSettingString("texture1");
503     m_shaderTextures[2].audio = kodi::GetSettingBoolean("texture2-sound");
504     m_shaderTextures[2].texture = kodi::GetSettingString("texture2");
505     m_shaderTextures[3].audio = kodi::GetSettingBoolean("texture3-sound");
506     m_shaderTextures[3].texture = kodi::GetSettingString("texture3");
507   }
508   else
509   {
510     m_usedShaderFile = kodi::GetAddonPath("resources/shaders/" + g_presets[preset].file);
511     for (int i = 0; i < 4; i++)
512     {
513       if (g_presets[preset].channel[i] >= 0 && g_presets[preset].channel[i] < g_fileTextures.size())
514       {
515         m_shaderTextures[i].texture = kodi::GetAddonPath("resources/" + g_fileTextures[g_presets[preset].channel[i]]);
516       }
517       else if (g_presets[preset].channel[i] == 99) // framebuffer
518       {
519         m_shaderTextures[i].audio = true;
520       }
521       else
522       {
523         m_shaderTextures[i].texture = "";
524         m_shaderTextures[i].audio = false;
525       }
526     }
527   }
528 
529   for (int i = 0; i < 4; i++)
530   {
531     if (m_shaderTextures[i].audio)
532     {
533       m_channelTextures[i] = CreateTexture(GL_RED, NUM_BANDS, 2, m_audioData);
534     }
535     else if (!m_shaderTextures[i].texture.empty())
536     {
537       GLint format = GL_RGBA;
538       GLint scaling = GL_LINEAR;
539       GLint repeat = GL_REPEAT;
540       m_channelTextures[i] = CreateTexture(m_shaderTextures[i].texture, format, scaling, repeat);
541     }
542   }
543 
544   const int size1 = 256, size2=512;
545   double t1 = MeasurePerformance(m_usedShaderFile, size1);
546   double t2 = MeasurePerformance(m_usedShaderFile, size2);
547 
548   double expected_fps = 40.0;
549   // time per pixel for rendering fragment shader
550   double B = (t2-t1)/(size2*size2-size1*size1);
551   // time to render to screen
552   double A = t2 - size2*size2 * B;
553   // how many pixels get the desired fps
554   double pixels = (1000.0/expected_fps - A) / B;
555   m_state.fbwidth = sqrtf(pixels * Width() / Height());
556   if (m_state.fbwidth >= Width())
557     m_state.fbwidth = 0;
558   else if (m_state.fbwidth < 320)
559     m_state.fbwidth = 320;
560   m_state.fbheight = m_state.fbwidth * Height() / Width();
561 
562 #ifdef DEBUG_PRINT
563   printf("expected fps=%f, pixels=%f %dx%d (A:%f B:%f t1:%.1f t2:%.1f)\n", expected_fps, pixels, m_state.fbwidth, m_state.fbheight, A, B, t1, t2);
564 #endif
565 
566   LoadPreset(m_usedShaderFile);
567 }
568 
UnloadTextures()569 void CVisualizationShadertoy::UnloadTextures()
570 {
571   for (int i = 0; i < 4; i++)
572   {
573     if (m_channelTextures[i])
574     {
575       glDeleteTextures(1, &m_channelTextures[i]);
576       m_channelTextures[i] = 0;
577     }
578   }
579 }
580 
LoadPreset(const std::string & shaderPath)581 void CVisualizationShadertoy::LoadPreset(const std::string& shaderPath)
582 {
583   UnloadPreset();
584   std::string vertShadertoyShader = kodi::GetAddonPath("resources/shaders/main_shadertoy_" GL_TYPE_STRING ".vert.glsl");
585   if (!m_shadertoyShader.LoadShaderFiles(vertShadertoyShader, shaderPath) ||
586       !m_shadertoyShader.CompileAndLink("", "", fsHeader, fsFooter))
587   {
588     kodi::Log(ADDON_LOG_ERROR, "Failed to compile shadertoy shaders (current shadertoy file '%s')", shaderPath.c_str());
589     return;
590   }
591 
592   GLuint shadertoyShader = m_shadertoyShader.ProgramHandle();
593 
594   m_attrResolutionLoc = glGetUniformLocation(shadertoyShader, "iResolution");
595   m_attrGlobalTimeLoc = glGetUniformLocation(shadertoyShader, "iGlobalTime");
596   m_attrChannelTimeLoc = glGetUniformLocation(shadertoyShader, "iChannelTime");
597   m_attrMouseLoc = glGetUniformLocation(shadertoyShader, "iMouse");
598   m_attrDateLoc = glGetUniformLocation(shadertoyShader, "iDate");
599   m_attrSampleRateLoc  = glGetUniformLocation(shadertoyShader, "iSampleRate");
600   m_attrChannelResolutionLoc = glGetUniformLocation(shadertoyShader, "iChannelResolution");
601   m_attrChannelLoc[0] = glGetUniformLocation(shadertoyShader, "iChannel0");
602   m_attrChannelLoc[1] = glGetUniformLocation(shadertoyShader, "iChannel1");
603   m_attrChannelLoc[2] = glGetUniformLocation(shadertoyShader, "iChannel2");
604   m_attrChannelLoc[3] = glGetUniformLocation(shadertoyShader, "iChannel3");
605 
606   m_state.uScale = glGetUniformLocation(shadertoyShader, "uScale");
607   m_state.attr_vertex_e = glGetAttribLocation(shadertoyShader,  "vertex");
608 
609   std::string vertShader = kodi::GetAddonPath("resources/shaders/main_display_" GL_TYPE_STRING ".vert.glsl");
610   std::string fraqShader = kodi::GetAddonPath("resources/shaders/main_display_" GL_TYPE_STRING ".frag.glsl");
611   if (!m_displayShader.LoadShaderFiles(vertShader, fraqShader) ||
612       !m_displayShader.CompileAndLink())
613   {
614     kodi::Log(ADDON_LOG_ERROR, "Failed to compile main shaders");
615     return;
616   }
617 
618   m_state.uTexture = glGetUniformLocation(m_displayShader.ProgramHandle(), "uTexture");
619   m_state.attr_vertex_r = glGetAttribLocation(m_displayShader.ProgramHandle(), "vertex");
620 
621   // Prepare a texture to render to
622   glActiveTexture(GL_TEXTURE0);
623   glGenTextures(1, &m_state.framebuffer_texture);
624   glBindTexture(GL_TEXTURE_2D, m_state.framebuffer_texture);
625   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_state.fbwidth, m_state.fbheight, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
626   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
627   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
628   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
629   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
630 
631   // Prepare a framebuffer for rendering
632   glGenFramebuffers(1, &m_state.effect_fb);
633   glBindFramebuffer(GL_FRAMEBUFFER, m_state.effect_fb);
634   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_state.framebuffer_texture, 0);
635   glBindFramebuffer(GL_FRAMEBUFFER, 0);
636 
637   m_initialTime = static_cast<int64_t>(std::chrono::duration<double>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0);
638 }
639 
UnloadPreset()640 void CVisualizationShadertoy::UnloadPreset()
641 {
642   if (m_state.framebuffer_texture)
643   {
644     glDeleteTextures(1, &m_state.framebuffer_texture);
645     m_state.framebuffer_texture = 0;
646   }
647   if (m_state.effect_fb)
648   {
649     glDeleteFramebuffers(1, &m_state.effect_fb);
650     m_state.effect_fb = 0;
651   }
652 }
653 
CreateTexture(GLint format,unsigned int w,unsigned int h,const GLvoid * data)654 GLuint CVisualizationShadertoy::CreateTexture(GLint format, unsigned int w, unsigned int h, const GLvoid* data)
655 {
656   GLuint texture = 0;
657   glActiveTexture(GL_TEXTURE0);
658   glGenTextures(1, &texture);
659   glBindTexture(GL_TEXTURE_2D, texture);
660 
661   glTexParameteri(GL_TEXTURE_2D,  GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
662   glTexParameteri(GL_TEXTURE_2D,  GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
663 
664   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
665   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
666 
667   glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data);
668   return texture;
669 }
670 
CreateTexture(const GLvoid * data,GLint format,unsigned int w,unsigned int h,GLint internalFormat,GLint scaling,GLint repeat)671 GLuint CVisualizationShadertoy::CreateTexture(const GLvoid* data, GLint format, unsigned int w, unsigned int h, GLint internalFormat, GLint scaling, GLint repeat)
672 {
673   GLuint texture = 0;
674   glGenTextures(1, &texture);
675   glBindTexture(GL_TEXTURE_2D, texture);
676 
677   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, scaling);
678   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, scaling);
679 
680   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
681   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
682 
683   glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, GL_UNSIGNED_BYTE, data);
684   glBindTexture(GL_TEXTURE_2D, 0);
685 
686   return texture;
687 }
688 
CreateTexture(const std::string & file,GLint internalFormat,GLint scaling,GLint repeat)689 GLuint CVisualizationShadertoy::CreateTexture(const std::string& file, GLint internalFormat, GLint scaling, GLint repeat)
690 {
691 #ifdef DEBUG_PRINT
692   printf("creating texture %s\n", file.c_str());
693 #endif
694 
695   unsigned error;
696   unsigned char* image;
697   unsigned width, height;
698 
699   error = lodepng_decode32_file(&image, &width, &height, file.c_str());
700   if (error)
701   {
702     kodi::Log(ADDON_LOG_ERROR, "lodepng_decode32_file error %u: %s", error, lodepng_error_text(error));
703     return 0;
704   }
705 
706   GLuint texture = CreateTexture(image, GL_RGBA, width, height, internalFormat, scaling, repeat);
707   free(image);
708   return texture;
709 }
710 
BlackmanWindow(float in,size_t i,size_t length)711 float CVisualizationShadertoy::BlackmanWindow(float in, size_t i, size_t length)
712 {
713   double alpha = 0.16;
714   double a0 = 0.5 * (1.0 - alpha);
715   double a1 = 0.5;
716   double a2 = 0.5 * alpha;
717 
718   float x = (float)i / (float)length;
719   return in * (a0 - a1 * cos(2.0 * M_PI * x) + a2 * cos(4.0 * M_PI * x));
720 }
721 
SmoothingOverTime(float * outputBuffer,float * lastOutputBuffer,kiss_fft_cpx * inputBuffer,size_t length,float smoothingTimeConstant,unsigned int fftSize)722 void CVisualizationShadertoy::SmoothingOverTime(float* outputBuffer, float* lastOutputBuffer, kiss_fft_cpx* inputBuffer, size_t length, float smoothingTimeConstant, unsigned int fftSize)
723 {
724   for (size_t i = 0; i < length; i++)
725   {
726     kiss_fft_cpx c = inputBuffer[i];
727     float magnitude = sqrt(c.r * c.r + c.i * c.i) / (float)fftSize;
728     outputBuffer[i] = smoothingTimeConstant * lastOutputBuffer[i] + (1.0 - smoothingTimeConstant) * magnitude;
729   }
730 }
731 
LinearToDecibels(float linear)732 float CVisualizationShadertoy::LinearToDecibels(float linear)
733 {
734   if (!linear)
735     return -1000;
736   return 20 * log10f(linear);
737 }
738 
DetermineBitsPrecision()739 int CVisualizationShadertoy::DetermineBitsPrecision()
740 {
741   m_state.fbwidth = 32, m_state.fbheight = 26*10;
742   LoadPreset(kodi::GetAddonPath("resources/shaders/main_test.frag.glsl"));
743   RenderTo(m_shadertoyShader.ProgramHandle(), m_state.effect_fb);
744   glFinish();
745 
746   unsigned char* buffer = new unsigned char[m_state.fbwidth * m_state.fbheight * 4];
747   if (buffer)
748     glReadPixels(0, 0, m_state.fbwidth, m_state.fbheight, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
749 
750   int bits = 0;
751   unsigned char b = 0;
752   for (int j=0; j<m_state.fbheight; j++)
753   {
754     unsigned char c = buffer[4*(j*m_state.fbwidth+(m_state.fbwidth>>1))];
755     if (c && !b)
756       bits++;
757     b = c;
758   }
759   delete[] buffer;
760   UnloadPreset();
761   return bits;
762 }
763 
MeasurePerformance(const std::string & shaderPath,int size)764 double CVisualizationShadertoy::MeasurePerformance(const std::string& shaderPath, int size)
765 {
766   int iterations = -1;
767   m_state.fbwidth = m_state.fbheight = size;
768   LoadPreset(shaderPath);
769 
770   int64_t end, start;
771   do
772   {
773     RenderTo(m_shadertoyShader.ProgramHandle(), m_state.effect_fb);
774     RenderTo(m_displayShader.ProgramHandle(), m_state.effect_fb);
775     glFinish();
776     if (++iterations == 0)
777       start = static_cast<int64_t>(std::chrono::duration<double>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0);
778     end = static_cast<int64_t>(std::chrono::duration<double>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0);
779   } while (end - start < 50);
780   double t = (double)(end - start)/iterations;
781 #ifdef DEBUG_PRINT
782   printf("%s %dx%d %.1fms = %.2f fps\n", __func__, size, size, t, 1000.0/t);
783 #endif
784   UnloadPreset();
785   return t;
786 }
787 
788 ADDONCREATOR(CVisualizationShadertoy) // Don't touch this!
789