• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17920603410

22 Sep 2025 03:39PM UTC coverage: 71.97% (-0.05%) from 72.02%
17920603410

push

github

paulmthompson
all tests pass

277 of 288 new or added lines in 8 files covered. (96.18%)

520 existing lines in 35 files now uncovered.

40275 of 55961 relevant lines covered (71.97%)

1225.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.75
/src/WhiskerToolbox/ShaderManager/ShaderManager.cpp
1
#include "ShaderManager.hpp"
2

3
#include "ShaderProgram.hpp"
4

5
#include <QDebug>
6
#include <QFileInfo>
7
#include <QOpenGLContext>
8

9
#include <iostream>
10

11
ShaderManager & ShaderManager::instance() {
526✔
12
    static ShaderManager instance;
526✔
13
    return instance;
526✔
14
}
15

16
ShaderManager::ShaderManager() {
45✔
17
    connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &ShaderManager::onFileChanged);
45✔
18
}
45✔
19

20
bool ShaderManager::loadProgram(std::string const & name,
199✔
21
                                std::string const & vertexPath,
22
                                std::string const & fragmentPath,
23
                                std::string const & geometryPath,
24
                                ShaderSourceType sourceType) {
25
    QOpenGLContext * ctx = QOpenGLContext::currentContext();
199✔
26
    if (!ctx) {
199✔
27
        std::cerr << "[ShaderManager] No current OpenGL context when loading program: " << name << std::endl;
×
28
        return false;
×
29
    }
30

31
    auto & program_map = m_programs_by_context[ctx];
199✔
32
    auto it_existing = program_map.find(name);
199✔
33
    if (it_existing != program_map.end()) {
199✔
34
        // Already loaded for this context
35
        return true;
18✔
36
    }
37

38
    auto program = std::make_unique<ShaderProgram>(vertexPath, fragmentPath, geometryPath, sourceType);
181✔
39
    if (!program->reload()) {
181✔
40
        std::cerr << "[ShaderManager] Failed to compile shader program: " << name << std::endl;
×
41
        return false;
×
42
    }
43
    program_map[name] = std::move(program);
181✔
44
    m_programSourceType[name] = sourceType;
181✔
45

46
    // Only watch files for hot-reloading if using filesystem
47
    if (sourceType == ShaderSourceType::FileSystem) {
181✔
48
        if (!vertexPath.empty()) m_fileWatcher.addPath(QString::fromStdString(vertexPath));
2✔
49
        if (!fragmentPath.empty()) m_fileWatcher.addPath(QString::fromStdString(fragmentPath));
2✔
50
        if (!geometryPath.empty()) m_fileWatcher.addPath(QString::fromStdString(geometryPath));
2✔
51
        if (!vertexPath.empty()) m_pathToProgramName[vertexPath] = name;
2✔
52
        if (!fragmentPath.empty()) m_pathToProgramName[fragmentPath] = name;
2✔
53
        if (!geometryPath.empty()) m_pathToProgramName[geometryPath] = name;
2✔
54
    } else {
55
        // Print info if resource path (no hot-reload)
56
        std::cout << "[ShaderManager] Loaded shader program '" << name << "' from Qt resource. Hot-reloading is not available." << std::endl;
179✔
57
    }
58
    return true;
181✔
59
}
181✔
60

61
ShaderProgram * ShaderManager::getProgram(std::string const & name) {
377✔
62
    QOpenGLContext * ctx = QOpenGLContext::currentContext();
377✔
63
    if (!ctx) {
377✔
64
        return nullptr;
×
65
    }
66
    auto ctx_it = m_programs_by_context.find(ctx);
377✔
67
    if (ctx_it == m_programs_by_context.end()) {
377✔
UNCOV
68
        return nullptr;
×
69
    }
70
    auto & program_map = ctx_it->second;
377✔
71
    auto it = program_map.find(name);
377✔
72
    if (it != program_map.end()) {
377✔
73
        return it->second.get();
375✔
74
    }
75
    return nullptr;
2✔
76
}
77

78
void ShaderManager::onFileChanged(QString const & path) {
×
79
    std::string const pathStr = path.toStdString();
×
80
    auto it = m_pathToProgramName.find(pathStr);
×
81
    if (it != m_pathToProgramName.end()) {
×
82
        std::string const & programName = it->second;
×
83
        // Reload the program in all contexts that have it
84
        for (auto & [ctx, program_map]: m_programs_by_context) {
×
85
            (void) ctx;
86
            auto progIt = program_map.find(programName);
×
87
            if (progIt != program_map.end()) {
×
88
                std::cout << "[ShaderManager] Detected change in shader file: " << pathStr << ". Reloading program: " << programName << std::endl;
×
89
                if (progIt->second->reload()) {
×
90
                    std::cout << "[ShaderManager] Successfully reloaded shader program: " << programName << std::endl;
×
91
                    emit shaderReloaded(programName);
×
92
                } else {
93
                    std::cerr << "[ShaderManager] Failed to reload shader program: " << programName << ". Keeping previous version active." << std::endl;
×
94
                }
95
            }
96
        }
97
    }
98
    // Re-add the path to the watcher in case it was removed
99
    if (QFileInfo::exists(path)) {
×
100
        m_fileWatcher.addPath(path);
×
101
    }
102
}
×
103

104
void ShaderManager::cleanup() {
32✔
105
    // Clear all shader programs for all contexts
106
    for (auto & [ctx, program_map]: m_programs_by_context) {
66✔
107
        (void) ctx;
108
        for (auto & [name, program]: program_map) {
164✔
109
            if (program) {
130✔
110
                delete program.release();
130✔
111
            }
112
        }
113
        program_map.clear();
34✔
114
    }
115
    m_programs_by_context.clear();
32✔
116
    m_fileWatcher.removePaths(m_fileWatcher.files());
32✔
117
    m_fileWatcher.removePaths(m_fileWatcher.directories());
32✔
118
}
32✔
119

120
void ShaderManager::cleanupCurrentContext() {
×
121
    QOpenGLContext * ctx = QOpenGLContext::currentContext();
×
122
    if (!ctx) return;
×
123
    auto it = m_programs_by_context.find(ctx);
×
124
    if (it == m_programs_by_context.end()) return;
×
125
    auto & program_map = it->second;
×
126
    for (auto & [name, program]: program_map) {
×
127
        if (program) {
×
128
            delete program.release();
×
129
        }
130
    }
131
    program_map.clear();
×
132
    m_programs_by_context.erase(it);
×
133
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc