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