Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ jobs:
working-directory: build
env:
# Render analyzer waveform tests to an offscreen buffer
QT_QPA_PLATFORM: ${{ matrix.qt_qpa_platform }}
QT_QPA_PLATFORM: offscreen
GTEST_COLOR: 1
# Only use single thread to prevent *.gcna files from overwriting each other
CTEST_PARALLEL_LEVEL: 1
Expand Down
16 changes: 4 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2978,23 +2978,15 @@ if(BUILD_TESTING)
include(CTest)
include(GoogleTest)
enable_testing()
gtest_add_tests(
TARGET mixxx-test
gtest_discover_tests(
mixxx-test
EXTRA_ARGS --logLevel info
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen"
TEST_LIST testsuite
DISCOVERY_MODE PRE_TEST
)

if(NOT WIN32)
# Default to offscreen rendering during tests.
# This is required if the build system like Fedora koji/mock does not
# allow to pass environment variables into the ctest macro expansion.
set_tests_properties(
${testsuite}
PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen"
)
endif()

if(BUILD_BENCH)
# Benchmarking
add_custom_target(
Expand Down
112 changes: 90 additions & 22 deletions src/controllers/rendering/controllerrenderingengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ ControllerRenderingEngine::ControllerRenderingEngine(
switch (m_screenInfo.pixelFormat) {
case QImage::Format_RGB16:
m_GLDataFormat = GL_RGB;
#ifndef QT_OPENGL_ES_2
if (m_screenInfo.reversedColor) {
#ifdef QT_OPENGL_ES_2
m_isValid = false;
kLogger.critical() << "Reversed RGB16 format is not supported in OpenGL ES";
#else
m_GLDataType = GL_UNSIGNED_SHORT_5_6_5_REV;
#endif
} else {
#endif
m_GLDataType = GL_UNSIGNED_SHORT_5_6_5;
#ifndef QT_OPENGL_ES_2
}
#endif
break;
case QImage::Format_RGB888:
if (m_screenInfo.reversedColor) {
Expand Down Expand Up @@ -220,6 +219,7 @@ void ControllerRenderingEngine::setup(std::shared_ptr<QQmlEngine> qmlEngine) {
}

m_renderControl = std::make_unique<QQuickRenderControl>(this);
m_renderControl->setSamples(format.samples());
m_quickWindow = std::make_unique<QQuickWindow>(m_renderControl.get());

if (!qmlEngine->incubationController()) {
Expand Down Expand Up @@ -268,10 +268,10 @@ void ControllerRenderingEngine::renderFrame() {

VERIFY_OR_TERMINATE(m_offscreenSurface->isValid(), "OffscreenSurface isn't valid anymore.");
VERIFY_OR_TERMINATE(m_context->isValid(), "GLContext isn't valid anymore.");
VERIFY_OR_TERMINATE(m_context->makeCurrent(m_offscreenSurface.get()),
"Couldn't make the GLContext current to the OffscreenSurface.");

if (!m_fbo) {
VERIFY_OR_TERMINATE(m_context->makeCurrent(m_offscreenSurface.get()),
"Couldn't make the GLContext current to the OffscreenSurface.");
ScopedTimer t(QStringLiteral("ControllerRenderingEngine::renderFrame::initFBO"));
VERIFY_OR_TERMINATE(
QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(),
Expand All @@ -295,18 +295,24 @@ void ControllerRenderingEngine::renderFrame() {
m_screenInfo.size));

m_quickWindow->setGeometry(0, 0, m_screenInfo.size.width(), m_screenInfo.size.height());

m_context->doneCurrent();
}

m_nextFrameStart = Clock::now();

m_renderControl->beginFrame();

if (m_pEngineThreadControl) {
m_pEngineThreadControl->pause();
if (!m_pEngineThreadControl->pause()) {
kLogger.debug() << "Couldn't pause the GUI thread. Rescheduling frame rendering";
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
return;
}
}

m_renderControl->polishItems();

m_renderControl->beginFrame();

{
ScopedTimer t(QStringLiteral("ControllerRenderingEngine::renderFrame::sync"));
VERIFY_OR_DEBUG_ASSERT(m_renderControl->sync()) {
Expand All @@ -317,27 +323,38 @@ void ControllerRenderingEngine::renderFrame() {
if (m_pEngineThreadControl) {
m_pEngineThreadControl->resume();
}

VERIFY_OR_TERMINATE(m_offscreenSurface->isValid(), "OffscreenSurface isn't valid anymore.");
VERIFY_OR_TERMINATE(m_context->makeCurrent(m_offscreenSurface.get()),
"Couldn't make the GLContext current to the OffscreenSurface.");

#ifdef QT_OPENGL_ES_2
// OpenGL ES doesn't support extended format and type when reading pixel and
// only support GL_RGBA/GL_UNSIGNED_BYTE On this platform, we fallback to Qt
// for the pixel transformation, using QImage conversion capabilities
QImage fboImage(m_screenInfo.size, QImage::Format_RGBA8888);
#else
QImage fboImage(m_screenInfo.size, m_screenInfo.pixelFormat);
#endif

VERIFY_OR_DEBUG_ASSERT(m_fbo->bind()) {
kLogger.warning() << "Couldn't bind the FBO.";
}
GLenum glError;
// Flush any remaining GL errors.
while ((glError = m_context->functions()->glGetError()) != GL_NO_ERROR) {
kLogger.debug() << "Retrieved a previously unhandled GL error: " << glError;
}
#ifndef QT_OPENGL_ES_2
m_context->functions()->glFlush();
glError = m_context->functions()->glGetError();
VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError);
VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError after glFlush: " << glError);
if (static_cast<std::endian>(m_screenInfo.endian) != std::endian::native) {
#ifdef QT_OPENGL_ES_2
kLogger.critical()
<< "Screen endianness mismatches native endianness, but OpenGL "
"ES does not let us specify a reverse pixel store order. "
"This will likely lead to invalid colors.";
#else
m_context->functions()->glPixelStorei(GL_PACK_SWAP_BYTES, GL_TRUE);
#endif
}
glError = m_context->functions()->glGetError();
VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError);
VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError after glPixelStorei: " << glError);
#endif

QDateTime timestamp = QDateTime::currentDateTime();
m_renderControl->render();
Expand All @@ -353,28 +370,79 @@ void ControllerRenderingEngine::renderFrame() {
0,
m_screenInfo.size.width(),
m_screenInfo.size.height(),
#ifndef QT_OPENGL_ES_2
m_GLDataFormat,
m_GLDataType,
#else
GL_RGBA,
GL_UNSIGNED_BYTE,
#endif
fboImage.bits());
}
glError = m_context->functions()->glGetError();
VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError: " << glError);
VERIFY_OR_TERMINATE(glError == GL_NO_ERROR, "GLError after glReadPixels: " << glError);
VERIFY_OR_DEBUG_ASSERT(!fboImage.isNull()) {
kLogger.warning() << "Screen frame is null!";
}
VERIFY_OR_DEBUG_ASSERT(m_fbo->release()) {
kLogger.debug() << "Couldn't release the FBO.";
}

m_context->doneCurrent();

#ifdef QT_OPENGL_ES_2
fboImage.convertTo(m_screenInfo.pixelFormat);

// OpenGL ES doesn't support extended reverse format (suffixed with _REV) so
// we use QImage function for this
if (static_cast<std::endian>(m_screenInfo.endian) != std::endian::native) {
fboImage.rgbSwap();
}

// OpenGL ES doesn't support explicit endianness (GL_PACK_SWAP_BYTES) se we
// use Qt helper function to convert the pixel buffer. Only 16 and 32 bit
// pixel format are supported currently.
switch (static_cast<std::endian>(m_screenInfo.endian)) {
case std::endian::big:
switch (m_screenInfo.pixelFormat) {
case QImage::Format_RGB16:
qToBigEndian<quint16>(fboImage.bits(), fboImage.sizeInBytes() / 2, fboImage.bits());
break;
case QImage::Format_RGBA8888:
qToBigEndian<quint32>(fboImage.bits(), fboImage.sizeInBytes() / 4, fboImage.bits());
break;
default:
kLogger.critical()
<< "Screen endianness mismatches native endianness, but OpenGL "
"ES does not let us specify a reverse pixel store order. "
"This will likely lead to invalid colors.";
}
break;
case std::endian::little:
switch (m_screenInfo.pixelFormat) {
case QImage::Format_RGB16:
qToLittleEndian<quint16>(fboImage.bits(), fboImage.sizeInBytes(), fboImage.bits());
break;
case QImage::Format_RGBA8888:
qToLittleEndian<quint32>(fboImage.bits(), fboImage.sizeInBytes(), fboImage.bits());
break;
default:
kLogger.critical()
<< "Screen endianness mismatches native endianness, but OpenGL "
"ES does not let us specify a reverse pixel store order. "
"This will likely lead to invalid colors.";
}
break;
}
#endif

#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
fboImage.flip(Qt::Vertical);
#else
fboImage.mirror(false, true);
#endif

emit frameRendered(m_screenInfo, fboImage.copy(), timestamp);

m_context->doneCurrent();
}

bool ControllerRenderingEngine::stop() {
Expand Down
40 changes: 30 additions & 10 deletions src/test/stemcontrolobjecttest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@
#include <memory>

#include "control/pollingcontrolproxy.h"
#include "gtest/gtest.h"
#include "mixxxtest.h"
#include "test/signalpathtest.h"

class StemControlTest : public BaseSignalPathTest {
protected:
#define STEM_FILE QStringLiteral("stems/sin_%1.stem.mp4").arg(QString::fromStdString(GetParam()))

namespace {
const std::vector<std::string> supportedCodecs = {
#if !defined(Q_OS_WIN)
"AAC_256kbps_VBR",
#endif
"ALAC_24bit"};
} // namespace

class StemControlFixture : public BaseSignalPathTest,
public ::testing::WithParamInterface<std::string> {
public:
QString getGroupForStem(QStringView deckGroup, int stemNr) {
DEBUG_ASSERT(deckGroup.endsWith(QChar(']')) && stemNr <= 4);
return deckGroup.chopped(1) + QStringLiteral("_Stem") + QChar('0' + stemNr) + QChar(']');
Expand Down Expand Up @@ -41,7 +53,7 @@ class StemControlTest : public BaseSignalPathTest {
m_pEffectsManager->addStem(stemHandleGroup);
}

const QString kStemFileLocationTest = getTestDir().filePath("stems/test.stem.mp4");
const QString kStemFileLocationTest = getTestDir().filePath(STEM_FILE);
TrackPointer pStemFile(Track::newTemporary(kStemFileLocationTest));

loadTrack(m_pMixerDeck1, pStemFile);
Expand Down Expand Up @@ -142,7 +154,7 @@ class StemControlTest : public BaseSignalPathTest {
std::unique_ptr<PollingControlProxy> m_pStemCount;
};

TEST_F(StemControlTest, StemCount) {
TEST_P(StemControlFixture, StemCount) {
EXPECT_EQ(m_pStemCount->get(), 4.0);

QString kTrackLocationTest = getTestDir().filePath(QStringLiteral("sine-30.wav"));
Expand All @@ -151,14 +163,14 @@ TEST_F(StemControlTest, StemCount) {

EXPECT_EQ(m_pStemCount->get(), 0.0);

kTrackLocationTest = getTestDir().filePath("stems/test.stem.mp4");
kTrackLocationTest = getTestDir().filePath(STEM_FILE);
pTrack = Track::newTemporary(kTrackLocationTest);
loadTrack(m_pMixerDeck1, pTrack);

EXPECT_EQ(m_pStemCount->get(), 4.0);
}

TEST_F(StemControlTest, StemColor) {
TEST_P(StemControlFixture, StemColor) {
EXPECT_EQ(m_pStem1Color->get(), 0xfd << 16 | 0x4a << 8 | 0x4a);
EXPECT_EQ(m_pStem2Color->get(), 0xff << 16 | 0xff << 8 | 0x00);
EXPECT_EQ(m_pStem3Color->get(), 0x00 << 16 | 0xe8 << 8 | 0xe8);
Expand All @@ -173,7 +185,7 @@ TEST_F(StemControlTest, StemColor) {
EXPECT_EQ(m_pStem3Color->get(), -1.0);
EXPECT_EQ(m_pStem4Color->get(), -1.0);

kTrackLocationTest = getTestDir().filePath("stems/test.stem.mp4");
kTrackLocationTest = getTestDir().filePath(STEM_FILE);
pTrack = Track::newTemporary(kTrackLocationTest);
loadTrack(m_pMixerDeck1, pTrack);

Expand All @@ -183,7 +195,7 @@ TEST_F(StemControlTest, StemColor) {
EXPECT_EQ(m_pStem4Color->get(), 0xad << 16 | 0x65 << 8 | 0xff);
}

TEST_F(StemControlTest, DISABLED_Volume) {
TEST_P(StemControlFixture, Volume) {
m_pChannel1->getEngineBuffer()->queueNewPlaypos(
mixxx::audio::FramePos{0}, EngineBuffer::SEEK_STANDARD);
m_pPlay->set(1.0);
Expand Down Expand Up @@ -231,7 +243,7 @@ TEST_F(StemControlTest, DISABLED_Volume) {
QStringLiteral("StemVolumeControlFull"));
}

TEST_F(StemControlTest, VolumeResetOnLoad) {
TEST_P(StemControlFixture, VolumeResetOnLoad) {
m_pStem1Volume->set(0.1);
m_pStem2Volume->set(0.2);
m_pStem3Volume->set(0.3);
Expand Down Expand Up @@ -270,7 +282,7 @@ TEST_F(StemControlTest, VolumeResetOnLoad) {
EXPECT_EQ(m_pStem4Mute->get(), 0.0);
}

TEST_F(StemControlTest, DISABLED_Mute) {
TEST_P(StemControlFixture, Mute) {
m_pChannel1->getEngineBuffer()->queueNewPlaypos(
mixxx::audio::FramePos{0}, EngineBuffer::SEEK_STANDARD);
m_pPlay->set(1.0);
Expand Down Expand Up @@ -316,3 +328,11 @@ TEST_F(StemControlTest, DISABLED_Mute) {
assertBufferMatchesReference(m_pEngineMixer->getMainBuffer(),
QStringLiteral("StemMuteControlFull"));
}

INSTANTIATE_TEST_SUITE_P(
StemControlTest,
StemControlFixture,
::testing::ValuesIn(supportedCodecs),
[](const testing::TestParamInfo<StemControlFixture::ParamType>& info) {
return info.param;
});
Binary file added src/test/stems/sin_ALAC_24bit.stem.mp4
Binary file not shown.
Loading