diff --git a/plugins/channelrx/demodatv/atvdemod.h b/plugins/channelrx/demodatv/atvdemod.h index aae35a695..95133c5f3 100644 --- a/plugins/channelrx/demodatv/atvdemod.h +++ b/plugins/channelrx/demodatv/atvdemod.h @@ -87,7 +87,7 @@ public: } void setScopeSink(BasebandSampleSink* scopeSink) { m_basebandSink->setScopeSink(scopeSink); } - void setTVScreen(TVScreen *tvScreen) { m_basebandSink->setTVScreen(tvScreen); }; //!< set by the GUI + void setTVScreen(TVScreenAnalog *tvScreen) { m_basebandSink->setTVScreen(tvScreen); }; //!< set by the GUI double getMagSq() const { return m_basebandSink->getMagSq(); } //!< Beware this is scaled to 2^30 bool getBFOLocked() { return m_basebandSink->getBFOLocked(); } void setVideoTabIndex(int videoTabIndex) { m_basebandSink->setVideoTabIndex(videoTabIndex); } diff --git a/plugins/channelrx/demodatv/atvdemodbaseband.h b/plugins/channelrx/demodatv/atvdemodbaseband.h index f666bea08..a154a5a83 100644 --- a/plugins/channelrx/demodatv/atvdemodbaseband.h +++ b/plugins/channelrx/demodatv/atvdemodbaseband.h @@ -64,7 +64,7 @@ public: int getChannelSampleRate() const; double getMagSq() const { return m_sink.getMagSq(); } void setScopeSink(BasebandSampleSink* scopeSink) { m_sink.setScopeSink(scopeSink); } - void setTVScreen(TVScreen *tvScreen) { m_sink.setTVScreen(tvScreen); } + void setTVScreen(TVScreenAnalog *tvScreen) { m_sink.setTVScreen(tvScreen); } bool getBFOLocked() { return m_sink.getBFOLocked(); } void setVideoTabIndex(int videoTabIndex) { m_sink.setVideoTabIndex(videoTabIndex); } void setBasebandSampleRate(int sampleRate); //!< To be used when supporting thread is stopped diff --git a/plugins/channelrx/demodatv/atvdemodgui.cpp b/plugins/channelrx/demodatv/atvdemodgui.cpp index b03c7bb50..02d7f91cc 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.cpp +++ b/plugins/channelrx/demodatv/atvdemodgui.cpp @@ -242,8 +242,6 @@ ATVDemodGUI::ATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Base m_tvSampleRate(48000) { ui->setupUi(this); - ui->screenTV->setColor(false); - ui->screenTV->setExtraColumns(true); setAttribute(Qt::WA_DeleteOnClose, true); connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); diff --git a/plugins/channelrx/demodatv/atvdemodgui.ui b/plugins/channelrx/demodatv/atvdemodgui.ui index 37a18c348..02641c0fd 100644 --- a/plugins/channelrx/demodatv/atvdemodgui.ui +++ b/plugins/channelrx/demodatv/atvdemodgui.ui @@ -1138,7 +1138,7 @@ QLayout::SetMinimumSize - + 0 @@ -1242,9 +1242,9 @@ 1 - TVScreen + TVScreenAnalog QWidget -
gui/tvscreen.h
+
gui/tvscreenanalog.h
1
diff --git a/plugins/channelrx/demodatv/atvdemodsink.cpp b/plugins/channelrx/demodatv/atvdemodsink.cpp index 70ed65d8e..72b9c7a2f 100644 --- a/plugins/channelrx/demodatv/atvdemodsink.cpp +++ b/plugins/channelrx/demodatv/atvdemodsink.cpp @@ -43,14 +43,12 @@ ATVDemodSink::ATVDemodSink() : m_ampMin(-1.0f), m_ampMax(1.0f), m_ampDelta(2.0f), - m_colIndex(0), - m_sampleIndex(0), - m_sampleIndexDetected(0), - m_hSyncShiftSum(0.0f), - m_hSyncShiftCount(0), + m_sampleOffset(0), + m_sampleOffsetFrac(0.0f), + m_sampleOffsetDetected(0), + m_hSyncShift(0.0f), m_hSyncErrorCount(0), m_amSampleIndex(0), - m_rowIndex(0), m_lineIndex(0), m_ampAverage(4800), m_bfoPLL(200/1000000, 100/1000000, 0.01), @@ -359,7 +357,7 @@ void ATVDemodSink::applyStandard(int sampleRate, const ATVDemodSettings& setting // what is left in a line for the image m_interleaved = true; m_numberOfVSyncLines = 4; - m_numberOfBlackLines = 58; + m_numberOfBlackLines = 59; m_firstVisibleLine = 27; m_numberSamplesHSyncCrop = (int) (0.085f * lineDuration * sampleRate); // 8.5% of full line empirically break; @@ -486,11 +484,10 @@ void ATVDemodSink::applyChannelSettings(int channelSampleRate, int channelFreque m_samplesPerLine - m_numberSamplesPerLineSignals, m_settings.m_nbLines - m_numberOfBlackLines ); + m_tvScreenData = m_registeredTVScreen->getData(); } m_fieldIndex = 0; - m_colIndex = 0; - m_rowIndex = 0; m_channelSampleRate = channelSampleRate; m_channelFrequencyOffset = channelFrequencyOffset; @@ -579,11 +576,10 @@ void ATVDemodSink::applySettings(const ATVDemodSettings& settings, bool force) m_samplesPerLine - m_numberSamplesPerLineSignals, m_settings.m_nbLines - m_numberOfBlackLines ); + m_tvScreenData = m_registeredTVScreen->getData(); } m_fieldIndex = 0; - m_colIndex = 0; - m_rowIndex = 0; } if ((settings.m_topTimeFactor != m_settings.m_topTimeFactor) || force) { diff --git a/plugins/channelrx/demodatv/atvdemodsink.h b/plugins/channelrx/demodatv/atvdemodsink.h index 6c6a74d58..40e48eb02 100644 --- a/plugins/channelrx/demodatv/atvdemodsink.h +++ b/plugins/channelrx/demodatv/atvdemodsink.h @@ -20,6 +20,7 @@ #include #include +#include #include "dsp/channelsamplesink.h" #include "dsp/basebandsamplesink.h" @@ -32,7 +33,7 @@ #include "dsp/phasediscri.h" #include "audio/audiofifo.h" #include "util/movingaverage.h" -#include "gui/tvscreen.h" +#include "gui/tvscreenanalog.h" #include "atvdemodsettings.h" @@ -44,7 +45,7 @@ public: virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); void setScopeSink(BasebandSampleSink* scopeSink) { m_scopeSink = scopeSink; } - void setTVScreen(TVScreen *tvScreen) { m_registeredTVScreen = tvScreen; } //!< set by the GUI + void setTVScreen(TVScreenAnalog *tvScreen) { m_registeredTVScreen = tvScreen; } //!< set by the GUI double getMagSq() const { return m_magSqAverage; } //!< Beware this is scaled to 2^30 bool getBFOLocked(); void setVideoTabIndex(int videoTabIndex) { m_videoTabIndex = videoTabIndex; } @@ -107,7 +108,8 @@ private: SampleVector m_scopeSampleBuffer; //*************** ATV PARAMETERS *************** - TVScreen *m_registeredTVScreen; + TVScreenAnalog *m_registeredTVScreen; + std::shared_ptr m_tvScreenData; //int m_intNumberSamplePerLine; int m_numberSamplesPerHTopNom; //!< number of samples per horizontal synchronization pulse (pulse in ultra-black) - nominal value @@ -148,15 +150,14 @@ private: float m_fltBufferI[6]; float m_fltBufferQ[6]; - int m_colIndex; - int m_sampleIndex; // assumed (averaged) sample offset from the start of horizontal sync pulse - int m_sampleIndexDetected; // detected sample offset from the start of horizontal sync pulse - int m_amSampleIndex; - int m_rowIndex; + int m_amSampleIndex; + + int m_sampleOffset; // assumed (averaged) sample offset from the start of horizontal sync pulse + float m_sampleOffsetFrac; // sample offset, fractional part + int m_sampleOffsetDetected; // detected sample offset from the start of horizontal sync pulse int m_lineIndex; - float m_hSyncShiftSum; - int m_hSyncShiftCount; + float m_hSyncShift; int m_hSyncErrorCount; float prevSample; @@ -198,67 +199,71 @@ private: inline void processSample(float& sample, int& sampleVideo) { // Filling pixel on the current line - reference index 0 at start of sync pulse - m_registeredTVScreen->setDataColor(m_sampleIndex - m_numberSamplesPerHSync, sampleVideo, sampleVideo, sampleVideo); + m_tvScreenData->setSampleValue(m_sampleOffset - m_numberSamplesPerHSync, sampleVideo); if (m_settings.m_hSync) { // Horizontal Synchro detection if ((prevSample >= m_settings.m_levelSynchroTop && sample < m_settings.m_levelSynchroTop) // horizontal synchro detected - && (m_sampleIndexDetected > m_samplesPerLine - m_numberSamplesPerHTopNom)) + && (m_sampleOffsetDetected > m_samplesPerLine - m_numberSamplesPerHTopNom)) { - double sampleIndexDetectedFrac = + float sampleOffsetDetectedFrac = (sample - m_settings.m_levelSynchroTop) / (prevSample - sample); - double hSyncShift = -m_sampleIndex - sampleIndexDetectedFrac; + float hSyncShift = -m_sampleOffset - m_sampleOffsetFrac - sampleOffsetDetectedFrac; if (hSyncShift > m_samplesPerLine / 2) - hSyncShift -= m_samplesPerLine; + hSyncShift -= m_samplesPerLine; else if (hSyncShift < -m_samplesPerLine / 2) - hSyncShift += m_samplesPerLine; + hSyncShift += m_samplesPerLine; if (fabs(hSyncShift) > m_numberSamplesPerHTopNom) { m_hSyncErrorCount++; - if (m_hSyncErrorCount >= 8) + if (m_hSyncErrorCount >= 4) { // Fast sync: shift is too large, needs to be fixed ASAP - m_sampleIndex = 0; - m_hSyncShiftSum = 0.0; - m_hSyncShiftCount = 0; + m_hSyncShift = hSyncShift; m_hSyncErrorCount = 0; } } else { - m_hSyncShiftSum += hSyncShift; - m_hSyncShiftCount++; + // Slow sync: slight adjustment is needed + m_hSyncShift = hSyncShift * 0.2f; m_hSyncErrorCount = 0; } - m_sampleIndexDetected = 0; + m_sampleOffsetDetected = 0; } else - m_sampleIndexDetected++; + m_sampleOffsetDetected++; } - else - { - m_hSyncShiftSum = 0.0f; - m_hSyncShiftCount = 0; - } - m_sampleIndex++; + m_sampleOffset++; if (m_settings.m_vSync) { - if (m_sampleIndex > m_fieldDetectStartPos && m_sampleIndex < m_fieldDetectEndPos) + if (m_sampleOffset > m_fieldDetectStartPos && m_sampleOffset < m_fieldDetectEndPos) m_fieldDetectSampleCount += sample < m_settings.m_levelSynchroTop; - if (m_sampleIndex > m_vSyncDetectStartPos && m_sampleIndex < m_vSyncDetectEndPos) + if (m_sampleOffset > m_vSyncDetectStartPos && m_sampleOffset < m_vSyncDetectEndPos) m_vSyncDetectSampleCount += sample < m_settings.m_levelSynchroTop; } // end of line - if (m_sampleIndex >= m_samplesPerLine) + if (m_sampleOffset >= m_samplesPerLine) { - m_sampleIndex = 0; + if (m_settings.m_hSync) + { + float sampleOffsetFloat = m_hSyncShift + m_sampleOffsetFrac; + m_sampleOffset = sampleOffsetFloat; + m_sampleOffsetFrac = sampleOffsetFloat - m_sampleOffset; + } + else + { + m_sampleOffset = 0; + } + m_hSyncShift = 0.0f; - if (m_settings.m_atvStd == ATVDemodSettings::ATVStdHSkip) { + m_lineIndex++; + if (m_settings.m_atvStd == ATVDemodSettings::ATVStdHSkip) { processEOLHSkip(); } else { processEOLClassic(); @@ -271,23 +276,9 @@ private: // Standard vertical sync inline void processEOLClassic() { - m_lineIndex++; - if (m_lineIndex == m_numberOfVSyncLines + 3 && m_fieldIndex == 0) { - float shiftSamples = 0.0f; - - // Slow sync: slight adjustment is needed - if (m_hSyncShiftCount != 0 && m_settings.m_hSync) - { - shiftSamples = m_hSyncShiftSum / m_hSyncShiftCount; - m_sampleIndex = shiftSamples; - m_hSyncShiftSum = 0.0f; - m_hSyncShiftCount = 0; - m_hSyncErrorCount = 0; - } - m_registeredTVScreen->renderImage(0, - shiftSamples < -1.0f ? -1.0f : (shiftSamples > 1.0f ? 1.0f : shiftSamples)); + m_registeredTVScreen->renderImage(); } if (m_vSyncDetectSampleCount > m_vSyncDetectThreshold && @@ -319,38 +310,22 @@ private: int rowIndex = m_lineIndex - m_firstVisibleLine; if (m_interleaved) rowIndex = rowIndex * 2 - m_fieldIndex; - m_registeredTVScreen->selectRow(rowIndex); - } + + m_tvScreenData->selectRow(rowIndex, m_sampleOffsetFrac); + } // Vertical sync is obtained by skipping horizontal sync on the line that triggers vertical sync (new frame) inline void processEOLHSkip() { - m_lineIndex++; - m_rowIndex++; - - if ((m_sampleIndexDetected > (3*m_samplesPerLine) / 2) // Vertical sync is first horizontal sync after skip (count at least 1.5 line length) + if ((m_sampleOffsetDetected > (3 * m_samplesPerLine) / 2) // Vertical sync is first horizontal sync after skip (count at least 1.5 line length) || (!m_settings.m_vSync && (m_lineIndex >= m_settings.m_nbLines))) // Vsync ignored and reached nominal number of lines per frame { - float shiftSamples = 0.0f; - - // Slow sync: slight adjustment is needed - if (m_hSyncShiftCount != 0 && m_settings.m_hSync) - { - shiftSamples = m_hSyncShiftSum / m_hSyncShiftCount; - m_sampleIndex = shiftSamples; - m_hSyncShiftSum = 0.0f; - m_hSyncShiftCount = 0; - m_hSyncErrorCount = 0; - } - m_registeredTVScreen->renderImage(0, - shiftSamples < -1.0f ? -1.0f : (shiftSamples > 1.0f ? 1.0f : shiftSamples)); - m_lineIndex = 0; - m_rowIndex = 0; + m_registeredTVScreen->renderImage(); + m_lineIndex = 0; } - m_registeredTVScreen->selectRow(m_rowIndex); + m_tvScreenData->selectRow(m_lineIndex, m_sampleOffsetFrac); } }; - #endif // INCLUDE_ATVDEMODSINK_H \ No newline at end of file diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index cde208ddc..3f3507065 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -51,6 +51,7 @@ set(sdrgui_SOURCES gui/transverterbutton.cpp gui/transverterdialog.cpp gui/tvscreen.cpp + gui/tvscreenanalog.cpp gui/valuedial.cpp gui/valuedialz.cpp @@ -123,6 +124,7 @@ set(sdrgui_HEADERS gui/transverterbutton.h gui/transverterdialog.h gui/tvscreen.h + gui/tvscreenanalog.h gui/valuedial.h gui/valuedialz.h diff --git a/sdrgui/gui/glshadertvarray.cpp b/sdrgui/gui/glshadertvarray.cpp index 68a4eb80d..e3375c46b 100644 --- a/sdrgui/gui/glshadertvarray.cpp +++ b/sdrgui/gui/glshadertvarray.cpp @@ -39,13 +39,11 @@ GLShaderTVArray::GLShaderTVArray(bool blnColor) : m_blnColor(blnColor) { m_blnAlphaBlend = false; m_blnAlphaReset = false; - m_blnExtraColumns = false; m_objProgram = 0; m_objImage = 0; m_objTexture = 0; m_intCols = 0; m_intRows = 0; - m_subsampleShift = 0.0f; m_blnInitialized = false; m_objCurrentRow = 0; @@ -112,8 +110,7 @@ void GLShaderTVArray::InitializeGL(int intCols, int intRows) } //Image container - int cols = intCols + (m_blnExtraColumns ? 2 : 0); - m_objImage = new QImage(cols, intRows, QImage::Format_RGBA8888); + m_objImage = new QImage(intCols, intRows, QImage::Format_RGBA8888); m_objImage->fill(QColor(0, 0, 0)); m_objTexture = new QOpenGLTexture(*m_objImage); @@ -158,23 +155,11 @@ void GLShaderTVArray::RenderPixels(unsigned char *chrData) QMatrix4x4 objQMatrix; - float rectHalfWidth = 1.0f; - float sampleSize = 2.0f / m_intCols; - if (m_blnExtraColumns) - rectHalfWidth += sampleSize; - float xShift = sampleSize * m_subsampleShift; - GLfloat arrVertices[] = // 2 3 // 1 4 - { - -rectHalfWidth + xShift, -1.0f, // 1 - -rectHalfWidth + xShift, 1.0f, // 2 - rectHalfWidth + xShift, 1.0f, // 3 - rectHalfWidth + xShift, 1.0f, // 3 - rectHalfWidth + xShift, -1.0f, // 4 - -rectHalfWidth + xShift, -1.0f // 1 - }; + //1 2 3 3 4 1 + { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; GLfloat arrTextureCoords[] = // 1 4 @@ -242,9 +227,8 @@ void GLShaderTVArray::RenderPixels(unsigned char *chrData) m_objTexture->bind(); - ptrF->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - m_intCols + (m_blnExtraColumns ? 2 : 0), m_intRows, GL_RGBA, - GL_UNSIGNED_BYTE, m_objImage->bits()); + ptrF->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_intCols, m_intRows, GL_RGBA, + GL_UNSIGNED_BYTE, m_objImage->bits()); ptrF->glEnableVertexAttribArray(0); // vertex ptrF->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, arrVertices); @@ -334,11 +318,9 @@ bool GLShaderTVArray::SetDataColor(int intCol, QRgb objColor) if (m_blnInitialized) { - if ((intCol < m_intCols + m_blnExtraColumns) && - (intCol >= -m_blnExtraColumns) && - (m_objCurrentRow != 0)) + if ((intCol < m_intCols) && (intCol >= 0) && (m_objCurrentRow != 0)) { - m_objCurrentRow[intCol + m_blnExtraColumns] = objColor; + m_objCurrentRow[intCol] = objColor; blnRslt = true; } } diff --git a/sdrgui/gui/glshadertvarray.h b/sdrgui/gui/glshadertvarray.h index 7b9a07600..559a7351f 100644 --- a/sdrgui/gui/glshadertvarray.h +++ b/sdrgui/gui/glshadertvarray.h @@ -43,8 +43,6 @@ public: GLShaderTVArray(bool blnColor); ~GLShaderTVArray(); - void setExtraColumns(bool blnExtraColumns) { m_blnExtraColumns = blnExtraColumns; } - void setSubsampleShift(float subsampleShift) { m_subsampleShift = subsampleShift; } void setColor(bool blnColor) { m_blnColor = blnColor; } void setAlphaBlend(bool blnAlphaBlend) { m_blnAlphaBlend = blnAlphaBlend; } void setAlphaReset() { m_blnAlphaReset = true; } @@ -73,7 +71,6 @@ protected: int m_intCols; int m_intRows; - float m_subsampleShift; QRgb * m_objCurrentRow; @@ -81,7 +78,6 @@ protected: bool m_blnColor; bool m_blnAlphaBlend; bool m_blnAlphaReset; - bool m_blnExtraColumns; }; #endif /* INCLUDE_GUI_GLTVSHADERARRAY_H_ */ diff --git a/sdrgui/gui/tvscreen.cpp b/sdrgui/gui/tvscreen.cpp index e1182999f..26ce7b490 100644 --- a/sdrgui/gui/tvscreen.cpp +++ b/sdrgui/gui/tvscreen.cpp @@ -37,7 +37,6 @@ TVScreen::TVScreen(bool blnColor, QWidget* parent) : m_objTimer.start(40); // capped at 25 FPS m_chrLastData = NULL; - m_subsampleShift = 0.0; m_blnConfigChanged = false; m_blnDataChanged = false; m_blnGLContextInitialized = false; @@ -59,11 +58,6 @@ void TVScreen::setColor(bool blnColor) m_objGLShaderArray.setColor(blnColor); } -void TVScreen::setExtraColumns(bool blnExtraColumns) -{ - m_objGLShaderArray.setExtraColumns(blnExtraColumns); -} - QRgb* TVScreen::getRowBuffer(int intRow) { if (!m_blnGLContextInitialized) @@ -74,10 +68,9 @@ QRgb* TVScreen::getRowBuffer(int intRow) return m_objGLShaderArray.GetRowBuffer(intRow); } -void TVScreen::renderImage(unsigned char * objData, float subsampleShift) +void TVScreen::renderImage(unsigned char * objData) { m_chrLastData = objData; - m_subsampleShift = subsampleShift; m_blnDataChanged = true; } @@ -184,7 +177,6 @@ void TVScreen::paintGL() m_intAskedRows = 0; } - m_objGLShaderArray.setSubsampleShift(m_subsampleShift); m_objGLShaderArray.RenderPixels(m_chrLastData); m_objMutex.unlock(); diff --git a/sdrgui/gui/tvscreen.h b/sdrgui/gui/tvscreen.h index 5ce4fa077..c796ee0ab 100644 --- a/sdrgui/gui/tvscreen.h +++ b/sdrgui/gui/tvscreen.h @@ -46,10 +46,9 @@ public: virtual ~TVScreen(); void setColor(bool blnColor); - void setExtraColumns(bool blnExtraColumns); void resizeTVScreen(int intCols, int intRows); void getSize(int& intCols, int& intRows) const; - void renderImage(unsigned char * objData, float subsampleShift = 0.0); + void renderImage(unsigned char * objData); QRgb* getRowBuffer(int intRow); void resetImage(); void resetImage(int alpha); @@ -74,7 +73,6 @@ private: bool m_blnGLContextInitialized; int m_intAskedCols; int m_intAskedRows; - float m_subsampleShift; // state diff --git a/sdrgui/gui/tvscreenanalog.cpp b/sdrgui/gui/tvscreenanalog.cpp new file mode 100644 index 000000000..5b6766515 --- /dev/null +++ b/sdrgui/gui/tvscreenanalog.cpp @@ -0,0 +1,249 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Vort // +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// // +// OpenGL interface modernization. // +// // +// 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 as 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "tvscreenanalog.h" + +static const char* vertexShaderSource = +"attribute highp vec4 vertex;\n" +"attribute highp vec2 texCoord;\n" +"varying highp vec2 texCoordVar;\n" +"void main() {\n" +" gl_Position = vertex;\n" +" texCoordVar = texCoord;\n" +"}\n"; + +static const char* fragmentShaderSource = +"uniform highp sampler2D tex1;\n" +"uniform highp sampler2D tex2;\n" +"uniform highp float imw;\n" +"uniform highp float imh;\n" +"uniform highp float tlw;\n" +"uniform highp float tlh;\n" +"varying highp vec2 texCoordVar;\n" +"void main() {\n" +" float tlhw = 0.5 * tlw;" +" float tlhh = 0.5 * tlh;" +" float tys = (texCoordVar.y + tlhh) * imh;\n" +" float p1y = floor(tys) * tlh - tlhh;\n" +" float p3y = p1y + tlh;\n" +" float tshift1 = texture2D(tex2, vec2(0.0, p1y)).r;\n" +" float tshift3 = texture2D(tex2, vec2(0.0, p3y)).r;\n" +" float shift1 = (1.0 - tshift1 * 2.0) * tlw;\n" +" float shift3 = (1.0 - tshift3 * 2.0) * tlw;\n" +" float txs1 = (texCoordVar.x + shift1 + tlhw) * imw;\n" +" float txs3 = (texCoordVar.x + shift3 + tlhw) * imw;\n" +" float p1x = floor(txs1) * tlw - tlhw;\n" +" float p3x = floor(txs3) * tlw - tlhw;\n" +" float p2x = p1x + tlw;\n" +" float p4x = p3x + tlw;\n" +" float p1 = texture2D(tex1, vec2(p1x, p1y)).r;\n" +" float p2 = texture2D(tex1, vec2(p2x, p1y)).r;\n" +" float p3 = texture2D(tex1, vec2(p3x, p3y)).r;\n" +" float p4 = texture2D(tex1, vec2(p4x, p3y)).r;\n" +" float p12 = mix(p1, p2, fract(txs1));\n" +" float p34 = mix(p3, p4, fract(txs3));\n" +" float p = mix(p12, p34, fract(tys));\n" +" gl_FragColor = vec4(p);\n" +"}\n"; + +TVScreenAnalog::TVScreenAnalog(QWidget *parent) + : QGLWidget(parent) +{ + m_isDataChanged = false; + m_data = std::make_shared(5, 1); + + connect(&m_objTimer, SIGNAL(timeout()), this, SLOT(tick())); + m_objTimer.start(40); // capped at 25 FPS +} + +void TVScreenAnalog::cleanup() +{ + m_shader = nullptr; + m_imageTexture = nullptr; + m_lineShiftsTexture = nullptr; +} + +std::shared_ptr TVScreenAnalog::getData() +{ + return m_data; +} + +void TVScreenAnalog::resizeTVScreen(int intCols, int intRows) +{ + qDebug("TVScreen::resizeTVScreen: cols: %d, rows: %d", intCols, intRows); + + int colsAdj = intCols + 4; + if (m_data->getWidth() != colsAdj || m_data->getHeight() != intRows) + m_data = std::make_shared(colsAdj, intRows); +} + +void TVScreenAnalog::resizeGL(int intWidth, int intHeight) +{ + glViewport(0, 0, intWidth, intHeight); +} + +void TVScreenAnalog::initializeGL() +{ + initializeOpenGLFunctions(); + + connect(QOpenGLContext::currentContext(), &QOpenGLContext::aboutToBeDestroyed, + this, &TVScreenAnalog::cleanup); // TODO: when migrating to QOpenGLWidget + + m_shader = std::make_shared(this); + if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) + { + qWarning() + << "TVScreenAnalog::initializeGL: error in vertex shader:" + << m_shader->log(); + m_shader = nullptr; + return; + } + if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) + { + qWarning() + << "TVScreenAnalog::initializeGL: error in fragment shader:" + << m_shader->log(); + m_shader = nullptr; + return; + } + if (!m_shader->link()) + { + qWarning() + << "TVScreenAnalog::initializeGL: error linking shader:" + << m_shader->log(); + m_shader = nullptr; + return; + } + + m_vertexAttribIndex = m_shader->attributeLocation("vertex"); + m_texCoordAttribIndex = m_shader->attributeLocation("texCoord"); + m_textureLoc1 = m_shader->uniformLocation("tex1"); + m_textureLoc2 = m_shader->uniformLocation("tex2"); + m_imageWidthLoc = m_shader->uniformLocation("imw"); + m_imageHeightLoc = m_shader->uniformLocation("imh"); + m_texelWidthLoc = m_shader->uniformLocation("tlw"); + m_texelHeightLoc = m_shader->uniformLocation("tlh"); +} + +void TVScreenAnalog::initializeTextures() +{ + m_imageTexture = std::make_shared(QOpenGLTexture::Target2D); + m_lineShiftsTexture = std::make_shared(QOpenGLTexture::Target2D); + m_imageTexture->setSize(m_data->getWidth(), m_data->getHeight()); + m_lineShiftsTexture->setSize(1, m_data->getHeight()); + m_imageTexture->setFormat(QOpenGLTexture::RGBA8_UNorm); + m_lineShiftsTexture->setFormat(QOpenGLTexture::RGBA8_UNorm); + m_imageTexture->setAutoMipMapGenerationEnabled(false); + m_lineShiftsTexture->setAutoMipMapGenerationEnabled(false); + m_imageTexture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); + m_lineShiftsTexture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); + + m_imageTexture->setMinificationFilter(QOpenGLTexture::Nearest); + m_imageTexture->setMagnificationFilter(QOpenGLTexture::Nearest); + m_lineShiftsTexture->setMinificationFilter(QOpenGLTexture::Nearest); + m_lineShiftsTexture->setMagnificationFilter(QOpenGLTexture::Nearest); + m_imageTexture->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::ClampToBorder); + m_imageTexture->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::ClampToEdge); + m_lineShiftsTexture->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat); + m_lineShiftsTexture->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::ClampToEdge); +} + +void TVScreenAnalog::renderImage() +{ + m_isDataChanged = true; +} + +void TVScreenAnalog::tick() +{ + if (m_isDataChanged) + { + update(); + } +} + +void TVScreenAnalog::paintGL() +{ + m_isDataChanged = false; + + if (!m_shader) + { + glClearColor(0.2f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + return; + } + + if (!m_imageTexture || + m_imageTexture->width() != m_data->getWidth() || + m_imageTexture->height() != m_data->getHeight()) + { + initializeTextures(); + } + + float imageWidth = m_data->getWidth(); + float imageHeight = m_data->getHeight(); + float texelWidth = 1.0f / imageWidth; + float texelHeight = 1.0f / imageHeight; + + m_shader->bind(); + m_shader->setUniformValue(m_textureLoc1, 0); + m_shader->setUniformValue(m_textureLoc2, 1); + m_shader->setUniformValue(m_imageWidthLoc, imageWidth); + m_shader->setUniformValue(m_imageHeightLoc, imageHeight); + m_shader->setUniformValue(m_texelWidthLoc, texelWidth); + m_shader->setUniformValue(m_texelHeightLoc, texelHeight); + + glActiveTexture(GL_TEXTURE0); + m_imageTexture->bind(); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + m_data->getWidth(), m_data->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, m_data->getImageData()); + + glActiveTexture(GL_TEXTURE1); + m_lineShiftsTexture->bind(); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + 1, m_data->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, m_data->getLineShiftData()); + + float rectHalfWidth = 1.0f + 4.0f / (imageWidth - 4.0f); + GLfloat vertices[] = + { + -rectHalfWidth, -1.0f, + -rectHalfWidth, 1.0f, + rectHalfWidth, 1.0f, + rectHalfWidth, -1.0f + }; + + static const GLfloat arrTextureCoords[] = + { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f + }; + + glVertexAttribPointer(m_vertexAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glEnableVertexAttribArray(m_vertexAttribIndex); + glVertexAttribPointer(m_texCoordAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords); + glEnableVertexAttribArray(m_texCoordAttribIndex); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDisableVertexAttribArray(m_vertexAttribIndex); + glDisableVertexAttribArray(m_texCoordAttribIndex); + + m_shader->release(); +} \ No newline at end of file diff --git a/sdrgui/gui/tvscreenanalog.h b/sdrgui/gui/tvscreenanalog.h new file mode 100644 index 000000000..080eda2f8 --- /dev/null +++ b/sdrgui/gui/tvscreenanalog.h @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Vort // +// Copyright (C) 2018 F4HKW // +// for F4EXB / SDRAngel // +// // +// OpenGL interface modernization. // +// See: http://doc.qt.io/qt-5/qopenglshaderprogram.html // +// // +// 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 as 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_TVSCREENANALOG_H +#define INCLUDE_TVSCREENANALOG_H + +#include "export.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +class TVScreenAnalogData +{ +public: + TVScreenAnalogData(int width, int height) + { + m_width = width; + m_height = height; + + m_imageData = new int[width * height]; + m_lineShiftData = new int[height]; + m_outOfBoundsLine = new int[width]; + m_currentLine = m_outOfBoundsLine; + + std::fill(m_imageData, m_imageData + width * height, 0); + std::fill(m_lineShiftData, m_lineShiftData + height, 127); + } + + ~TVScreenAnalogData() + { + delete[] m_imageData; + delete[] m_lineShiftData; + delete[] m_outOfBoundsLine; + } + + int getWidth() + { + return m_width; + } + + int getHeight() + { + return m_height; + } + + const int* getImageData() + { + return m_imageData; + } + + const int* getLineShiftData() + { + return m_lineShiftData; + } + + void selectRow(int line, float shift) + { + if ((line < m_height) && (line >= 0)) + { + m_currentLine = m_imageData + line * m_width; + m_lineShiftData[line] = (1.0f + shift) * 127.5f; + } + else + { + m_currentLine = m_outOfBoundsLine; + } + } + + void setSampleValue(int column, int value) + { + if ((column < m_width - 2) && (column >= -2)) + { + m_currentLine[column + 2] = value; + } + } + +private: + int m_width; + int m_height; + + int* m_imageData; + int* m_lineShiftData; + + int* m_currentLine; + int* m_outOfBoundsLine; +}; + +class SDRGUI_API TVScreenAnalog : public QGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + + QTimer m_objTimer; + + bool m_isDataChanged; + + int m_textureLoc1; + int m_textureLoc2; + int m_imageWidthLoc; + int m_imageHeightLoc; + int m_texelWidthLoc; + int m_texelHeightLoc; + int m_vertexAttribIndex; + int m_texCoordAttribIndex; + + std::shared_ptr m_data; + + std::shared_ptr m_shader; + std::shared_ptr m_imageTexture; + std::shared_ptr m_lineShiftsTexture; + +public: + TVScreenAnalog(QWidget *parent); + + std::shared_ptr getData(); + void resizeTVScreen(int intCols, int intRows); + void renderImage(); + +private: + void initializeTextures(); + void initializeGL() override; + void paintGL() override; + void resizeGL(int width, int height); + +private slots: + void cleanup(); + void tick(); +}; + +#endif // INCLUDE_TVSCREENANALOG_H