diff --git a/.gitignore b/.gitignore index 07d51c4ef..d902f2027 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,95 @@ -Makefile +# Qt Creator user files +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.qmlproject.user +*.qmlproject.user.* .qmake.stash -YUView.pro.user -# Exclude files that will be created by visual studio +.qmake.cache + +# Visual Studio files +.vs/ +*.sln +*.vcxproj +*.vcxproj.* +*.filters +*.user *.VC.opendb *.VC.db -*.sln -*.vcxproj* -/.vs -/build -/buildRelease -/GeneratedFiles -/x64 -/deploy -/decoders -.vscode -issues + +# Build directories +build/ +build_*/ +buildRelease/ +debug/ +release/ +bin/ +obj/ +x64/ +GeneratedFiles/ +deploy/ +decoders/ + +# Compiled object files +*.o +*.obj + +# Compiled executable and library files +*.exe +*.dll +*.so +*.so.* +*.dylib +*.a +*.lib + +# Qt generated files +moc_*.cpp +qrc_*.cpp +ui_*.h +Makefile* +*.qrc.depends + +# Core dump +core + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +Thumbs.db .DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Desktop.ini + +# Temporary and log files +*.log +*.tmp +*.temp + +# Issue tracking (local) +issues/ + +# Local development artifacts (not for upstream PR) +temp_docs/ +_upstream_ref/ +HDR_DEBUG_VERIFICATION.md +docs/HDR_*.md +docs/hdr_*.md +docs/Buffer_Pipeline_Investigation_Report.md +docs/PR_HDR_Debug_Plan_Verification.md + +# Compiled shader binaries +# These should be generated during build, not committed +*.qsb +_diff_list.txt +_substantive_diff.txt diff --git a/YUViewApp/YUViewApp.pro b/YUViewApp/YUViewApp.pro index 561012031..2caa8db28 100644 --- a/YUViewApp/YUViewApp.pro +++ b/YUViewApp/YUViewApp.pro @@ -1,8 +1,14 @@ +# Qt module configuration with version compatibility QT += core gui widgets opengl xml concurrent network +# openglwidgets module only exists in Qt6, not in Qt5 +greaterThan(QT_MAJOR_VERSION, 5) { + QT += openglwidgets +} + TARGET = YUView TEMPLATE = app -CONFIG += c++20 +CONFIG += c++17 CONFIG -= debug_and_release SOURCES += $$files(src/*.cpp, false) @@ -78,6 +84,8 @@ win32 { RC_FILE += images/WindowsAppIcon.rc SVNN = $$system("git describe --tags") DEFINES += NOMINMAX + # Windows-specific libraries needed for HDR functionality + LIBS += -ldxgi -luser32 -lole32 } LASTHASH = $$system("git rev-parse HEAD") diff --git a/YUViewApp/src/yuviewapp.cpp b/YUViewApp/src/yuviewapp.cpp index f63628294..4bf65d4ed 100644 --- a/YUViewApp/src/yuviewapp.cpp +++ b/YUViewApp/src/yuviewapp.cpp @@ -1,4 +1,4 @@ -/* This file is part of YUView - The YUV player with advanced analytics toolset +/* This file is part of YUView - The YUV player with advanced analytics toolset * * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY * @@ -31,22 +31,87 @@ */ #include +#include +#include +#include +#include +#include #include #include + int main(int argc, char *argv[]) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // DPI support - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); // DPI support -#endif + // ======================================== + // BASIC OPENGL SETUP - MUST BE FIRST + // ======================================== + + // Set basic OpenGL attributes before any application creation + // Qt6 enables high-DPI scaling by default. QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents,false); QCoreApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents,false); + // ======================================== + // STARTUP-BASED HDR DECISION LOGIC (PRD Requirement 5.1-5.5) + // Simplified branch structure: HDR vs SDR path determined once at startup + // ======================================== + qRegisterMetaType("recacheIndicator"); - YUViewApplication app(argc, argv); + // Set application identity before using QSettings + QCoreApplication::setApplicationName("YUView"); + QCoreApplication::setOrganizationName("Institut für Nachrichtentechnik, RWTH Aachen University"); + QCoreApplication::setOrganizationDomain("ient.rwth-aachen.de"); + + // Read HDR preference from configuration (PRD Requirement 5.2) + QSettings settings; + const bool userWantsHDR = settings.value("Enable10BitDisplay", false).toBool(); + + bool hdrModeEnabled = false; + bool hardwareFallbackOccurred = false; + QString fallbackMessage; + + + // Simplified pure branch structure for HDR/SDR decision + // When userWantsHDR is true: configure 10-bit OpenGL surface format for HDR rendering + // When userWantsHDR is false: use Qt default format, rely on standard QPainter path + // This eliminates the redundant 8-bit -> 10-bit reconfiguration pattern + if (userWantsHDR) { + // HDR PATH: Configure 10-bit OpenGL surface format with Qt 6.8+ native HDR color space + // This format is required for HDR_VideoWindow to render 10-bit content correctly + QSurfaceFormat hdrFormat; + hdrFormat.setProfile(QSurfaceFormat::CoreProfile); + hdrFormat.setVersion(3, 3); + hdrFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + hdrFormat.setSwapInterval(1); + // HDR RGBA16F requires 16-bit per channel for linear light rendering + hdrFormat.setRedBufferSize(16); + hdrFormat.setGreenBufferSize(16); + hdrFormat.setBlueBufferSize(16); + hdrFormat.setAlphaBufferSize(16); + + // Qt 6.8+ Native HDR: Use extended sRGB linear color space for RGBA16F + // The QRhi swap chain will be configured with HDRExtendedSrgbLinear format + // which uses scRGB linear light (values > 1.0 represent HDR content) + // PQ/HLG/Linear OETF will be applied in fragment shader (homework assignment) +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + hdrFormat.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); +#else +#endif + + QSurfaceFormat::setDefaultFormat(hdrFormat); + + hdrModeEnabled = true; + } else { + // SDR PATH: Use Qt default format, no explicit QSurfaceFormat configuration needed + // Standard QPainter rendering path handles 8-bit display automatically + hdrModeEnabled = false; + } + + // Create the main YUView application with HDR decision made + + YUViewApplication app(argc, argv, hdrModeEnabled, hardwareFallbackOccurred, fallbackMessage); return app.returnCode; } diff --git a/YUViewLib/YUViewLib.pro b/YUViewLib/YUViewLib.pro index 06e539a8d..7be8f761c 100644 --- a/YUViewLib/YUViewLib.pro +++ b/YUViewLib/YUViewLib.pro @@ -1,21 +1,56 @@ +# Qt module configuration with version compatibility QT += core gui widgets opengl xml concurrent network +# openglwidgets module only exists in Qt6, not in Qt5 +greaterThan(QT_MAJOR_VERSION, 5) { + QT += openglwidgets + # gui-private required for QRhi (Qt Rendering Hardware Interface) HDR support + QT += gui-private + # shadertools module for runtime shader compilation (optional, for RHI HDR support) + # If not available, pre-compiled .qsb files are needed + qtHaveModule(shadertools): QT += shadertools +} + TEMPLATE = lib CONFIG += staticlib -CONFIG += c++20 +CONFIG += c++17 CONFIG -= debug_and_release CONFIG += object_parallel_to_source +unix { + CONFIG += link_pkgconfig + PKGCONFIG += libpng +} + SOURCES += $$files(src/*.cpp, true) HEADERS += $$files(src/*.h, true) +# HDR files are automatically included by the recursive $$files() above + FORMS += $$files(ui/*.ui, false) INCLUDEPATH += src/ RESOURCES += \ images/images.qrc \ - docs/docs.qrc + docs/docs.qrc \ + resources/shaders/shaders.qrc + +greaterThan(QT_MAJOR_VERSION, 5) { + win32:QSB_BIN = $$shell_path($$[QT_HOST_BINS]/qsb.exe) + else:QSB_BIN = $$shell_path($$[QT_HOST_BINS]/qsb) + + HDR_SHADER_SOURCES = \ + $$files($$PWD/resources/shaders/*.vert, false) \ + $$files($$PWD/resources/shaders/*.frag, false) + + shader_compiler.name = qsb ${QMAKE_FILE_IN} + shader_compiler.input = HDR_SHADER_SOURCES + shader_compiler.output = ${QMAKE_FILE_IN}.qsb + shader_compiler.commands = $$QSB_BIN --glsl \"440,310 es\" --hlsl 50 --msl 12 -o \"${QMAKE_FILE_OUT}\" \"${QMAKE_FILE_IN}\" + shader_compiler.CONFIG += no_link target_predeps + QMAKE_EXTRA_COMPILERS += shader_compiler +} contains(QT_ARCH, x86_32|i386) { warning("You are building for a 32 bit system. This is untested and not supported.") @@ -32,11 +67,18 @@ isEmpty(SVNN) { win32 { DEFINES += NOMINMAX + # Windows-specific libraries for HDR detection + LIBS += -ldxgi -luser32 -lole32 + # Note: WinRT support (-lwindowsapp) temporarily disabled for MinGW compatibility } win32-msvc* { HASHSTRING = '\\"$${LASTHASH}\\"' DEFINES += YUVIEW_HASH=$${HASHSTRING} + CONFIG(release, debug|release) { + QMAKE_CXXFLAGS_RELEASE += -GL -arch:AVX2 -fp:fast -Ob3 + QMAKE_LFLAGS_RELEASE += /LTCG /OPT:REF /OPT:ICF + } } win32-g++ | linux | macx { HASHSTRING = '\\"$${LASTHASH}\\"' diff --git a/YUViewLib/resources/shaders/compile_shaders.bat b/YUViewLib/resources/shaders/compile_shaders.bat new file mode 100644 index 000000000..999a9fc13 --- /dev/null +++ b/YUViewLib/resources/shaders/compile_shaders.bat @@ -0,0 +1,19 @@ +echo Compiling HDR RHI shaders... + +REM RGB vertex shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 -o hdr_rhi_vertex.vert.qsb hdr_rhi_vertex.vert +echo hdr_rhi_vertex.vert -^> hdr_rhi_vertex.vert.qsb + +REM RGB fragment shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 -o hdr_rhi_fragment.frag.qsb hdr_rhi_fragment.frag +echo hdr_rhi_fragment.frag -^> hdr_rhi_fragment.frag.qsb + +REM YUV vertex shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 -o hdr_rhi_yuv_vertex.vert.qsb hdr_rhi_yuv_vertex.vert +echo hdr_rhi_yuv_vertex.vert -^> hdr_rhi_yuv_vertex.vert.qsb + +REM YUV fragment shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 -o hdr_rhi_yuv_fragment.frag.qsb hdr_rhi_yuv_fragment.frag +echo hdr_rhi_yuv_fragment.frag -^> hdr_rhi_yuv_fragment.frag.qsb + +echo Done! All shaders compiled successfully. diff --git a/YUViewLib/resources/shaders/compile_shaders.sh b/YUViewLib/resources/shaders/compile_shaders.sh new file mode 100644 index 000000000..9376adf15 --- /dev/null +++ b/YUViewLib/resources/shaders/compile_shaders.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Compile GLSL shaders to QSB format for QRhi +# Usage: ./compile_shaders.sh +# Requires: qsb tool from Qt 6.6+ installation + +# Check if qsb is available +if ! command -v qsb &> /dev/null; then + echo "Error: qsb tool not found. Please add Qt bin directory to PATH." + echo "Example: export PATH=\$PATH:/path/to/Qt/6.x.x/gcc_64/bin" + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "Compiling HDR RHI shaders..." + +# RGB vertex shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 \ + -o hdr_rhi_vertex.vert.qsb hdr_rhi_vertex.vert +echo " hdr_rhi_vertex.vert -> hdr_rhi_vertex.vert.qsb" + +# RGB fragment shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 \ + -o hdr_rhi_fragment.frag.qsb hdr_rhi_fragment.frag +echo " hdr_rhi_fragment.frag -> hdr_rhi_fragment.frag.qsb" + +# YUV vertex shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 \ + -o hdr_rhi_yuv_vertex.vert.qsb hdr_rhi_yuv_vertex.vert +echo " hdr_rhi_yuv_vertex.vert -> hdr_rhi_yuv_vertex.vert.qsb" + +# YUV fragment shader +qsb --glsl "440,310 es" --hlsl 50 --msl 12 \ + -o hdr_rhi_yuv_fragment.frag.qsb hdr_rhi_yuv_fragment.frag +echo " hdr_rhi_yuv_fragment.frag -> hdr_rhi_yuv_fragment.frag.qsb" + +echo "Done! All shaders compiled successfully." diff --git a/YUViewLib/resources/shaders/hdr_rhi_fragment.frag b/YUViewLib/resources/shaders/hdr_rhi_fragment.frag new file mode 100644 index 000000000..913d232c4 --- /dev/null +++ b/YUViewLib/resources/shaders/hdr_rhi_fragment.frag @@ -0,0 +1,20 @@ +#version 440 + +// HDR RGB Fragment Shader +// +// This shader samples from a pre-converted RGB texture. +// Used as fallback for SDR content or when RGB texture path is needed. +// For HDR10, the texture should already contain PQ-encoded values. + +layout(location = 0) in vec2 TexCoord; +layout(location = 0) out vec4 FragColor; + +// Texture sampler for RGB video texture +layout(binding = 1) uniform sampler2D videoTexture; + +void main() { + // Sample the texture and output directly + // For HDR10 swap chain, values are PQ-encoded + // For SDR, values are in sRGB color space + FragColor = texture(videoTexture, TexCoord); +} diff --git a/YUViewLib/resources/shaders/hdr_rhi_vertex.vert b/YUViewLib/resources/shaders/hdr_rhi_vertex.vert new file mode 100644 index 000000000..4e1d70bd1 --- /dev/null +++ b/YUViewLib/resources/shaders/hdr_rhi_vertex.vert @@ -0,0 +1,25 @@ +#version 440 + +// HDR RGB Vertex Shader +// +// This shader transforms vertex positions and texture coordinates for the fullscreen quad +// used in video rendering. Used for pre-converted RGB textures (SDR fallback path). + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aTexCoord; +layout(location = 0) out vec2 TexCoord; + +// Uniform block for RGB pipeline (simpler than YUV pipeline) +layout(std140, binding = 0) uniform UniformBlock { + mat4 projectionMatrix; + mat4 textureMatrix; +}; + +void main() { + // Transform vertex position by projection matrix + gl_Position = projectionMatrix * vec4(aPos, 0.0, 1.0); + + // Transform texture coordinates by texture matrix and flip Y + TexCoord = (textureMatrix * vec4(aTexCoord, 0.0, 1.0)).xy; + TexCoord.y = 1.0 - TexCoord.y; +} diff --git a/YUViewLib/resources/shaders/hdr_rhi_yuv_fragment.frag b/YUViewLib/resources/shaders/hdr_rhi_yuv_fragment.frag new file mode 100644 index 000000000..ce189e372 --- /dev/null +++ b/YUViewLib/resources/shaders/hdr_rhi_yuv_fragment.frag @@ -0,0 +1,153 @@ +#version 440 + +// ============================================================================ +// HDR YUV->RGB Fragment Shader - High Performance Version +// ============================================================================ +// +// Pipeline for PQ mode: +// YUV -> BT.2020 R'G'B' -> PQ EOTF -> linear BT.2020 +// -> Linear exposure scaling -> gamut + scRGB scale +// +// Exposure uses a linear luminance multiplier: +// display_luminance *= userSelectedNits / EOTF_peak_nits +// with logarithmic UI spacing handled in C++. +// Active for PQ and HLG. Linear mode bypasses exposure. +// +// Output: Linear light in scRGB space (1.0 = 80 nits) +// ============================================================================ + +precision highp float; + +layout(location = 0) in vec2 TexCoord; +layout(location = 0) out vec4 FragColor; + +layout(std140, binding = 0) uniform UniformBlock { + mat4 projectionMatrix; + mat4 textureMatrix; + mat4 colorMatrix; // YCbCr -> R'G'B' (BT.2020) + vec4 offsetVec; // xyz: Y/U/V offsets + vec4 hdrParams; // x: exposureNits, y: displayMax, z: renderMode, w: exposureEnabled +}; + +layout(binding = 1) uniform sampler2D texY; +layout(binding = 2) uniform sampler2D texU; +layout(binding = 3) uniform sampler2D texV; + +// ============================================================================ +// Compile-Time Constants +// ============================================================================ + +// PQ EOTF inverse constants (PQ signal -> linear light) +const float PQ_M1_INV = 6.27739463602; // 16384/2610 +const float PQ_M2_INV = 0.01268331352; // 4096/322944 +const float PQ_C1 = 0.8359375; // 3424/4096 +const float PQ_C2 = 18.8515625; // 2413/128 +const float PQ_C3 = 18.6875; // 2392/128 + +// HLG constants +const float HLG_A_INV = 5.59181630973; // 1/0.17883277 +const float HLG_B = 0.28466892; +const float HLG_C = 0.55991073; +const float HLG_GAMMA_M1 = 0.2; // gamma - 1 for 1000 nits + +// BT.2020 luma coefficients +const vec3 BT2020_LUMA = vec3(0.2627, 0.6780, 0.0593); + +// ============================================================================ +// Pre-merged Gamut Conversion + scRGB Scaling Matrices +// ============================================================================ + +// PQ mode: BT.2020->sRGB * 125.0 (10000 nits / 80 nits) +const mat3 MAT_BT2020_TO_SCRGB_PQ = mat3( + 207.5625, -15.575, -2.275, + -73.4625, 141.625, -12.575, + -9.1, -1.05, 139.8375 +); + +// HLG mode: BT.2020->sRGB * 12.5 (1000 nits / 80 nits) +const mat3 MAT_BT2020_TO_SCRGB_HLG = mat3( + 20.75625, -1.5575, -0.2275, + -7.34625, 14.1625, -1.2575, + -0.91, -0.105, 13.98375 +); + +// Linear mode: BT.2020->sRGB (scale applied separately from uniform) +const mat3 MAT_BT2020_TO_SRGB = mat3( + 1.6605, -0.1246, -0.0182, + -0.5877, 1.1330, -0.1006, + -0.0728, -0.0084, 1.1187 +); + +const int MODE_PQ = 1; +const int MODE_HLG = 2; + +// ============================================================================ +// Main Fragment Shader +// ============================================================================ + +void main() { + // Stage 1: Sample YUV and convert to non-linear RGB (BT.2020) + vec3 yuv = vec3( + texture(texY, TexCoord).r, + texture(texU, TexCoord).r, + texture(texV, TexCoord).r + ) - offsetVec.xyz; + + vec3 rgb = mat3(colorMatrix) * yuv; + + // Stage 2: Mode-specific processing (single uniform-based branch) + int mode = int(hdrParams.z + 0.5); + float exposureNits = hdrParams.x; + bool exposureEnabled = hdrParams.w > 0.5 && exposureNits > 0.0; + + rgb = clamp(rgb, 0.0, 1.0); + + if (mode == MODE_PQ) { + // ==================================================================== + // PQ Mode: EOTF -> linear exposure -> gamut + scRGB scale + // ==================================================================== + + // PQ EOTF: decode PQ signal to linear light [0,1] where 1.0 = 10000 nits + vec3 Nm2 = pow(max(rgb, vec3(1e-10)), vec3(PQ_M2_INV)); + vec3 num = max(Nm2 - PQ_C1, 0.0); + vec3 den = max(PQ_C2 - PQ_C3 * Nm2, 1e-6); + vec3 linear2020 = pow(max(num / den, vec3(1e-10)), vec3(PQ_M1_INV)); + + if (exposureEnabled) { + linear2020 *= exposureNits * 0.0001; + } + + // Gamut conversion (BT.2020->sRGB) + scRGB scale (*125) + rgb = MAT_BT2020_TO_SCRGB_PQ * linear2020; + } + else if (mode == MODE_HLG) { + // ==================================================================== + // HLG Mode: OETF^-1 -> OOTF -> linear exposure -> gamut + scRGB scale + // ==================================================================== + + vec3 low = rgb * rgb * 0.333333333; + vec3 high = (exp((rgb - HLG_C) * HLG_A_INV) + HLG_B) * 0.083333333; + vec3 t = step(0.5, rgb); + vec3 scene = mix(low, high, t); + + float Ys = dot(scene, BT2020_LUMA); + float gain = pow(max(Ys, 1e-6), HLG_GAMMA_M1); + vec3 linear2020 = scene * gain; + + if (exposureEnabled) { + linear2020 *= exposureNits * 0.001; + } + + rgb = MAT_BT2020_TO_SCRGB_HLG * linear2020; + } + else { + // ==================================================================== + // Linear Mode: gamut conversion only (NO tone mapping) + // ==================================================================== + float scale = hdrParams.y * 0.0125; + rgb = MAT_BT2020_TO_SRGB * rgb * scale; + } + + // Stage 3: Output (scRGB can be negative for wide gamut) + FragColor = vec4(clamp(rgb, vec3(-125.0), vec3(125.0)), 1.0); +} diff --git a/YUViewLib/resources/shaders/hdr_rhi_yuv_vertex.vert b/YUViewLib/resources/shaders/hdr_rhi_yuv_vertex.vert new file mode 100644 index 000000000..158c2b77e --- /dev/null +++ b/YUViewLib/resources/shaders/hdr_rhi_yuv_vertex.vert @@ -0,0 +1,31 @@ +#version 440 + +// HDR10 YUV->RGB Vertex Shader +// +// This shader transforms vertex positions and texture coordinates for the fullscreen quad +// used in HDR10 video rendering. It shares the same uniform block as the fragment shader +// but only uses the projection and texture matrices. + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aTexCoord; +layout(location = 0) out vec2 TexCoord; + +// Uniform block layout must match the fragment shader exactly for binding compatibility +// Note: colorMatrix, offsetVec, hdrParams are included for layout matching but not used here +layout(std140, binding = 0) uniform UniformBlock { + mat4 projectionMatrix; + mat4 textureMatrix; + mat4 colorMatrix; // Unused in vertex shader, included for layout matching + vec4 offsetVec; // Unused in vertex shader, included for layout matching + vec4 hdrParams; // Unused in vertex shader, included for layout matching +}; + +void main() { + // Transform vertex position by projection matrix + gl_Position = projectionMatrix * vec4(aPos, 0.0, 1.0); + + // Transform texture coordinates by texture matrix and flip Y + // The Y flip is needed because OpenGL/Vulkan have different coordinate conventions + TexCoord = (textureMatrix * vec4(aTexCoord, 0.0, 1.0)).xy; + TexCoord.y = 1.0 - TexCoord.y; +} diff --git a/YUViewLib/resources/shaders/shaders.qrc b/YUViewLib/resources/shaders/shaders.qrc new file mode 100644 index 000000000..893536ce6 --- /dev/null +++ b/YUViewLib/resources/shaders/shaders.qrc @@ -0,0 +1,8 @@ + + + hdr_rhi_vertex.vert.qsb + hdr_rhi_fragment.frag.qsb + hdr_rhi_yuv_vertex.vert.qsb + hdr_rhi_yuv_fragment.frag.qsb + + diff --git a/YUViewLib/src/common/Functions.cpp b/YUViewLib/src/common/Functions.cpp index 534802bbb..fbde73ebc 100644 --- a/YUViewLib/src/common/Functions.cpp +++ b/YUViewLib/src/common/Functions.cpp @@ -198,32 +198,6 @@ std::string toLower(const std::string_view str) return lowercaseStr; } -std::vector splitString(const std::string_view str, const char delimiter) -{ - std::vector result; - size_t start = 0; - size_t end = str.find(delimiter); - while (end != std::string_view::npos) - { - result.emplace_back(str.substr(start, end - start)); - start = end + 1; - end = str.find(delimiter, start); - } - if (start != str.size()) - result.emplace_back(str.substr(start)); - return result; -} - -std::string_view stripWhitespace(std::string_view str) -{ - str.remove_prefix(std::min(str.find_first_not_of(" "), str.size())); - - const auto lastNonWhitespace = str.find_last_not_of(" "); - if (lastNonWhitespace != std::string_view::npos) - str.remove_suffix(str.size() - lastNonWhitespace - 1); - return str; -} - ByteVector readData(std::istream &istream, const size_t nrBytes) { ByteVector data; @@ -237,12 +211,11 @@ ByteVector readData(std::istream &istream, const size_t nrBytes) std::optional toUnsigned(const std::string_view text) { unsigned value{}; - const auto endPointer = text.data() + text.size(); - const auto result = std::from_chars(text.data(), endPointer, value); + const auto result = std::from_chars(text.data(), text.data() + text.size(), value); if (result.ec != std::errc()) return {}; - const auto allCharactersParsed = (result.ptr == endPointer); + const auto allCharactersParsed = (result.ptr == &(*text.end())); if (!allCharactersParsed) return {}; @@ -252,12 +225,11 @@ std::optional toUnsigned(const std::string_view text) std::optional toInt(const std::string_view text) { int value{}; - const auto endPointer = text.data() + text.size(); - const auto result = std::from_chars(text.data(), endPointer, value); + const auto result = std::from_chars(text.data(), text.data() + text.size(), value); if (result.ec != std::errc()) return {}; - const auto allCharactersParsed = (result.ptr == endPointer); + const auto allCharactersParsed = (result.ptr == &(*text.end())); if (!allCharactersParsed) return {}; diff --git a/YUViewLib/src/common/Functions.h b/YUViewLib/src/common/Functions.h index 39aad04af..546278d4c 100644 --- a/YUViewLib/src/common/Functions.h +++ b/YUViewLib/src/common/Functions.h @@ -36,8 +36,6 @@ #include #include -#include -#include namespace functions { @@ -79,12 +77,8 @@ template QStringList toQStringList(const std::array toInt(const std::string_view str); -std::vector splitString(const std::string_view str, const char delimiter); -std::string_view stripWhitespace(std::string_view str); - -ByteVector readData(std::istream &istream, const size_t nrBytes); +std::string toLower(const std::string_view str); +ByteVector readData(std::istream &istream, const size_t nrBytes); template unsigned clipToUnsigned(T val) { diff --git a/YUViewLib/src/common/FunctionsGui.cpp b/YUViewLib/src/common/FunctionsGui.cpp index e2fea6dc0..e2c973af3 100644 --- a/YUViewLib/src/common/FunctionsGui.cpp +++ b/YUViewLib/src/common/FunctionsGui.cpp @@ -108,6 +108,40 @@ QIcon functionsGui::convertIcon(QString iconPath) return outIcon; } +QPixmap functionsGui::convertPixmap(QString pixmapPath) +{ + QSettings settings; + QString themeName = settings.value("Theme", "Default").toString(); + + // Get the active and inactive colors + QStringList colors = functions::getThemeColors(themeName); + QRgb activeColor; + if (colors.size() == 4) + { + QColor active(colors[1]); + activeColor = active.rgb(); + } + else + activeColor = qRgb(0, 0, 0); + + QImage input(pixmapPath); + + QImage active(input.size(), input.format()); + for (int y = 0; y < input.height(); y++) + { + for (int x = 0; x < input.width(); x++) + { + QRgb in = input.pixel(x, y); + if (qAlpha(in) != 0) + active.setPixel(x, y, activeColor); + else + active.setPixel(x, y, in); + } + } + + return QPixmap::fromImage(active); +} + QString functionsGui::pixelFormatToString(QImage::Format f) { if (f == QImage::Format_Invalid) diff --git a/YUViewLib/src/common/FunctionsGui.h b/YUViewLib/src/common/FunctionsGui.h index 73f2bd035..347c5d566 100644 --- a/YUViewLib/src/common/FunctionsGui.h +++ b/YUViewLib/src/common/FunctionsGui.h @@ -108,5 +108,6 @@ void setupUi(void *ui, void (*setupUi)(void *ui, QWidget *widget)); // Return the icon/pixmap from the given file path (inverted if necessary) QIcon convertIcon(QString iconPath); +QPixmap convertPixmap(QString pixmapPath); } // namespace functionsGui diff --git a/YUViewLib/src/common/Typedef.h b/YUViewLib/src/common/Typedef.h index 155e6f11d..a1fd7e3ba 100644 --- a/YUViewLib/src/common/Typedef.h +++ b/YUViewLib/src/common/Typedef.h @@ -62,21 +62,21 @@ // Convenience macro definitions which can be used in if clauses: // if (is_Q_OS_MAC) ... #ifdef Q_OS_MAC -constexpr bool is_Q_OS_MAC = true; +const bool is_Q_OS_MAC = true; #else -constexpr bool is_Q_OS_MAC = false; +const bool is_Q_OS_MAC = false; #endif #ifdef Q_OS_WIN -constexpr bool is_Q_OS_WIN = true; +const bool is_Q_OS_WIN = true; #else -constexpr bool is_Q_OS_WIN = false; +const bool is_Q_OS_WIN = false; #endif #ifdef Q_OS_LINUX -constexpr bool is_Q_OS_LINUX = true; +const bool is_Q_OS_LINUX = true; #else -constexpr bool is_Q_OS_LINUX = false; +const bool is_Q_OS_LINUX = false; #endif // Set this to one to enable the code that handles single instances. @@ -256,3 +256,50 @@ enum recacheIndicator // useless in the cache. }; Q_DECLARE_METATYPE(recacheIndicator) + +#if QT_VERSION <= 0x050700 +// copied from newer version of qglobal.h +template struct QNonConstOverload +{ + template + Q_DECL_CONSTEXPR auto operator()(R (T::*ptr)(Args...)) const Q_DECL_NOTHROW->decltype(ptr) + { + return ptr; + } + template + static Q_DECL_CONSTEXPR auto of(R (T::*ptr)(Args...)) Q_DECL_NOTHROW -> decltype(ptr) + { + return ptr; + } +}; +template struct QConstOverload +{ + template + Q_DECL_CONSTEXPR auto operator()(R (T::*ptr)(Args...) const) const Q_DECL_NOTHROW->decltype(ptr) + { + return ptr; + } + template + static Q_DECL_CONSTEXPR auto of(R (T::*ptr)(Args...) const) Q_DECL_NOTHROW -> decltype(ptr) + { + return ptr; + } +}; +template struct QOverload : QConstOverload, QNonConstOverload +{ + using QConstOverload::of; + using QConstOverload::operator(); + using QNonConstOverload::of; + using QNonConstOverload::operator(); + template + Q_DECL_CONSTEXPR auto operator()(R (*ptr)(Args...)) const Q_DECL_NOTHROW->decltype(ptr) + { + return ptr; + } + template + static Q_DECL_CONSTEXPR auto of(R (*ptr)(Args...)) Q_DECL_NOTHROW -> decltype(ptr) + { + return ptr; + } +}; +#endif diff --git a/YUViewLib/src/dataSource/DataSourceLocalFile.cpp b/YUViewLib/src/dataSource/DataSourceLocalFile.cpp index 34becf6a1..af9c3d73c 100644 --- a/YUViewLib/src/dataSource/DataSourceLocalFile.cpp +++ b/YUViewLib/src/dataSource/DataSourceLocalFile.cpp @@ -65,6 +65,11 @@ DataSourceLocalFile::DataSourceLocalFile(const std::filesystem::path &filePath) this->lastWriteTime = getLastWriteTime(this->filePath); } +DataSourceLocalFile::~DataSourceLocalFile() +{ + cleanupFileHandlePool(); +} + std::vector DataSourceLocalFile::getInfoList() const { if (!this->isOk()) @@ -183,4 +188,79 @@ std::optional DataSourceLocalFile::getFileSize() const return this->filePath; } +std::int64_t DataSourceLocalFile::readAt(ByteVector &buffer, + const std::int64_t position, + const std::int64_t nrBytes) +{ + // Get thread-local file handle for lock-free parallel reading + std::ifstream* threadFile = getThreadLocalFileHandle(); + if (!threadFile || !threadFile->is_open()) + { + // Fallback to serialized read if thread-local handle unavailable + const std::lock_guard readLock(this->readingMutex); + if (!this->seek(position)) + return 0; + return this->read(buffer, nrBytes); + } + + // Seek to position (no mutex needed - each thread has its own handle) + threadFile->clear(); + threadFile->seekg(static_cast(position)); + if (threadFile->fail()) + return 0; + + // Resize buffer if needed + const auto size = static_cast(nrBytes); + if (static_cast(buffer.size()) < nrBytes) + buffer.resize(size); + + // Read data directly without mutex contention + threadFile->read(reinterpret_cast(buffer.data()), size); + const auto bytesRead = threadFile->gcount(); + buffer.resize(bytesRead); + + return static_cast(bytesRead); +} + +std::ifstream* DataSourceLocalFile::getThreadLocalFileHandle() +{ + const auto threadId = std::this_thread::get_id(); + + // Fast path: check if handle already exists (with minimal lock time) + { + std::lock_guard lock(poolMutex); + auto it = fileHandlePool.find(threadId); + if (it != fileHandlePool.end()) + return it->second.get(); + } + + // Slow path: create new handle for this thread + auto newHandle = std::make_unique(); + newHandle->open(this->filePath.string(), std::ios_base::in | std::ios_base::binary); + + if (!newHandle->is_open()) + return nullptr; + + std::ifstream* rawPtr = newHandle.get(); + + // Insert into pool + { + std::lock_guard lock(poolMutex); + fileHandlePool[threadId] = std::move(newHandle); + } + + return rawPtr; +} + +void DataSourceLocalFile::cleanupFileHandlePool() +{ + std::lock_guard lock(poolMutex); + for (auto& [threadId, handle] : fileHandlePool) + { + if (handle && handle->is_open()) + handle->close(); + } + fileHandlePool.clear(); +} + } // namespace datasource diff --git a/YUViewLib/src/dataSource/DataSourceLocalFile.h b/YUViewLib/src/dataSource/DataSourceLocalFile.h index 9a0185815..11269eeeb 100644 --- a/YUViewLib/src/dataSource/DataSourceLocalFile.h +++ b/YUViewLib/src/dataSource/DataSourceLocalFile.h @@ -36,16 +36,37 @@ #include #include +#include #include +#include +#include namespace datasource { +/** + * @brief Local file data source with parallel reading support + * + * This class provides file I/O for local files with optimized parallel reading + * capability for multi-threaded caching scenarios. + * + * PERFORMANCE OPTIMIZATION: + * For large video files (>10GB), traditional serialized I/O causes severe + * bottlenecks when multiple caching threads try to read simultaneously. + * This implementation uses a pool of file handles (one per thread) to enable + * true parallel I/O operations, significantly improving cache loading speed. + * + * Key features: + * - Thread-local file handles for lock-free parallel reads + * - Automatic handle pooling and cleanup + * - Backward compatible with single-threaded sequential reads + */ class DataSourceLocalFile : public IDataSource { public: DataSourceLocalFile() = delete; DataSourceLocalFile(const std::filesystem::path &filePath); + ~DataSourceLocalFile(); [[nodiscard]] std::vector getInfoList() const override; [[nodiscard]] bool atEnd() const override; @@ -59,6 +80,28 @@ class DataSourceLocalFile : public IDataSource [[nodiscard]] bool seek(const std::int64_t pos) override; [[nodiscard]] std::int64_t read(ByteVector &buffer, const std::int64_t nrBytes) override; + /** + * @brief Read data at specific position with parallel I/O support + * + * Uses thread-local file handles to enable concurrent reads from multiple + * caching threads without mutex contention. Each thread gets its own file + * handle from the pool, eliminating serialization overhead. + * + * @param buffer Output buffer (will be resized if needed) + * @param position Byte offset to start reading from + * @param nrBytes Number of bytes to read + * @return Number of bytes actually read + */ + [[nodiscard]] std::int64_t readAt(ByteVector &buffer, + const std::int64_t position, + const std::int64_t nrBytes) override; + + /** + * @brief Check if parallel reading is supported + * @return Always true for local files + */ + [[nodiscard]] bool supportsParallelRead() const override { return true; } + [[nodiscard]] std::optional getFileSize() const; [[nodiscard]] std::filesystem::path getFilePath() const; @@ -67,10 +110,31 @@ class DataSourceLocalFile : public IDataSource std::optional lastWriteTime{}; bool isFileOpened{}; + // Primary file handle for sequential reads (seek+read pattern) std::ifstream file{}; std::int64_t filePosition{}; - std::mutex readingMutex; + +private: + /** + * @brief Get or create a file handle for the current thread + * + * File handles are cached per-thread to avoid repeated open/close overhead. + * Each thread gets its own independent file handle for lock-free I/O. + * + * @return Pointer to thread-local file stream, or nullptr on error + */ + std::ifstream* getThreadLocalFileHandle(); + + /** + * @brief Clean up all pooled file handles + */ + void cleanupFileHandlePool(); + + // Thread-local file handle pool for parallel reads + // Key: thread ID, Value: unique_ptr to ifstream + std::unordered_map> fileHandlePool; + std::mutex poolMutex; // Only protects pool access, not reads }; } // namespace datasource diff --git a/YUViewLib/src/dataSource/IDataSource.h b/YUViewLib/src/dataSource/IDataSource.h index 322657ca9..5a4270bc7 100644 --- a/YUViewLib/src/dataSource/IDataSource.h +++ b/YUViewLib/src/dataSource/IDataSource.h @@ -58,6 +58,40 @@ class IDataSource [[nodiscard]] virtual bool seek(const std::int64_t pos) = 0; [[nodiscard]] virtual std::int64_t read(ByteVector &buffer, const std::int64_t nrBytes) = 0; + + /** + * @brief Read data at specific position without mutex serialization + * + * This method supports parallel reading from multiple threads by using + * separate file handles per thread. Unlike seek()+read() which are serialized + * by a mutex, this method can be called concurrently from multiple threads. + * + * Performance improvement: For large video files (>10GB), this enables true + * parallel I/O when caching frames, significantly reducing buffer loading time. + * + * Default implementation falls back to seek()+read() for compatibility. + * + * @param buffer Output buffer for read data + * @param position File position to start reading from + * @param nrBytes Number of bytes to read + * @return Number of bytes actually read + */ + [[nodiscard]] virtual std::int64_t readAt(ByteVector &buffer, + const std::int64_t position, + const std::int64_t nrBytes) + { + // Default implementation: fallback to serialized seek+read + if (!this->seek(position)) + return 0; + return this->read(buffer, nrBytes); + } + + /** + * @brief Check if parallel reading is supported + * + * @return true if readAt() uses true parallel I/O (no global mutex) + */ + [[nodiscard]] virtual bool supportsParallelRead() const { return false; } }; } // namespace datasource diff --git a/YUViewLib/src/decoder/decoderBase.cpp b/YUViewLib/src/decoder/decoderBase.cpp index 22688152e..43b3d4009 100644 --- a/YUViewLib/src/decoder/decoderBase.cpp +++ b/YUViewLib/src/decoder/decoderBase.cpp @@ -40,27 +40,7 @@ namespace decoder // Debug the decoder ( 0:off 1:interactive decoder only 2:caching decoder only 3:both) #define DECODERBASE_DEBUG_OUTPUT 0 -#if DECODERBASE_DEBUG_OUTPUT && !NDEBUG -#include -#if DECODERBASE_DEBUG_OUTPUT == 1 -#define DEBUG_HEVCDECODERBASE \ - if (!isCachingDecoder) \ - qDebug -#elif DECODERBASE_DEBUG_OUTPUT == 2 -#define DEBUG_HEVCDECODERBASE \ - if (isCachingDecoder) \ - qDebug -#elif DECODERBASE_DEBUG_OUTPUT == 3 -#define DEBUG_HEVCDECODERBASE \ - if (isCachingDecoder) \ - qDebug("c:"); \ - else \ - qDebug("i:"); \ - qDebug -#endif -#else #define DEBUG_DECODERBASE(fmt, ...) ((void)0) -#endif decoderBase::decoderBase(bool cachingDecoder) { diff --git a/YUViewLib/src/decoder/decoderDav1d.cpp b/YUViewLib/src/decoder/decoderDav1d.cpp index 7792a9625..224a298f1 100644 --- a/YUViewLib/src/decoder/decoderDav1d.cpp +++ b/YUViewLib/src/decoder/decoderDav1d.cpp @@ -48,27 +48,7 @@ using Subsampling = video::yuv::Subsampling; // Debug the decoder (0:off 1:interactive decoder only 2:caching decoder only 3:both) #define DECODERDAV1D_DEBUG_OUTPUT 0 -#if DECODERDAV1D_DEBUG_OUTPUT && !NDEBUG -#include -#if DECODERDAV1D_DEBUG_OUTPUT == 1 -#define DEBUG_DAV1D \ - if (!isCachingDecoder) \ - qDebug -#elif DECODERDAV1D_DEBUG_OUTPUT == 2 -#define DEBUG_DAV1D \ - if (isCachingDecoder) \ - qDebug -#elif DECODERDAV1D_DEBUG_OUTPUT == 3 -#define DEBUG_DAV1D \ - if (isCachingDecoder) \ - qDebug("c:"); \ - else \ - qDebug("i:"); \ - qDebug -#endif -#else #define DEBUG_DAV1D(fmt, ...) ((void)0) -#endif namespace { diff --git a/YUViewLib/src/decoder/decoderFFmpeg.cpp b/YUViewLib/src/decoder/decoderFFmpeg.cpp index 622e56ac6..f6520b213 100644 --- a/YUViewLib/src/decoder/decoderFFmpeg.cpp +++ b/YUViewLib/src/decoder/decoderFFmpeg.cpp @@ -37,7 +37,6 @@ #define DECODERFFMPEG_DEBUG_OUTPUT 0 #if DECODERFFMPEG_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_FFMPEG(f) qDebug() << f #else #define DEBUG_FFMPEG(f) ((void)0) #endif @@ -204,14 +203,14 @@ void decoderFFmpeg::copyCurImageToBuffer() for (unsigned plane = 0; plane < pixFmt.getNrPlanes(); plane++) { const auto component = - (plane == 0) ? video::yuv::Component::Luma : video::yuv::Component::Chroma; + (plane == 0) ? video::yuv::Component::Luma : video::yuv::Component::Chroma; auto src = frame.getData(plane); const auto srcLinesize = frame.getLineSize(plane); auto dst = this->currentOutputBuffer.data(); if (plane > 0) dst += (nrBytesY + (plane - 1) * nrBytesC); const auto dstLinesize = - this->frameSize.width / pixFmt.getSubsamplingHor(component) * nrBytesPerSample; + this->frameSize.width / pixFmt.getSubsamplingHor(component) * nrBytesPerSample; const auto height = this->frameSize.height / pixFmt.getSubsamplingVer(component); for (unsigned y = 0; y < height; y++) { @@ -224,10 +223,10 @@ void decoderFFmpeg::copyCurImageToBuffer() else if (this->rawFormat == video::RawFormat::RGB) { const auto pixFmt = this->getRGBPixelFormat(); - const auto nrBytesPerSample = pixFmt.getBitsPerComponent() <= 8 ? 1 : 2; + const auto nrBytesPerSample = pixFmt.getBitsPerSample() <= 8 ? 1 : 2; const auto nrBytesPerComponent = - this->frameSize.width * this->frameSize.height * nrBytesPerSample; - const auto nrBytes = nrBytesPerComponent * pixFmt.getNrChannels(); + this->frameSize.width * this->frameSize.height * nrBytesPerSample; + const auto nrBytes = nrBytesPerComponent * pixFmt.nrChannels(); // Is the output big enough? if (auto c = functions::clipToUnsigned(this->currentOutputBuffer.capacity()); c < nrBytes) @@ -257,7 +256,7 @@ void decoderFFmpeg::copyCurImageToBuffer() else { // We only need to iterate over the image once and copy all values per line at once (RGB(A)) - const auto wDst = this->frameSize.width * nrBytesPerSample * pixFmt.getNrChannels(); + const auto wDst = this->frameSize.width * nrBytesPerSample * pixFmt.nrChannels(); auto src = frame.getData(0); const auto srcLinesize = frame.getLineSize(0); for (unsigned y = 0; y < hDst; y++) @@ -291,9 +290,9 @@ void decoderFFmpeg::cacheCurStatistics() const int16_t mvY = mvs.dst_y - mvs.src_y; this->statisticsData->at(mvs.source < 0 ? 0 : 1) - .addBlockValue(blockX, blockY, mvs.w, mvs.h, (int)mvs.source); + .addBlockValue(blockX, blockY, mvs.w, mvs.h, (int)mvs.source); this->statisticsData->at(mvs.source < 0 ? 2 : 3) - .addBlockVector(blockX, blockY, mvs.w, mvs.h, mvX, mvY); + .addBlockVector(blockX, blockY, mvs.w, mvs.h, mvX, mvY); } } } @@ -339,7 +338,7 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) if (this->flushing) { DEBUG_FFMPEG( - "decoderFFmpeg::pushAVPacket: Error no new packets should be pushed in flushing mode."); + "decoderFFmpeg::pushAVPacket: Error no new packets should be pushed in flushing mode."); return false; } @@ -351,7 +350,7 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) #if DECODERFFMPEG_DEBUG_OUTPUT { QString meaning = - QString("decoderFFmpeg::pushAVPacket: Error sending packet - err %1").arg(retPush); + QString("decoderFFmpeg::pushAVPacket: Error sending packet - err %1").arg(retPush); if (retPush == -1094995529) meaning += " INDA"; // Log the first bytes @@ -364,7 +363,6 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) meaning += QString(" %1").arg(b, 2, 16); } meaning += ")"; - qDebug() << meaning; } #endif this->setError(QStringLiteral("Error sending packet (avcodec_send_packet)")); @@ -379,7 +377,7 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) { // Enough data pushed. Decode and retrieve frames now. DEBUG_FFMPEG( - "decoderFFmpeg::pushAVPacket: Enough data pushed. Decode and retrieve frames now."); + "decoderFFmpeg::pushAVPacket: Enough data pushed. Decode and retrieve frames now."); this->decoderState = DecoderState::RetrieveFrames; return false; } @@ -430,7 +428,7 @@ bool decoderFFmpeg::decodeFrame() void decoderFFmpeg::fillStatisticList(stats::StatisticsData &statisticsData) const { auto sourceColorMapper = - stats::color::ColorMapper({-2, 2}, stats::color::PredefinedType::Col3_bblg); + stats::color::ColorMapper({-2, 2}, stats::color::PredefinedType::Col3_bblg); statisticsData.addStatType(stats::StatisticsType(0, "Source -", sourceColorMapper)); statisticsData.addStatType(stats::StatisticsType(1, "Source +", sourceColorMapper)); @@ -454,7 +452,7 @@ bool decoderFFmpeg::createDecoder(FFmpeg::AVCodecIDWrapper codecID, this->decCtx = this->ff.allocDecoder(this->videoCodec); if (!this->decCtx) return this->setErrorB( - QStringLiteral("Could not allocate video decoder (avcodec_alloc_context3)")); + QStringLiteral("Could not allocate video decoder (avcodec_alloc_context3)")); if (codecpar && !this->ff.configureDecoder(decCtx, codecpar)) return this->setErrorB(QStringLiteral("Unable to configure decoder from codecpar")); @@ -474,13 +472,13 @@ bool decoderFFmpeg::createDecoder(FFmpeg::AVCodecIDWrapper codecID, int ret = this->ff.dictSet(opts, "flags2", "+export_mvs", 0); if (ret < 0) return this->setErrorB( - QStringLiteral("Could not request motion vector retrieval. Return code %1").arg(ret)); + QStringLiteral("Could not request motion vector retrieval. Return code %1").arg(ret)); // Open codec ret = this->ff.avcodecOpen2(decCtx, videoCodec, opts); if (ret < 0) return this->setErrorB( - QStringLiteral("Could not open the video codec (avcodec_open2). Return code %1.").arg(ret)); + QStringLiteral("Could not open the video codec (avcodec_open2). Return code %1.").arg(ret)); this->frame = ff.allocateFrame(); if (!this->frame) diff --git a/YUViewLib/src/decoder/decoderHM.cpp b/YUViewLib/src/decoder/decoderHM.cpp index 4f9444e7f..e61a13391 100644 --- a/YUViewLib/src/decoder/decoderHM.cpp +++ b/YUViewLib/src/decoder/decoderHM.cpp @@ -45,27 +45,7 @@ namespace decoder // Debug the decoder ( 0:off 1:interactive decoder only 2:caching decoder only 3:both) #define DECODERHM_DEBUG_OUTPUT 0 -#if DECODERHM_DEBUG_OUTPUT && !NDEBUG -#include -#if DECODERHM_DEBUG_OUTPUT == 1 -#define DEBUG_DECHM \ - if (!isCachingDecoder) \ - qDebug -#elif DECODERHM_DEBUG_OUTPUT == 2 -#define DEBUG_DECHM \ - if (isCachingDecoder) \ - qDebug -#elif DECODERHM_DEBUG_OUTPUT == 3 -#define DEBUG_DECHM \ - if (isCachingDecoder) \ - qDebug("c:"); \ - else \ - qDebug("i:"); \ - qDebug -#endif -#else #define DEBUG_DECHM(fmt, ...) ((void)0) -#endif // Restrict is basically a promise to the compiler that for the scope of the pointer, the target of // the pointer will only be accessed through that pointer (and pointers copied from it). diff --git a/YUViewLib/src/decoder/decoderLibde265.cpp b/YUViewLib/src/decoder/decoderLibde265.cpp index e87a82ef1..fdf024b43 100644 --- a/YUViewLib/src/decoder/decoderLibde265.cpp +++ b/YUViewLib/src/decoder/decoderLibde265.cpp @@ -45,27 +45,7 @@ namespace decoder // Debug the decoder ( 0:off 1:interactive decoder only 2:caching decoder only 3:both) #define DECODERLIBD265_DEBUG_OUTPUT 0 -#if DECODERLIBD265_DEBUG_OUTPUT && !NDEBUG -#include -#if DECODERLIBD265_DEBUG_OUTPUT == 1 -#define DEBUG_LIBDE265 \ - if (!isCachingDecoder) \ - qDebug -#elif DECODERLIBD265_DEBUG_OUTPUT == 2 -#define DEBUG_LIBDE265 \ - if (isCachingDecoder) \ - qDebug -#elif DECODERLIBD265_DEBUG_OUTPUT == 3 -#define DEBUG_LIBDE265 \ - if (isCachingDecoder) \ - qDebug("c:"); \ - else \ - qDebug("i:"); \ - qDebug -#endif -#else #define DEBUG_LIBDE265(fmt, ...) ((void)0) -#endif using Subsampling = video::yuv::Subsampling; diff --git a/YUViewLib/src/decoder/decoderVTM.cpp b/YUViewLib/src/decoder/decoderVTM.cpp index 8d4eb4285..4de5c8217 100644 --- a/YUViewLib/src/decoder/decoderVTM.cpp +++ b/YUViewLib/src/decoder/decoderVTM.cpp @@ -45,27 +45,7 @@ namespace decoder // Debug the decoder ( 0:off 1:interactive decoder only 2:caching decoder only 3:both) #define DECODERVTM_DEBUG_OUTPUT 0 -#if DECODERVTM_DEBUG_OUTPUT && !NDEBUG -#include -#if DECODERVTM_DEBUG_OUTPUT == 1 -#define DEBUG_DECVTM \ - if (!isCachingDecoder) \ - qDebug -#elif DECODERVTM_DEBUG_OUTPUT == 2 -#define DEBUG_DECVTM \ - if (isCachingDecoder) \ - qDebug -#elif DECODERVTM_DEBUG_OUTPUT == 3 -#define DEBUG_DECVTM \ - if (isCachingDecoder) \ - qDebug("c:"); \ - else \ - qDebug("i:"); \ - qDebug -#endif -#else #define DEBUG_DECVTM(fmt, ...) ((void)0) -#endif // Restrict is basically a promise to the compiler that for the scope of the pointer, the target of // the pointer will only be accessed through that pointer (and pointers copied from it). diff --git a/YUViewLib/src/decoder/decoderVVDec.cpp b/YUViewLib/src/decoder/decoderVVDec.cpp index 78b0fd8dd..4e0bbfd5f 100644 --- a/YUViewLib/src/decoder/decoderVVDec.cpp +++ b/YUViewLib/src/decoder/decoderVVDec.cpp @@ -41,27 +41,7 @@ // Debug the decoder ( 0:off 1:interactive decoder only 2:caching decoder only 3:both) #define decoderVVDec_DEBUG_OUTPUT 0 -#if decoderVVDec_DEBUG_OUTPUT && !NDEBUG -#include -#if decoderVVDec_DEBUG_OUTPUT == 1 -#define DEBUG_vvdec \ - if (!isCachingDecoder) \ - qDebug -#elif decoderVVDec_DEBUG_OUTPUT == 2 -#define DEBUG_vvdec \ - if (isCachingDecoder) \ - qDebug -#elif decoderVVDec_DEBUG_OUTPUT == 3 -#define DEBUG_vvdec \ - if (isCachingDecoder) \ - qDebug("c:"); \ - else \ - qDebug("i:"); \ - qDebug -#endif -#else #define DEBUG_vvdec(fmt, ...) ((void)0) -#endif // Restrict is basically a promise to the compiler that for the scope of the pointer, the target of // the pointer will only be accessed through that pointer (and pointers copied from it). @@ -98,7 +78,6 @@ void loggingCallback(void *ptr, int level, const char *msg, va_list list) #if decoderVVDec_DEBUG_OUTPUT && !NDEBUG char buf[200]; vsnprintf(buf, 200, msg, list); - qDebug() << "decoderVVDec::decoderVVDec vvdeclog(" << level << "): " << buf; #endif } diff --git a/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.cpp b/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.cpp index 36c8f82b4..e077a62ee 100644 --- a/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.cpp +++ b/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.cpp @@ -380,7 +380,7 @@ bool AVPixFmtDescriptorWrapper::Flags::operator==( this->floatValues == other.floatValues; } -bool AVPixFmtDescriptorWrapper::operator==(const AVPixFmtDescriptorWrapper &other) const +bool AVPixFmtDescriptorWrapper::operator==(const AVPixFmtDescriptorWrapper &other) { if (this->nb_components != other.nb_components) return false; diff --git a/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.h b/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.h index 3277e5888..d8642a3a3 100644 --- a/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.h +++ b/YUViewLib/src/ffmpeg/AVPixFmtDescriptorWrapper.h @@ -103,7 +103,7 @@ class AVPixFmtDescriptorWrapper QString aliases{}; AVComponentDescriptor comp[4]; - bool operator==(const AVPixFmtDescriptorWrapper &a) const; + bool operator==(const AVPixFmtDescriptorWrapper &a); }; } // namespace FFmpeg diff --git a/YUViewLib/src/filesource/FileSource.cpp b/YUViewLib/src/filesource/FileSource.cpp index e5df613a9..b2bbc5881 100644 --- a/YUViewLib/src/filesource/FileSource.cpp +++ b/YUViewLib/src/filesource/FileSource.cpp @@ -184,3 +184,75 @@ void FileSource::clearFileCache() this->srcFile.open(QIODevice::ReadOnly); #endif } + +FileSource::~FileSource() +{ + cleanupFileHandlePool(); +} + +int64_t FileSource::readBytesParallel(QByteArray &targetBuffer, int64_t startPos, int64_t nrBytes) +{ + // Get thread-local file handle for lock-free parallel reading + QFile* threadFile = getThreadLocalFileHandle(); + if (!threadFile || !threadFile->isOpen()) + { + // Fallback to serialized read if thread-local handle unavailable + return this->readBytes(targetBuffer, startPos, nrBytes); + } + + // Resize buffer if needed + if (targetBuffer.size() < nrBytes) + targetBuffer.resize(nrBytes); + +#if FILESOURCE_DEBUG_SIMULATESLOWLOADING && !NDEBUG + QThread::msleep(50); +#endif + + // Seek and read without mutex contention - each thread has its own handle + if (!threadFile->seek(startPos)) + return 0; + + return threadFile->read(targetBuffer.data(), nrBytes); +} + +QFile* FileSource::getThreadLocalFileHandle() +{ + // Use Qt's thread handle as the key + const Qt::HANDLE threadId = QThread::currentThreadId(); + + // Fast path: check if handle already exists (with minimal lock time) + { + QMutexLocker lock(&poolMutex); + auto it = fileHandlePool.find(threadId); + if (it != fileHandlePool.end()) + return it->second.get(); + } + + // Slow path: create new handle for this thread + auto newHandle = std::make_unique(); + newHandle->setFileName(QString::fromStdString(this->fullFilePath.string())); + + if (!newHandle->open(QIODevice::ReadOnly)) + return nullptr; + + QFile* rawPtr = newHandle.get(); + + // Insert into pool + { + QMutexLocker lock(&poolMutex); + fileHandlePool[threadId] = std::move(newHandle); + } + + return rawPtr; +} + +void FileSource::cleanupFileHandlePool() +{ + QMutexLocker lock(&poolMutex); + for (auto& [threadId, handle] : fileHandlePool) + { + if (handle && handle->isOpen()) + handle->close(); + } + fileHandlePool.clear(); +} diff --git a/YUViewLib/src/filesource/FileSource.h b/YUViewLib/src/filesource/FileSource.h index c9b89875d..de7ed18d9 100644 --- a/YUViewLib/src/filesource/FileSource.h +++ b/YUViewLib/src/filesource/FileSource.h @@ -38,12 +38,15 @@ #include #include #include +#include #include #include #include #include +#include +#include enum class InputFormat { @@ -94,9 +97,36 @@ class FileSource : public QObject // Resize the QByteArray if necessary. Return how many bytes were read. int64_t readBytes(QByteArray &targetBuffer, int64_t startPos, int64_t nrBytes); + /** + * @brief Read bytes at position using parallel I/O + * + * This method enables true parallel file I/O by using thread-local file handles. + * For large video files (>10GB), this significantly improves cache loading + * performance when multiple threads read simultaneously. + * + * PERFORMANCE IMPROVEMENT: + * - Traditional readBytes: All reads serialized by single mutex + * - readBytesParallel: Each thread gets own file handle, no mutex contention + * + * @param targetBuffer Output buffer (will be resized if needed) + * @param startPos Byte offset to start reading from + * @param nrBytes Number of bytes to read + * @return Number of bytes actually read + */ + int64_t readBytesParallel(QByteArray &targetBuffer, int64_t startPos, int64_t nrBytes); + + /** + * @brief Check if parallel reading is supported + * @return true (always supported for local files) + */ + bool supportsParallelRead() const { return true; } + void updateFileWatchSetting(); void clearFileCache(); + // Cleanup parallel file handles on destruction + virtual ~FileSource(); + private slots: void fileSystemWatcherFileChanged(const QString &) { fileChanged = true; } @@ -106,8 +136,28 @@ private slots: bool isFileOpened{}; private: + /** + * @brief Get or create thread-local file handle for parallel reads + * + * Each calling thread gets its own QFile handle, enabling lock-free I/O. + * Handles are cached per thread-ID and reused. + * + * @return Pointer to thread-local QFile, or nullptr on error + */ + QFile* getThreadLocalFileHandle(); + + /** + * @brief Clean up all pooled file handles + */ + void cleanupFileHandlePool(); + QFileSystemWatcher fileWatcher{}; bool fileChanged{}; QMutex readMutex; + + // Thread-local file handle pool for parallel reading + // Maps thread ID to unique QFile handle + std::unordered_map> fileHandlePool; + QMutex poolMutex; // Only protects pool access, not individual reads }; diff --git a/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp b/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp index 2d9dc6337..b38eccc93 100644 --- a/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp +++ b/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp @@ -35,7 +35,6 @@ #define ANNEXBFILE_DEBUG_OUTPUT 0 #if ANNEXBFILE_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_ANNEXBFILE(f) qDebug() << f #else #define DEBUG_ANNEXBFILE(f) ((void)0) #endif diff --git a/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp b/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp index 41afa62ec..6726ce65b 100644 --- a/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp +++ b/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp @@ -44,7 +44,6 @@ #define FILESOURCEFFMPEGFILE_DEBUG_OUTPUT 0 #if FILESOURCEFFMPEGFILE_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_FFMPEG qDebug #else #define DEBUG_FFMPEG(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/handler/ItemMemoryHandler.cpp b/YUViewLib/src/handler/ItemMemoryHandler.cpp index 009701c52..23b06eb80 100644 --- a/YUViewLib/src/handler/ItemMemoryHandler.cpp +++ b/YUViewLib/src/handler/ItemMemoryHandler.cpp @@ -1,34 +1,34 @@ /* This file is part of YUView - The YUV player with advanced analytics toolset - * - * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY - * - * 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; either version 3 of the License, or - * (at your option) any later version. - * - * In addition, as a special exception, the copyright holders give - * permission to link the code of portions of this program with the - * OpenSSL library under certain conditions as described in each - * individual source file, and distribute linked combinations including - * the two. - * - * You must obey the GNU General Public License in all respects for all - * of the code used other than OpenSSL. If you modify file(s) with this - * exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do - * so, delete this exception statement from your version. If you delete - * this exception statement from all source files in the program, then - * also delete it here. - * - * 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 for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +* +* Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY +* +* 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; either version 3 of the License, or +* (at your option) any later version. +* +* In addition, as a special exception, the copyright holders give +* permission to link the code of portions of this program with the +* OpenSSL library under certain conditions as described in each +* individual source file, and distribute linked combinations including +* the two. +* +* You must obey the GNU General Public License in all respects for all +* of the code used other than OpenSSL. If you modify file(s) with this +* exception, you may extend this exception to your version of the +* file(s), but you are not obligated to do so. If you do not wish to do +* so, delete this exception statement from your version. If you delete +* this exception statement from all source files in the program, then +* also delete it here. +* +* 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 for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #include "ItemMemoryHandler.h" @@ -38,7 +38,6 @@ #define ITEMMEMORYHANDLER_DEBUG 0 #if ITEMMEMORYHANDLER_DEBUG && !NDEBUG #include -#define DEBUG_MEMORY(msg) qDebug() << msg #else #define DEBUG_MEMORY(msg) ((void)0) #endif @@ -48,32 +47,26 @@ namespace itemMemoryHandler struct ItemData { - QString filePath; - QDateTime itemChangedLast; - std::string format; - QString toString() const - { - return QString("File: %1 Last Changed: %2 Format: %3") - .arg(filePath) - .arg(itemChangedLast.toString("yy-M-d H-m-s")) - .arg(QString::fromStdString(format)); - } + QString filePath; + QDateTime itemChangedLast; + QString format; + QString toString() const { return QString("File: %1 Last Changed: %2 Format: %3").arg(filePath).arg(itemChangedLast.toString("yy-M-d H-m-s")).arg(format); } }; QList getAllValidItems() { - QSettings settings; + QSettings settings; QList validItems; - auto timeYesterday = QDateTime::currentDateTime().addDays(-2); + auto timeYesterday = QDateTime::currentDateTime().addDays(-2); auto size = settings.beginReadArray("itemMemory"); - for (int i = 0; i < size; ++i) + for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); ItemData data; - data.filePath = settings.value("filePath").toString(); + data.filePath = settings.value("filePath").toString(); data.itemChangedLast = settings.value("dateChanged").toDateTime(); - data.format = settings.value("format").toString().toStdString(); + data.format = settings.value("format").toString(); if (data.itemChangedLast >= timeYesterday) { validItems.append(data); @@ -92,22 +85,22 @@ QList getAllValidItems() void writeNewItemList(QList newItemList) { QSettings settings; - settings.remove("itemMemory"); // Delete the old list + settings.remove("itemMemory"); // Delete the old list settings.beginWriteArray("itemMemory"); - for (int i = 0; i < newItemList.size(); ++i) + for (int i = 0; i < newItemList.size(); ++i) { const auto &item = newItemList[i]; settings.setArrayIndex(i); settings.setValue("filePath", item.filePath); settings.setValue("dateChanged", QVariant(item.itemChangedLast)); - settings.setValue("format", QString::fromStdString(item.format)); + settings.setValue("format", item.format); DEBUG_MEMORY("writeNewItemList Written item " << item.toString()); } settings.endArray(); } -void itemMemoryAddFormat(const QString &filePath, const std::string &format) +void itemMemoryAddFormat(QString filePath, QString format) { auto validItems = getAllValidItems(); @@ -117,8 +110,8 @@ void itemMemoryAddFormat(const QString &filePath, const std::string &format) if (validItems[i].filePath == filePath) { validItems[i].itemChangedLast = QDateTime::currentDateTime(); - validItems[i].format = format; - itemUpdated = true; + validItems[i].format = format; + itemUpdated = true; DEBUG_MEMORY("itemMemoryAddFormat Modified item " << validItems[i].toString()); break; } @@ -127,17 +120,17 @@ void itemMemoryAddFormat(const QString &filePath, const std::string &format) if (!itemUpdated) { ItemData newItem; - newItem.filePath = filePath; + newItem.filePath = filePath; newItem.itemChangedLast = QDateTime::currentDateTime(); - newItem.format = format; + newItem.format = format; validItems.append(newItem); DEBUG_MEMORY("itemMemoryAddFormat Added new item " << newItem.toString()); } - + writeNewItemList(validItems); } -std::optional itemMemoryGetFormat(const QString &filePath) +QString itemMemoryGetFormat(QString filePath) { auto validItems = getAllValidItems(); diff --git a/YUViewLib/src/handler/ItemMemoryHandler.h b/YUViewLib/src/handler/ItemMemoryHandler.h index b82ac9711..000905b10 100644 --- a/YUViewLib/src/handler/ItemMemoryHandler.h +++ b/YUViewLib/src/handler/ItemMemoryHandler.h @@ -1,34 +1,34 @@ /* This file is part of YUView - The YUV player with advanced analytics toolset - * - * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY - * - * 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; either version 3 of the License, or - * (at your option) any later version. - * - * In addition, as a special exception, the copyright holders give - * permission to link the code of portions of this program with the - * OpenSSL library under certain conditions as described in each - * individual source file, and distribute linked combinations including - * the two. - * - * You must obey the GNU General Public License in all respects for all - * of the code used other than OpenSSL. If you modify file(s) with this - * exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do - * so, delete this exception statement from your version. If you delete - * this exception statement from all source files in the program, then - * also delete it here. - * - * 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 for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ +* +* Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY +* +* 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; either version 3 of the License, or +* (at your option) any later version. +* +* In addition, as a special exception, the copyright holders give +* permission to link the code of portions of this program with the +* OpenSSL library under certain conditions as described in each +* individual source file, and distribute linked combinations including +* the two. +* +* You must obey the GNU General Public License in all respects for all +* of the code used other than OpenSSL. If you modify file(s) with this +* exception, you may extend this exception to your version of the +* file(s), but you are not obligated to do so. If you do not wish to do +* so, delete this exception statement from your version. If you delete +* this exception statement from all source files in the program, then +* also delete it here. +* +* 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 for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ #pragma once @@ -40,7 +40,7 @@ namespace itemMemoryHandler /* Simple memory that uses QSettings to save the format for a file. * Entries older then 48 hours are automatically deleted. */ -void itemMemoryAddFormat(const QString &filePath, const std::string &format); -std::optional itemMemoryGetFormat(const QString &filePath); +void itemMemoryAddFormat(QString filePath, QString format); +QString itemMemoryGetFormat(QString filePath); } // namespace itemMemoryHandler diff --git a/YUViewLib/src/handler/SingleInstanceHandler.cpp b/YUViewLib/src/handler/SingleInstanceHandler.cpp index 1456df080..ed7500263 100644 --- a/YUViewLib/src/handler/SingleInstanceHandler.cpp +++ b/YUViewLib/src/handler/SingleInstanceHandler.cpp @@ -35,8 +35,7 @@ // Activate this if you want to know when which difference is loaded #define SINGLEINSTANCEHANDLER_DEBUG 0 #if SINGLEINSTANCEHANDLER_DEBUG && !NDEBUG -#include -#define DEBUG_SINGLEISNTANCE qDebug +#include #else #define DEBUG_SINGLEISNTANCE(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/handler/UpdateHandler.cpp b/YUViewLib/src/handler/UpdateHandler.cpp index ff0b31690..2b0aa2f83 100644 --- a/YUViewLib/src/handler/UpdateHandler.cpp +++ b/YUViewLib/src/handler/UpdateHandler.cpp @@ -60,7 +60,6 @@ #define UPDATER_DEBUG_OUTPUT 0 #if UPDATER_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_UPDATE(msg) qDebug() << msg #else #define DEBUG_UPDATE(msg) ((void)0) #endif @@ -101,18 +100,15 @@ void updateHandler::sslErrors(QNetworkReply *reply, const QList &erro for (auto s : errors) { QString errorString = s.errorString(); - qDebug() << s.errorString(); auto cert = s.certificate(); QStringList certText = cert.toText().split("\n"); for (QString s : certText) - qDebug() << s; auto altNames = cert.subjectAlternativeNames(); QMultiMap::iterator i = altNames.begin(); while (i != altNames.end()) { - qDebug() << i.key() << " - " << i.value(); ++i; } } @@ -125,6 +121,10 @@ void updateHandler::sslErrors(QNetworkReply *reply, const QList &erro // Start the asynchronous checking for an update. void updateHandler::startCheckForNewVersion(bool userRequest, bool force) { + // Skip automatic update check - disable the annoying version popup + if (!userRequest && !force) + return; + QSettings settings; settings.beginGroup("updates"); bool checkForUpdates = settings.value("checkForUpdates", true).toBool(); diff --git a/YUViewLib/src/handler/UpdateHandlerFile.cpp b/YUViewLib/src/handler/UpdateHandlerFile.cpp index 034e849c7..b91e8e858 100644 --- a/YUViewLib/src/handler/UpdateHandlerFile.cpp +++ b/YUViewLib/src/handler/UpdateHandlerFile.cpp @@ -38,7 +38,6 @@ #define UPDATER_DEBUG_FILE 0 #if UPDATER_DEBUG_FILE && !NDEBUG #include -#define DEBUG_UPDATE_FILE(msg) qDebug() << msg #else #define DEBUG_UPDATE_FILE(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/AVC/HRD.cpp b/YUViewLib/src/parser/AVC/HRD.cpp index 64f5deb18..834d063af 100644 --- a/YUViewLib/src/parser/AVC/HRD.cpp +++ b/YUViewLib/src/parser/AVC/HRD.cpp @@ -41,7 +41,6 @@ #define PARSER_AVC_HRD_DEBUG_OUTPUT 0 #if PARSER_AVC_HRD_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_AVC_HRD(msg) qDebug() << msg #else #define DEBUG_AVC_HRD(fmt) ((void)0) #endif diff --git a/YUViewLib/src/parser/AVC/ParserAnnexBAVC.cpp b/YUViewLib/src/parser/AVC/ParserAnnexBAVC.cpp index f904b0b6c..3b2342616 100644 --- a/YUViewLib/src/parser/AVC/ParserAnnexBAVC.cpp +++ b/YUViewLib/src/parser/AVC/ParserAnnexBAVC.cpp @@ -50,7 +50,6 @@ #define PARSER_AVC_DEBUG_OUTPUT 0 #if PARSER_AVC_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_AVC(msg) qDebug() << msg #else #define DEBUG_AVC(fmt) ((void)0) #endif diff --git a/YUViewLib/src/parser/AVC/slice_header.cpp b/YUViewLib/src/parser/AVC/slice_header.cpp index b5b64f29a..31dff8f3b 100644 --- a/YUViewLib/src/parser/AVC/slice_header.cpp +++ b/YUViewLib/src/parser/AVC/slice_header.cpp @@ -47,7 +47,6 @@ #define PARSER_AVC_SLICEHEADER_DEBUG_OUTPUT 0 #if PARSER_AVC_SLICEHEADER_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_AVC(msg) qDebug() << msg #else #define DEBUG_AVC(fmt) ((void)0) #endif diff --git a/YUViewLib/src/parser/AVFormat/ParserAVFormat.cpp b/YUViewLib/src/parser/AVFormat/ParserAVFormat.cpp index e0f7e22e2..c5025c7da 100644 --- a/YUViewLib/src/parser/AVFormat/ParserAVFormat.cpp +++ b/YUViewLib/src/parser/AVFormat/ParserAVFormat.cpp @@ -49,7 +49,6 @@ #define PARSERAVCFORMAT_DEBUG_OUTPUT 0 #if PARSERAVCFORMAT_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_AVFORMAT qDebug #else #define DEBUG_AVFORMAT(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/parser/HEVC/ParserAnnexBHEVC.cpp b/YUViewLib/src/parser/HEVC/ParserAnnexBHEVC.cpp index 390d85a05..cbc73c0d1 100644 --- a/YUViewLib/src/parser/HEVC/ParserAnnexBHEVC.cpp +++ b/YUViewLib/src/parser/HEVC/ParserAnnexBHEVC.cpp @@ -50,7 +50,6 @@ #define PARSER_HEVC_DEBUG_OUTPUT 0 #if PARSER_HEVC_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_HEVC(msg) qDebug() << msg #else #define DEBUG_HEVC(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/Mpeg2/ParserAnnexBMpeg2.cpp b/YUViewLib/src/parser/Mpeg2/ParserAnnexBMpeg2.cpp index 34ce90ea0..60fa259c8 100644 --- a/YUViewLib/src/parser/Mpeg2/ParserAnnexBMpeg2.cpp +++ b/YUViewLib/src/parser/Mpeg2/ParserAnnexBMpeg2.cpp @@ -46,7 +46,6 @@ #define PARSER_MPEG2_DEBUG_OUTPUT 0 #if PARSER_MPEG2_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_MPEG2(msg) qDebug() << msg #else #define DEBUG_MPEG2(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/Parser.cpp b/YUViewLib/src/parser/Parser.cpp index 72a410693..39d04541e 100644 --- a/YUViewLib/src/parser/Parser.cpp +++ b/YUViewLib/src/parser/Parser.cpp @@ -37,7 +37,6 @@ #define BASE_DEBUG_OUTPUT 0 #if BASE_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_PARSER qDebug #else #define DEBUG_PARSER(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/parser/ParserAnnexB.cpp b/YUViewLib/src/parser/ParserAnnexB.cpp index 21f669b29..474f2412f 100644 --- a/YUViewLib/src/parser/ParserAnnexB.cpp +++ b/YUViewLib/src/parser/ParserAnnexB.cpp @@ -42,7 +42,6 @@ #define PARSERANNEXB_DEBUG_OUTPUT 0 #if PARSERANNEXB_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_ANNEXB(msg) qDebug() << msg #else #define DEBUG_ANNEXB(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/VVC/ParserAnnexBVVC.cpp b/YUViewLib/src/parser/VVC/ParserAnnexBVVC.cpp index 1ca5163c8..3b81fc222 100644 --- a/YUViewLib/src/parser/VVC/ParserAnnexBVVC.cpp +++ b/YUViewLib/src/parser/VVC/ParserAnnexBVVC.cpp @@ -55,7 +55,6 @@ #define PARSER_VVC_DEBUG_OUTPUT 0 #if PARSER_VVC_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_VVC(msg) qDebug() << msg #else #define DEBUG_VVC(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/VVC/picture_header_structure.cpp b/YUViewLib/src/parser/VVC/picture_header_structure.cpp index dd62ef8ca..1f2286a4f 100644 --- a/YUViewLib/src/parser/VVC/picture_header_structure.cpp +++ b/YUViewLib/src/parser/VVC/picture_header_structure.cpp @@ -40,7 +40,6 @@ #define PARSER_VVC_PICTURE_HEADER_DEBUG_OUTPUT 0 #if PARSER_VVC_PICTURE_HEADER_DEBUG_OUTPUT && !NDEBUG #include -#define DEBUG_PICHEADER(msg) qDebug() << msg #else #define DEBUG_PICHEADER(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/common/BitratePlotModel.cpp b/YUViewLib/src/parser/common/BitratePlotModel.cpp index a99dbd798..0a4a96f8c 100644 --- a/YUViewLib/src/parser/common/BitratePlotModel.cpp +++ b/YUViewLib/src/parser/common/BitratePlotModel.cpp @@ -34,7 +34,6 @@ #define BITRATE_PLOT_MODE_DEBUG 0 #if BITRATE_PLOT_MODE_DEBUG && !NDEBUG #include -#define DEBUG_PLOT(msg) qDebug() << msg #else #define DEBUG_PLOT(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/common/HRDPlotModel.cpp b/YUViewLib/src/parser/common/HRDPlotModel.cpp index 209fe2cf4..559d10b8e 100644 --- a/YUViewLib/src/parser/common/HRDPlotModel.cpp +++ b/YUViewLib/src/parser/common/HRDPlotModel.cpp @@ -34,7 +34,6 @@ #define HRD_PLOT_MODE_DEBUG 0 #if HRD_PLOT_MODE_DEBUG && !NDEBUG #include -#define DEBUG_PLOT(msg) qDebug() << msg #else #define DEBUG_PLOT(msg) ((void)0) #endif diff --git a/YUViewLib/src/parser/common/PacketItemModel.cpp b/YUViewLib/src/parser/common/PacketItemModel.cpp index 5b5e9b4d1..c28d1db80 100644 --- a/YUViewLib/src/parser/common/PacketItemModel.cpp +++ b/YUViewLib/src/parser/common/PacketItemModel.cpp @@ -1,4 +1,4 @@ -/* This file is part of YUView - The YUV player with advanced analytics toolset +/* This file is part of YUView - The YUV player with advanced analytics toolset * * Copyright (C) 2015 Institut f�r Nachrichtentechnik, RWTH Aachen University, GERMANY * @@ -40,7 +40,6 @@ #if PARSERCOMMON_DEBUG_FILTER_OUTPUT && !NDEBUG #include -#define DEBUG_FILTER qDebug #else #define DEBUG_FILTER(fmt, ...) ((void)0) #endif @@ -197,7 +196,15 @@ void PacketItemModel::setUseColorCoding(bool colorCoding) return; useColorCoding = colorCoding; - emit dataChanged(QModelIndex(), QModelIndex(), QVector() << Qt::BackgroundRole); + // Use valid index range to avoid Qt warning about invalid indices + // Only emit dataChanged if we have data to update + const int rows = rowCount(); + if (rows > 0) + { + const QModelIndex topLeft = index(0, 0); + const QModelIndex bottomRight = index(rows - 1, columnCount() - 1); + emit dataChanged(topLeft, bottomRight, QVector() << Qt::BackgroundRole); + } } void PacketItemModel::setShowVideoStreamOnly(bool videoOnly) @@ -206,7 +213,15 @@ void PacketItemModel::setShowVideoStreamOnly(bool videoOnly) return; showVideoOnly = videoOnly; - emit dataChanged(QModelIndex(), QModelIndex()); + // Use valid index range to avoid Qt warning about invalid indices + // Only emit dataChanged if we have data to update + const int rows = rowCount(); + if (rows > 0) + { + const QModelIndex topLeft = index(0, 0); + const QModelIndex bottomRight = index(rows - 1, columnCount() - 1); + emit dataChanged(topLeft, bottomRight); + } } /// ------------------- FilterByStreamIndexProxyModel ----------------------------- diff --git a/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp b/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp index 30dd3dfc5..985358897 100644 --- a/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp +++ b/YUViewLib/src/playlistitem/playlistItemCompressedVideo.cpp @@ -64,7 +64,6 @@ using namespace decoder; #define COMPRESSED_VIDEO_DEBUG_OUTPUT 0 #if COMPRESSED_VIDEO_DEBUG_OUTPUT #include -#define DEBUG_COMPRESSED(f) qDebug() << f #else #define DEBUG_COMPRESSED(f) ((void)0) #endif diff --git a/YUViewLib/src/playlistitem/playlistItemDifference.cpp b/YUViewLib/src/playlistitem/playlistItemDifference.cpp index 0918d4018..796203f15 100644 --- a/YUViewLib/src/playlistitem/playlistItemDifference.cpp +++ b/YUViewLib/src/playlistitem/playlistItemDifference.cpp @@ -39,7 +39,6 @@ // Activate this if you want to know when which difference is loaded #define PLAYLISTITEMDIFFERENCE_DEBUG_LOADING 0 #if PLAYLISTITEMDIFFERENCE_DEBUG_LOADING && !NDEBUG -#define DEBUG_DIFF qDebug #else #define DEBUG_DIFF(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/playlistitem/playlistItemImageFile.cpp b/YUViewLib/src/playlistitem/playlistItemImageFile.cpp index 93208465f..d8d5bbebe 100644 --- a/YUViewLib/src/playlistitem/playlistItemImageFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemImageFile.cpp @@ -36,6 +36,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -57,6 +62,8 @@ playlistItemImageFile::playlistItemImageFile(const QString &filePath) this->prop.isFileSource = true; this->prop.propertiesWidgetTitle = "Image Properties"; + this->frame.setBitDepth(this->m_bitDepth); + QFileInfo fileInfo(filePath); if (!fileInfo.exists() || !fileInfo.isFile()) return; @@ -97,6 +104,7 @@ void playlistItemImageFile::savePlaylist(QDomElement &root, const QDir &playlist // Append all the properties of the raw file (the path to the file. Relative and absolute) d.appendProperiteChild("absolutePath", fileURL.toString()); d.appendProperiteChild("relativePath", relativePath); + d.appendProperiteChild("bitDepth", QString::number(this->m_bitDepth)); root.appendChild(d); } @@ -122,6 +130,8 @@ playlistItemImageFile::newplaylistItemImageFile(const YUViewDomElement &root, // Load the propertied of the playlistItemIndexed playlistItem::loadPropertiesFromPlaylist(root, newImage); + newImage->setBitDepth(root.findChildValueInt("bitDepth", 10)); + return newImage; } @@ -231,3 +241,57 @@ void playlistItemImageFile::fileSystemWatcherFileChanged(const QString &) emit SignalItemChanged(true, RECACHE_CLEAR); this->updateSettings(); } + +void playlistItemImageFile::setBitDepth(int depth) +{ + if (this->m_bitDepth != depth) + { + this->m_bitDepth = depth; + this->frame.setBitDepth(depth); + emit SignalItemChanged(true, RECACHE_CLEAR); + } +} + +void playlistItemImageFile::createPropertiesWidget() +{ + Q_ASSERT_X(!this->propertiesWidget, "createPropertiesWidget", "Properties widget already exists"); + + this->preparePropertiesWidget(QStringLiteral("playlistItemImageFile")); + + auto vAllLayout = new QVBoxLayout(this->propertiesWidget.get()); + + // First add the parent's controls (duration) + vAllLayout->addLayout(createPlaylistItemControls()); + + // Add a line separator + auto line = new QFrame; + line->setObjectName(QStringLiteral("line")); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + vAllLayout->addWidget(line); + + // Add the custom controls for bit depth + auto bitDepthLayout = new QHBoxLayout(); + auto bitDepthLabel = new QLabel(QStringLiteral("Bit depth:")); + auto bitDepthComboBox = new QComboBox(); + bitDepthComboBox->addItem(QStringLiteral("16-bit"), 16); + bitDepthComboBox->addItem(QStringLiteral("10-bit"), 10); + + // Set the current value based on m_bitDepth + int currentIndex = bitDepthComboBox->findData(this->m_bitDepth); + if (currentIndex != -1) { + bitDepthComboBox->setCurrentIndex(currentIndex); + } + + bitDepthLayout->addWidget(bitDepthLabel); + bitDepthLayout->addWidget(bitDepthComboBox); + vAllLayout->addLayout(bitDepthLayout); + + // Connect the combobox signal + connect(bitDepthComboBox, QOverload::of(&QComboBox::currentIndexChanged), [this, bitDepthComboBox](int index) { + int depth = bitDepthComboBox->itemData(index).toInt(); + this->setBitDepth(depth); + }); + + vAllLayout->insertStretch(-1, 1); // Push controls up +} diff --git a/YUViewLib/src/playlistitem/playlistItemImageFile.h b/YUViewLib/src/playlistitem/playlistItemImageFile.h index 98888a981..65c90e7f9 100644 --- a/YUViewLib/src/playlistitem/playlistItemImageFile.h +++ b/YUViewLib/src/playlistitem/playlistItemImageFile.h @@ -78,6 +78,11 @@ class playlistItemImageFile : public playlistItem virtual void reloadItemSource() override { needToLoadImage = false; } virtual void updateSettings() override; + virtual void createPropertiesWidget() override; + + void setBitDepth(int depth); + int getBitDepth() const { return m_bitDepth; } + // Load the frame. Emit SignalItemChanged(true,false) when done. Always called from a thread. virtual void loadFrame(int frameIdx, bool playing, bool loadRawdata, bool emitSignals = true) override; @@ -99,4 +104,6 @@ private slots: // Does the image need to be loaded? Is it currently loading? bool needToLoadImage{true}; std::atomic imageLoading{false}; + + int m_bitDepth{10}; }; diff --git a/YUViewLib/src/playlistitem/playlistItemImageFileSequence.cpp b/YUViewLib/src/playlistitem/playlistItemImageFileSequence.cpp index fcefa23c4..ff7ca8497 100644 --- a/YUViewLib/src/playlistitem/playlistItemImageFileSequence.cpp +++ b/YUViewLib/src/playlistitem/playlistItemImageFileSequence.cpp @@ -242,7 +242,6 @@ playlistItemImageFileSequence::newplaylistItemImageFileSequence(const YUViewDomE if (!fileInfo.exists() || !fileInfo.isFile()) { // The file does not exist - // qDebug() << "Error while loading playlistItemImageFileSequence. The file " << absolutePath // << "could not be found."; newSequence->loadPlaylistFrameMissing = true; } diff --git a/YUViewLib/src/playlistitem/playlistItemOverlay.cpp b/YUViewLib/src/playlistitem/playlistItemOverlay.cpp index 271948c22..9def52ac2 100644 --- a/YUViewLib/src/playlistitem/playlistItemOverlay.cpp +++ b/YUViewLib/src/playlistitem/playlistItemOverlay.cpp @@ -45,7 +45,6 @@ #define PLAYLISTITEMOVERLAY_DEBUG 0 #if PLAYLISTITEMOVERLAY_DEBUG && !NDEBUG #include -#define DEBUG_OVERLAY qDebug #else #define DEBUG_OVERLAY(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp index d3d2054fb..cf1637132 100644 --- a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp @@ -44,7 +44,6 @@ // Activate this if you want to know when which buffer is loaded/converted to image and so on. #define PLAYLISTITEMRAWFILE_DEBUG_LOADING 0 #if PLAYLISTITEMRAWFILE_DEBUG_LOADING && !NDEBUG -#define DEBUG_RAWFILE(f) qDebug() << f #else #define DEBUG_RAWFILE(f) ((void)0) #endif @@ -63,9 +62,9 @@ constexpr auto CMYK_EXTENSIONS = {"cmyk"}; bool isInExtensions(const QString &testValue, const std::initializer_list &extensions) { const auto it = - std::find_if(extensions.begin(), - extensions.end(), - [testValue](const char *extension) { return QString(extension) == testValue; }); + std::find_if(extensions.begin(), + extensions.end(), + [testValue](const char *extension) { return QString(extension) == testValue; }); return it != extensions.end(); } @@ -122,10 +121,10 @@ playlistItemRawFile::playlistItemRawFile(const QString &rawFilePath, if (!this->parseY4MFile()) return; } - else if (pixelFormatFromMemory) + else if (!pixelFormatFromMemory.isEmpty()) { // Use the format that we got from the memory. Don't do any auto detection. - this->video->setFormatFromString(*pixelFormatFromMemory); + this->video->setFormatFromString(pixelFormatFromMemory); } else if (!frameSize.isValid() && sourcePixelFormat.isEmpty()) { @@ -159,6 +158,14 @@ playlistItemRawFile::playlistItemRawFile(const QString &rawFilePath, &playlistItemRawFile::loadRawData, Qt::DirectConnection); + // PERFORMANCE OPTIMIZATION: Set up direct read callback for parallel caching + // This bypasses the shared rawData buffer and requestDataMutex, enabling + // true parallel frame loading when multiple caching threads are active. + this->video->setDirectReadCallback( + [this](int frameIndex, QByteArray& targetBuffer) -> int64_t { + return this->readFrameDataDirect(frameIndex, targetBuffer); + }); + // Connect the basic signals from the video playlistItemWithVideo::connectVideo(); connect(this->video.get(), @@ -206,7 +213,7 @@ InfoData playlistItemRawFile::getInfo() const info.items.append(infoItem); const auto nrFrames = - (this->properties().startEndRange.second - this->properties().startEndRange.first + 1); + (this->properties().startEndRange.second - this->properties().startEndRange.first + 1); info.items.append(InfoItem("Num Frames", std::to_string(nrFrames))); info.items.append(InfoItem("Bytes per Frame", std::to_string(this->video->getBytesPerFrame()))); @@ -221,7 +228,7 @@ InfoData playlistItemRawFile::getInfo() const { if ((*fileSize % bpf) != 0) info.items.append(InfoItem( - "Warning"sv, "The file size and the given video size and/or raw format do not match.")); + "Warning"sv, "The file size and the given video size and/or raw format do not match.")); } else info.items.append(InfoItem("Warning"sv, "Could not obtain file size from input.")); @@ -251,7 +258,7 @@ bool playlistItemRawFile::parseY4MFile() unsigned width = 0; unsigned height = 0; auto format = - video::yuv::PixelFormatYUV(video::yuv::Subsampling::YUV_420, 8, video::yuv::PlaneOrder::YUV); + video::yuv::PixelFormatYUV(video::yuv::Subsampling::YUV_420, 8, video::yuv::PlaneOrder::YUV); while (rawData.at(offset++) == ' ') { @@ -389,7 +396,7 @@ bool playlistItemRawFile::parseY4MFile() if (width == 0 || height == 0) return setError( - "Error parsing the Y4M header: The size could not be obtained from the header."); + "Error parsing the Y4M header: The size could not be obtained from the header."); // Next, all frames should follow. Each frame starts with the sequence 'FRAME', followed by a set // of paramters for the frame. The 'FRAME' indicator is terminated by a 0x0A. The list of @@ -452,7 +459,7 @@ bool playlistItemRawFile::parseY4MFile() void playlistItemRawFile::setFormatFromFileName() { const auto fileInfoForGuess = filesource::frameFormatGuess::getFileInfoForGuessFromPath( - this->dataSource.getAbsoluteFilePath()); + this->dataSource.getAbsoluteFilePath()); const auto frameFormat = filesource::frameFormatGuess::guessFrameFormat(fileInfoForGuess); @@ -496,7 +503,7 @@ void playlistItemRawFile::savePlaylist(QDomElement &root, const QDir &playlistDi QUrl fileURL(QString::fromStdString(dataSource.getAbsoluteFilePath())); fileURL.setScheme("file"); auto relativePath = - playlistDir.relativeFilePath(QString::fromStdString(dataSource.getAbsoluteFilePath())); + playlistDir.relativeFilePath(QString::fromStdString(dataSource.getAbsoluteFilePath())); auto d = YUViewDomElement(root.ownerDocument().createElement("playlistItemRawFile")); @@ -523,7 +530,7 @@ playlistItemRawFile *playlistItemRawFile::newplaylistItemRawFile(const YUViewDom // check if file with absolute path exists, otherwise check relative path const auto filePath = - functions::getAbsPathFromAbsAndRel(playlistFilePath, absolutePath, relativePath); + functions::getAbsPathFromAbsAndRel(playlistFilePath, absolutePath, relativePath); if (filePath.isEmpty()) return nullptr; @@ -552,20 +559,59 @@ void playlistItemRawFile::loadRawData(int frameIdx) DEBUG_RAWFILE("playlistItemRawFile::loadRawData Start loading frame " << frameIdx << " bytes " << int(nrBytes)); - if (this->dataSource.readBytes(this->video->rawData, fileStartPos, nrBytes) < nrBytes) + + // Use parallel read for better performance when multiple caching threads are active + // This avoids mutex contention by using thread-local file handles + if (this->dataSource.readBytesParallel(this->video->rawData, fileStartPos, nrBytes) < nrBytes) return; // Error this->video->rawData_frameIndex = frameIdx; DEBUG_RAWFILE("playlistItemRawFile::loadRawData Frame " << frameIdx << " loaded"); } +int64_t playlistItemRawFile::readFrameDataDirect(int frameIndex, QByteArray& targetBuffer) +{ + // PERFORMANCE OPTIMIZATION: Direct parallel frame reading + // This method bypasses the shared rawData buffer, enabling true parallel I/O. + // Each caching thread provides its own buffer, eliminating mutex contention. + + if (!this->video->isFormatValid()) + return 0; + + const auto nrBytes = this->video->getBytesPerFrame(); + + // Calculate file position for the requested frame + int64_t fileStartPos; + if (this->isY4MFile) + { + if (frameIndex < 0 || frameIndex >= this->y4mFrameIndices.size()) + return 0; + fileStartPos = this->y4mFrameIndices.at(frameIndex); + } + else + { + fileStartPos = static_cast(frameIndex) * nrBytes; + } + + DEBUG_RAWFILE("playlistItemRawFile::readFrameDataDirect frame " << frameIndex + << " pos " << fileStartPos << " bytes " << nrBytes); + + // Use parallel read - each thread gets its own file handle + const auto bytesRead = this->dataSource.readBytesParallel(targetBuffer, fileStartPos, nrBytes); + + DEBUG_RAWFILE("playlistItemRawFile::readFrameDataDirect frame " << frameIndex + << " read " << bytesRead << " bytes"); + + return bytesRead; +} + void playlistItemRawFile::slotVideoPropertiesChanged() { DEBUG_RAWFILE("playlistItemRawFile::slotVideoPropertiesChanged"); - const auto currentPixelFormat = video->getFormatAsString(); - if (currentPixelFormat && currentPixelFormat != this->pixelFormatAfterLoading) - itemMemoryHandler::itemMemoryAddFormat(this->properties().name, *currentPixelFormat); + auto currentPixelFormat = video->getFormatAsString(); + if (currentPixelFormat != this->pixelFormatAfterLoading) + itemMemoryHandler::itemMemoryAddFormat(this->properties().name, currentPixelFormat); } ValuePairListSets playlistItemRawFile::getPixelValues(const QPoint &pixelPos, int frameIdx) diff --git a/YUViewLib/src/playlistitem/playlistItemRawFile.h b/YUViewLib/src/playlistitem/playlistItemRawFile.h index c1c75751a..17242da28 100644 --- a/YUViewLib/src/playlistitem/playlistItemRawFile.h +++ b/YUViewLib/src/playlistitem/playlistItemRawFile.h @@ -64,7 +64,7 @@ class playlistItemRawFile : public playlistItemWithVideo // Create a new playlistItemRawFile from the playlist file entry. Return nullptr if parsing // failed. static playlistItemRawFile *newplaylistItemRawFile(const YUViewDomElement &root, - const QString &playlistFilePath); + const QString & playlistFilePath); virtual bool canBeUsedInProcessing() const override { return true; } @@ -86,6 +86,23 @@ class playlistItemRawFile : public playlistItemWithVideo playlistItemWithVideo::cacheFrame(idx, testMode); } + /** + * @brief Direct frame data read for parallel caching (thread-safe) + * + * PERFORMANCE OPTIMIZATION: This method reads frame data directly into + * the caller's buffer without going through the shared rawData buffer. + * This enables true parallel I/O when multiple caching threads are active. + * + * Unlike loadRawData() which uses a shared buffer protected by mutex, + * this method can be called concurrently from multiple threads, each + * with their own buffer. + * + * @param frameIndex Frame index to read + * @param targetBuffer Output buffer (caller-owned, will be resized) + * @return Number of bytes read, or 0 on failure + */ + int64_t readFrameDataDirect(int frameIndex, QByteArray& targetBuffer); + private slots: // Load the raw data for the given frame index from file. This slot is called by the videoHandler // if the frame that is requested to be drawn has not been loaded yet. @@ -116,5 +133,5 @@ private slots: bool isY4MFile{}; QList y4mFrameIndices; - std::optional pixelFormatAfterLoading{}; + QString pixelFormatAfterLoading{}; }; diff --git a/YUViewLib/src/playlistitem/playlistItemResample.cpp b/YUViewLib/src/playlistitem/playlistItemResample.cpp index 250ea2391..9370eff61 100644 --- a/YUViewLib/src/playlistitem/playlistItemResample.cpp +++ b/YUViewLib/src/playlistitem/playlistItemResample.cpp @@ -39,7 +39,6 @@ // Activate this if you want to know when which difference is loaded #define PLAYLISTITEMRESAMPLE_DEBUG_LOADING 0 #if PLAYLISTITEMRESAMPLE_DEBUG_LOADING && !NDEBUG -#define DEBUG_RESAMPLE qDebug #else #define DEBUG_RESAMPLE(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp b/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp index 6b3931c3e..572885821 100644 --- a/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp @@ -47,7 +47,6 @@ #define PLAYLISTITEMSTATISTICS_DEBUG 0 #if PLAYLISTITEMSTATISTICS_DEBUG && !NDEBUG -#define DEBUG_STAT qDebug #else #define DEBUG_STAT(fmt, ...) ((void)0) #endif @@ -103,7 +102,7 @@ InfoData playlistItemStatisticsFile::getInfo() const } playlistItemStatisticsFile *playlistItemStatisticsFile::newplaylistItemStatisticsFile( - const YUViewDomElement &root, const QString &playlistFilePath, OpenMode openMode) + const YUViewDomElement &root, const QString &playlistFilePath, OpenMode openMode) { // Parse the DOM element. It should have all values of a playlistItemStatisticsFile auto absolutePath = root.findChildValue("absolutePath"); @@ -111,7 +110,7 @@ playlistItemStatisticsFile *playlistItemStatisticsFile::newplaylistItemStatistic // check if file with absolute path exists, otherwise check relative path const auto filePath = - functions::getAbsPathFromAbsAndRel(playlistFilePath, absolutePath, relativePath); + functions::getAbsPathFromAbsAndRel(playlistFilePath, absolutePath, relativePath); if (filePath.isEmpty()) return nullptr; @@ -299,12 +298,12 @@ void playlistItemStatisticsFile::openStatisticsFile() this->timer.start(1000, this); this->breakBackgroundAtomic.store(false); this->backgroundParserFuture = QtConcurrent::run( - [this](stats::StatisticsFileBase *file) - { file->readFrameAndTypePositionsFromFile(std::ref(this->breakBackgroundAtomic)); }, - this->file.get()); + [=](stats::StatisticsFileBase *file) + { file->readFrameAndTypePositionsFromFile(std::ref(this->breakBackgroundAtomic)); }, + this->file.get()); DEBUG_STAT( - "playlistItemStatisticsFile::openStatisticsFile File opened. Background parsing started."); + "playlistItemStatisticsFile::openStatisticsFile File opened. Background parsing started."); } // This timer event is called regularly when the background loading process is running. diff --git a/YUViewLib/src/playlistitem/playlistItemText.cpp b/YUViewLib/src/playlistitem/playlistItemText.cpp index 2d319aa0e..fad0711f2 100644 --- a/YUViewLib/src/playlistitem/playlistItemText.cpp +++ b/YUViewLib/src/playlistitem/playlistItemText.cpp @@ -42,7 +42,6 @@ // Activate this if you want to know when which buffer is loaded/converted to image and so on. #define PLAYLISTITEMTEXT_DEBUG 0 #if PLAYLISTITEMTEXT_DEBUG && !NDEBUG -#define DEBUG_TEXT qDebug #else #define DEBUG_TEXT(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/playlistitem/playlistItemWithVideo.cpp b/YUViewLib/src/playlistitem/playlistItemWithVideo.cpp index b88e134ef..7c186c5e9 100644 --- a/YUViewLib/src/playlistitem/playlistItemWithVideo.cpp +++ b/YUViewLib/src/playlistitem/playlistItemWithVideo.cpp @@ -35,7 +35,6 @@ // Activate this if you want to know when which buffer is loaded/converted to image and so on. #define PLAYLISTITEMWITHVIDEO_DEBUG_LOADING 0 #if PLAYLISTITEMWITHVIDEO_DEBUG_LOADING && !NDEBUG -#define DEBUG_PLVIDEO qDebug #else #define DEBUG_PLVIDEO(fmt, ...) ((void)0) #endif @@ -113,8 +112,10 @@ void playlistItemWithVideo::loadFrame(int frameIdx, emit SignalItemChanged(true, RECACHE_NONE); } - if (playing && (state == ItemLoadingState::LoadingNeeded || - state == ItemLoadingState::LoadingNeededDoubleBuffer)) + const bool useDoubleBufferPreload = !this->usesRawYUVCache(); + if (useDoubleBufferPreload && playing && + (state == ItemLoadingState::LoadingNeeded || + state == ItemLoadingState::LoadingNeededDoubleBuffer)) { // Load the next frame into the double buffer int nextFrameIdx = frameIdx + 1; @@ -150,4 +151,4 @@ QList playlistItemWithVideo::getCachedFrames() const if (video) return video->getCachedFrames(); return {}; -} \ No newline at end of file +} diff --git a/YUViewLib/src/playlistitem/playlistItemWithVideo.h b/YUViewLib/src/playlistitem/playlistItemWithVideo.h index ed719b73e..bf41c5d62 100644 --- a/YUViewLib/src/playlistitem/playlistItemWithVideo.h +++ b/YUViewLib/src/playlistitem/playlistItemWithVideo.h @@ -77,6 +77,16 @@ class playlistItemWithVideo : public playlistItem { return unresolvableError ? 0 : video->getNumberCachedFrames(); } + /** + * @brief Return whether this item uses raw YUV caching. + * + * Raw YUV caching is enabled for HDR playback paths where GPU-based + * conversion is used and CPU-side double-buffer RGB conversion is not needed. + */ + bool usesRawYUVCache() const + { + return video && video->shouldUseRawYUVCache(); + } // How many bytes will caching one frame use (in bytes)? virtual unsigned int getCachingFrameSize() const override { diff --git a/YUViewLib/src/statistics/StatisticUIHandler.cpp b/YUViewLib/src/statistics/StatisticUIHandler.cpp index 25567f537..cfd96f108 100644 --- a/YUViewLib/src/statistics/StatisticUIHandler.cpp +++ b/YUViewLib/src/statistics/StatisticUIHandler.cpp @@ -40,10 +40,9 @@ #endif #include -#include -#include #include #include +#include namespace stats { @@ -51,7 +50,6 @@ namespace stats // Activate this if you want to know when what is loaded. #define STATISTICS_DEBUG_LOADING 0 #if STATISTICS_DEBUG_LOADING && !NDEBUG -#define DEBUG_STATUI qDebug #else #define DEBUG_STATUI(fmt, ...) ((void)0) #endif @@ -65,10 +63,7 @@ StatisticUIHandler::StatisticUIHandler() Qt::QueuedConnection); } -void StatisticUIHandler::setStatisticsData(StatisticsData *data) -{ - this->statisticsData = data; -} +void StatisticUIHandler::setStatisticsData(StatisticsData *data) { this->statisticsData = data; } QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateControlsOnly) { @@ -76,7 +71,7 @@ QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateContro { // Absolutely always only do this once Q_ASSERT_X( - !ui.created(), Q_FUNC_INFO, "The primary statistics controls must only be created once."); + !ui.created(), Q_FUNC_INFO, "The primary statistics controls must only be created once."); ui.setupUi(); } @@ -97,8 +92,10 @@ QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateContro itemNameCheck->setChecked(statType.render); itemNameCheck->setToolTip(statType.description); ui.gridLayout->addWidget(itemNameCheck, int(row + 2), 0); - connect( - itemNameCheck, QCheckBoxStateChanged, this, &StatisticUIHandler::onStatisticsControlChanged); + connect(itemNameCheck, + &QCheckBox::stateChanged, + this, + &StatisticUIHandler::onStatisticsControlChanged); itemNameCheckBoxes[0].push_back(itemNameCheck); // Append the opacity slider @@ -107,16 +104,17 @@ QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateContro opacitySlider->setMaximum(100); opacitySlider->setValue(statType.alphaFactor); ui.gridLayout->addWidget(opacitySlider, int(row + 2), 1); - connect( - opacitySlider, &QSlider::valueChanged, this, &StatisticUIHandler::onStatisticsControlChanged); + connect(opacitySlider, + &QSlider::valueChanged, + this, + &StatisticUIHandler::onStatisticsControlChanged); itemOpacitySliders[0].push_back(opacitySlider); // Append the change style buttons QPushButton *pushButton = new QPushButton( - functionsGui::convertIcon(":img_edit.png"), QString(), ui.scrollAreaWidgetContents); + functionsGui::convertIcon(":img_edit.png"), QString(), ui.scrollAreaWidgetContents); ui.gridLayout->addWidget(pushButton, int(row + 2), 2); - connect( - pushButton, &QPushButton::released, this, [this, row] { this->onStyleButtonClicked(row); }); + connect(pushButton, &QPushButton::released, this, [=] { onStyleButtonClicked(row); }); itemStyleButtons[0].push_back(pushButton); } @@ -145,7 +143,7 @@ QWidget *StatisticUIHandler::getSecondaryStatisticsHandlerControls(bool recreate if (!this->statisticsData) { DEBUG_STATUI( - "StatisticUIHandler::getSecondaryStatisticsHandlerControls statisticsData not set"); + "StatisticUIHandler::getSecondaryStatisticsHandlerControls statisticsData not set"); return {}; } @@ -160,7 +158,7 @@ QWidget *StatisticUIHandler::getSecondaryStatisticsHandlerControls(bool recreate itemNameCheck->setChecked(statType.render); ui2.gridLayout->addWidget(itemNameCheck, int(row + 2), 0); connect(itemNameCheck, - QCheckBoxStateChanged, + &QCheckBox::stateChanged, this, &StatisticUIHandler::onSecondaryStatisticsControlChanged); itemNameCheckBoxes[1].push_back(itemNameCheck); @@ -179,10 +177,9 @@ QWidget *StatisticUIHandler::getSecondaryStatisticsHandlerControls(bool recreate // Append the change style buttons QPushButton *pushButton = new QPushButton( - functionsGui::convertIcon(":img_edit.png"), QString(), ui2.scrollAreaWidgetContents); + functionsGui::convertIcon(":img_edit.png"), QString(), ui2.scrollAreaWidgetContents); ui2.gridLayout->addWidget(pushButton, int(row + 2), 2); - connect( - pushButton, &QPushButton::released, this, [this, row] { this->onStyleButtonClicked(row); }); + connect(pushButton, &QPushButton::released, this, [=] { onStyleButtonClicked(row); }); itemStyleButtons[1].push_back(pushButton); } @@ -192,7 +189,7 @@ QWidget *StatisticUIHandler::getSecondaryStatisticsHandlerControls(bool recreate if (true || ui2.created()) { QSpacerItem *verticalSpacer = - new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding); + new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding); ui2.gridLayout->addItem(verticalSpacer, int(statTypes.size() + 2), 0, 1, 1); spacerItems[1] = verticalSpacer; } @@ -323,8 +320,8 @@ void StatisticUIHandler::updateStatisticsHandlerControls() } // First run a check if all statisticsTypes are identical - bool controlsStillValid = true; - auto &statTypes = this->statisticsData->getStatisticsTypes(); + bool controlsStillValid = true; + auto &statTypes = this->statisticsData->getStatisticsTypes(); if (statTypes.size() != itemNameCheckBoxes[0].size()) // There are more or less statistics types as before controlsStillValid = false; diff --git a/YUViewLib/src/statistics/StatisticsData.cpp b/YUViewLib/src/statistics/StatisticsData.cpp index aa93a2bd7..0c883c96f 100644 --- a/YUViewLib/src/statistics/StatisticsData.cpp +++ b/YUViewLib/src/statistics/StatisticsData.cpp @@ -38,7 +38,6 @@ #define STATISTICS_DEBUG_LOADING 0 #if STATISTICS_DEBUG_LOADING && !NDEBUG #include -#define DEBUG_STATDATA(fmt) qDebug() << fmt #else #define DEBUG_STATDATA(fmt) ((void)0) #endif diff --git a/YUViewLib/src/statistics/StatisticsDataPainting.cpp b/YUViewLib/src/statistics/StatisticsDataPainting.cpp index f421055a8..df04d614f 100644 --- a/YUViewLib/src/statistics/StatisticsDataPainting.cpp +++ b/YUViewLib/src/statistics/StatisticsDataPainting.cpp @@ -47,7 +47,6 @@ namespace // Activate this if you want to know when what is loaded. #define STATISTICS_DEBUG_PAINTING 0 #if STATISTICS_DEBUG_PAINTING && !NDEBUG -#define DEBUG_PAINT qDebug #else #define DEBUG_PAINT(fmt, ...) ((void)0) #endif diff --git a/YUViewLib/src/ui/Mainwindow.cpp b/YUViewLib/src/ui/Mainwindow.cpp index e0c6852b0..6aa7de655 100644 --- a/YUViewLib/src/ui/Mainwindow.cpp +++ b/YUViewLib/src/ui/Mainwindow.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include #include @@ -47,11 +49,16 @@ #include #include #include +#include