Program.cc 24.8 KB
Newer Older
1
2
#include "Program.hh"

3
#include <cassert>
Philip Trettner's avatar
Philip Trettner committed
4
#include <chrono>
5
#include <fstream>
6

7
8
#include "AtomicCounterBuffer.hh"
#include "Framebuffer.hh"
9
#include "Shader.hh"
10
#include "ShaderStorageBuffer.hh"
11
#include "Texture.hh"
12
#include "UniformBuffer.hh"
13

14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <glow/callbacks.hh>
#include <glow/common/gl_to_string.hh>
#include <glow/common/gltypeinfo.hh>
#include <glow/common/ogl_typeinfo.hh>
#include <glow/common/runtime_assert.hh>
#include <glow/common/shader_endings.hh>
#include <glow/common/str_utils.hh>
#include <glow/common/thread_local.hh>
#include <glow/glow.hh>
#include <glow/limits.hh>
#include <glow/util/LocationMapping.hh>
#include <glow/util/UniformState.hh>

#include <glow/common/profiling.hh>
28

29
30
using namespace glow;

31
/// Currently used program
32
static GLOW_THREADLOCAL UsedProgram* sCurrentProgram = nullptr;
33

Philip Trettner's avatar
Philip Trettner committed
34
bool Program::sCheckShaderReloading = true;
35

36
37
bool Program::linkAndCheckErrors()
{
38
    checkValidGLOW();
39
40
    GLOW_ACTION();

41
42
43
44
45
46
47
48
49
50
51
52
    // link
    glLinkProgram(mObjectName);

    // check error log
    GLint logLength = 0;
    glGetProgramiv(mObjectName, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 1)
    {
        std::vector<GLchar> log;
        log.resize(logLength + 1);
        glGetProgramInfoLog(mObjectName, logLength + 1, nullptr, log.data());

53
        error() << "Linker log for: " << to_string(this);
54
        for (auto const& s : mShader)
55
            error() << "  " << glShaderTypeToString(s->getType()) << ": " << (s->getFileName().empty() ? to_string(s.get()) : s->getFileName());
56
57
58
59
60
61
62
        error() << "Shader linker: " << log.data();
        return false;
    }

    return true;
}

63
64
void Program::restoreExtendedState()
{
65
66
    checkValidGLOW();

67
68
69
70
    // check transform feedback
    if (isConfiguredForTransformFeedback() && !mIsLinkedForTransformFeedback)
        link();

71
    // bind uniform buffer
72
    for (auto const& kvp : mUniformBuffers)
73
74
75
76
77
78
    {
        auto loc = mUniformBufferMapping.queryLocation(kvp.first);
        glBindBufferBase(GL_UNIFORM_BUFFER, loc, kvp.second ? kvp.second->getObjectName() : 0);
    }

    // bind shader storage buffer
79
    for (auto const& kvp : mShaderStorageBuffers)
80
81
82
83
84
    {
        auto loc = mShaderStorageBufferMapping.queryLocation(kvp.first);
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, loc, kvp.second ? kvp.second->getObjectName() : 0);
    }

85
    // bind atomic counters
86
    for (auto const& kvp : mAtomicCounterBuffers)
87
88
89
90
    {
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, kvp.first, kvp.second ? kvp.second->getObjectName() : 0);
    }

91
92
93
    // restore texture bindings
    for (auto unit = 0u; unit < mTextures.size(); ++unit)
    {
94
        auto const& tex = mTextures[unit];
95
96
97
98
99
100
101
102
103
104
105
        if (!tex)
            continue;

        glActiveTexture(GL_TEXTURE0 + unit);
        glBindTexture(tex->getTarget(), tex->getObjectName());
    }

    // safety net: activate different unit
    glActiveTexture(GL_TEXTURE0 + limits::maxCombinedTextureImageUnits - 1);
}

106
107
bool Program::hasShaderType(GLenum type) const
{
108
    for (auto const& shader : mShader)
109
110
111
112
113
114
        if (shader->getType() == type)
            return true;

    return false;
}

115
UsedProgram* Program::getCurrentProgram() { return sCurrentProgram; }
116

117
void Program::setShaderReloading(bool enabled) { sCheckShaderReloading = enabled; }
Philip Trettner's avatar
Philip Trettner committed
118

119
120
void Program::validateTextureMipmaps() const
{
121
    for (auto const& t : mTextures)
122
        if (t != nullptr && !t->areMipmapsGenerated() && t->hasMipmapsEnabled())
123
            glow::error() << "Texture is bound to shader but does not have mipmaps generated. " << to_string(t.get());
124
125
}

126
GLint Program::getUniformLocation(const std::string& name) const
Philip Trettner's avatar
Philip Trettner committed
127
{
128
129
130
    auto info = getUniformInfo(name);
    return info ? info->location : -1;
}
Philip Trettner's avatar
Philip Trettner committed
131

132
Program::UniformInfo const* Program::getUniformInfo(const std::string& name) const
133
{
134
    for (auto const& e : mUniformCache)
135
136
137
138
139
140
        if (e.name == name)
            return &e;

    return nullptr;
}

141
Program::UniformInfo* Program::getUniformInfo(const std::string& name)
142
{
143
    for (auto& e : mUniformCache)
144
145
146
147
148
149
        if (e.name == name)
            return &e;

    return nullptr;
}

150
GLint Program::useUniformLocationAndVerify(const std::string& name, GLint size, GLenum type)
151
152
153
{
    auto info = getUniformInfo(name);

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    if (!info)
    {
        // This uniform might be missing from the cache because it's name is formatted differently
        // by the user, vs the output from glGetActiveUniform that was used to build the cache.
        // Retry with glGetUniformLocation

        auto loc = glGetUniformLocation(mObjectName, name.c_str());

        if (loc != -1)
        {
            mUniformCache.emplace_back(name, loc, size, type);
            info = &mUniformCache.back();
        }
    }

169
170
    if (info)
    {
171
        if (info->size != size || info->type != type)
172
173
            warning() << "Uniform `" << name << "' has type `" << glUniformTypeToString(info->type, info->size) << "' in shader but is set as `"
                      << glUniformTypeToString(type, size) << "' in GLOW. " << to_string(this);
174
175
176
177
178
        info->wasSet = true;
        return info->location;
    }

    return -1;
Philip Trettner's avatar
Philip Trettner committed
179
180
}

181
GLuint Program::getUniformBlockIndex(const std::string& name) const
Philip Trettner's avatar
Philip Trettner committed
182
{
183
    checkValidGLOW();
Philip Trettner's avatar
Philip Trettner committed
184
185
186
    return glGetUniformBlockIndex(mObjectName, name.c_str());
}

187
188
Program::Program()
{
189
    checkValidGLOW();
190
    mObjectName = glCreateProgram();
191
192
193

    mAttributeMapping = std::make_shared<LocationMapping>();
    mFragmentMapping = std::make_shared<LocationMapping>();
194
195
196
197
}

Program::~Program()
{
198
    checkValidGLOW();
199
200
201
202
203
    glDeleteProgram(mObjectName);
}

bool Program::isLinked() const
{
204
    checkValidGLOW();
205
206
207
208
209
210
211
212
213
    GLint linkStatus;
    glGetProgramiv(mObjectName, GL_LINK_STATUS, &linkStatus);

    if (linkStatus == GL_FALSE)
        return false;
    else
        return true;
}

214
215
std::vector<std::pair<std::string, int>> Program::extractAttributeLocations()
{
216
    checkValidGLOW();
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    std::vector<std::pair<std::string, int>> locs;

    // get attribute locations
    GLint maxAttrLength = 0;
    GLint attrCnt = 0;
    glGetProgramiv(mObjectName, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttrLength);
    glGetProgramiv(mObjectName, GL_ACTIVE_ATTRIBUTES, &attrCnt);

    std::vector<char> nameBuffer;
    nameBuffer.resize(maxAttrLength);
    for (auto i = 0; i < attrCnt; ++i)
    {
        GLint size;
        GLenum type;
Philip Trettner's avatar
Philip Trettner committed
231
        glGetActiveAttrib(mObjectName, i, (GLsizei)nameBuffer.size(), nullptr, &size, &type, nameBuffer.data());
232
233
234
235
236
237
238
239
240
241
242
243
244

        std::string name{nameBuffer.data()};
        if (name.find("gl_") == 0)
            continue;

        auto shaderLoc = glGetAttribLocation(mObjectName, name.c_str());
        assert(shaderLoc != -1); // should not happen
        locs.push_back({name, shaderLoc});
    }

    return locs;
}

245
void Program::attachShader(const SharedShader& shader)
246
{
247
    checkValidGLOW();
248
249
250
251
    mShader.push_back(shader);
    glAttachShader(mObjectName, shader->getObjectName());
}

252
void Program::link(bool saveUniformState)
253
{
254
    checkValidGLOW();
255
256
    GLOW_ACTION();

257
258
    // save uniforms
    auto uniforms = saveUniformState ? getUniforms() : nullptr;
259

260
261
262
    auto requiresRelink = true;
    auto linkCnt = 0;
    while (requiresRelink)
263
    {
264
265
266
        requiresRelink = false;

        // set attribute locations
267
        for (auto const& mapping : mAttributeMapping->getMap())
268
269
            glBindAttribLocation(mObjectName, mapping.second, mapping.first.c_str());

270
        // set fragment locations
271
        for (auto const& mapping : mFragmentMapping->getMap())
272
273
            glBindFragDataLocation(mObjectName, mapping.second, mapping.first.c_str());

274
275
276
        // set transform feedback
        if (isConfiguredForTransformFeedback())
        {
277
278
            std::vector<const char*> varyings;
            for (auto const& s : mTransformFeedbackVaryings)
279
                varyings.push_back(s.c_str());
Philip Trettner's avatar
Philip Trettner committed
280
            glTransformFeedbackVaryings(mObjectName, (GLsizei)varyings.size(), varyings.data(), mTransformFeedbackMode);
281
282
283
            mIsLinkedForTransformFeedback = true;
        }

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
        ++linkCnt;
        if (!linkAndCheckErrors())
            return; // ERROR!

        // get attribute locations
        GLint maxAttrLength = 0;
        GLint attrCnt = 0;
        glGetProgramiv(mObjectName, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttrLength);
        glGetProgramiv(mObjectName, GL_ACTIVE_ATTRIBUTES, &attrCnt);

        std::vector<char> nameBuffer;
        nameBuffer.resize(maxAttrLength);
        for (auto i = 0; i < attrCnt; ++i)
        {
            GLint size;
            GLenum type;
Philip Trettner's avatar
Philip Trettner committed
300
            glGetActiveAttrib(mObjectName, i, (GLsizei)nameBuffer.size(), nullptr, &size, &type, nameBuffer.data());
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315

            std::string name{nameBuffer.data()};
            if (name.find("gl_") == 0)
                continue;

            auto shaderLoc = glGetAttribLocation(mObjectName, name.c_str());
            assert(shaderLoc != -1); // should not happen
            auto refLoc = mAttributeMapping->getOrAddLocation(name, shaderLoc);
            if (refLoc != (GLuint)shaderLoc)
                requiresRelink = true;
        }

        // sanity
        if (linkCnt > 2)
        {
316
            warning() << "Linking does not converge. Aborting. " << to_string(this);
317
318
319
            break;
        }
    }
320

321
322
    // perform layout location check
    // TODO: configure via cmake
323
    if (!mCheckedForAttributeLocationLayout)
324
325
326
327
328
    {
        GLint maxAttrs = 0;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttrs);

        // check mapping
329
        for (auto const& mapping : mAttributeMapping->getMap())
330
331
        {
            auto shaderLoc = glGetAttribLocation(mObjectName, mapping.first.c_str());
332
333
334
            if (shaderLoc == -1)
                continue; // unused

335
336
            if (shaderLoc != mapping.second)
            {
337
338
                error() << "Attribute '" << mapping.first << "' has inconsistent location (" << mapping.second << " vs " << shaderLoc
                        << "). Note, use of layout(location = X) is not allowed! " << to_string(this);
339
340
341
342
            }
        }

        // map with offset
343
        for (auto const& mapping : mAttributeMapping->getMap())
344
345
346
347
348
349
            glBindAttribLocation(mObjectName, (mapping.second + 1) % maxAttrs, mapping.first.c_str());

        // relink
        linkAndCheckErrors();

        // check offset mapping
350
        for (auto const& mapping : mAttributeMapping->getMap())
351
352
        {
            auto shaderLoc = glGetAttribLocation(mObjectName, mapping.first.c_str());
353
354
355
            if (shaderLoc == -1)
                continue; // unused

356
357
            if (shaderLoc != (mapping.second + 1) % maxAttrs)
            {
358
359
                error() << "Attribute '" << mapping.first << "' has inconsistent location (" << ((mapping.second + 1) % maxAttrs) << " vs "
                        << shaderLoc << "). Note, use of layout(location = X) is not allowed! " << to_string(this);
360
361
362
363
            }
        }

        // map normal again
364
        for (auto const& mapping : mAttributeMapping->getMap())
365
366
367
368
            glBindAttribLocation(mObjectName, mapping.second, mapping.first.c_str());
        // link normal again
        linkAndCheckErrors();

369
370
371
372
373
374
375
376
377
378
379
        mCheckedForAttributeLocationLayout = true;
    }

    // perform layout location check
    // TODO: configure via cmake
    if (!mCheckedForFragmentLocationLayout)
    {
        GLint maxFrags = 0;
        glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxFrags);

        // check mapping
380
        for (auto const& mapping : mFragmentMapping->getMap())
381
382
383
384
385
386
387
        {
            auto shaderLoc = glGetFragDataLocation(mObjectName, mapping.first.c_str());
            if (shaderLoc == -1)
                continue; // unused

            if (shaderLoc != mapping.second)
            {
388
389
                error() << "Fragment output '" << mapping.first << "' has inconsistent location (" << mapping.second << " vs " << shaderLoc
                        << "). Note, use of layout(location = X) is not allowed! " << to_string(this);
390
391
392
393
            }
        }

        // map with offset
394
        for (auto const& mapping : mFragmentMapping->getMap())
395
396
397
398
399
400
            glBindFragDataLocation(mObjectName, (mapping.second + 1) % maxFrags, mapping.first.c_str());

        // relink
        linkAndCheckErrors();

        // check offset mapping
401
        for (auto const& mapping : mFragmentMapping->getMap())
402
403
404
405
406
407
408
        {
            auto shaderLoc = glGetFragDataLocation(mObjectName, mapping.first.c_str());
            if (shaderLoc == -1)
                continue; // unused

            if (shaderLoc != (mapping.second + 1) % maxFrags)
            {
409
410
                error() << "Fragment output '" << mapping.first << "' has inconsistent location (" << ((mapping.second + 1) % maxFrags) << " vs "
                        << shaderLoc << "). Note, use of layout(location = X) is not allowed! " << to_string(this);
411
412
413
414
            }
        }

        // map normal again
415
        for (auto const& mapping : mFragmentMapping->getMap())
416
417
418
419
420
            glBindFragDataLocation(mObjectName, mapping.second, mapping.first.c_str());
        // link normal again
        linkAndCheckErrors();

        mCheckedForFragmentLocationLayout = true;
421
    }
Philip Trettner's avatar
Philip Trettner committed
422

Philip Trettner's avatar
Philip Trettner committed
423
    // rebind uniform buffers
424
    for (auto const& kvp : mUniformBuffers)
Philip Trettner's avatar
Philip Trettner committed
425
426
        setUniformBuffer(kvp.first, kvp.second);

427
    for (auto const& kvp : mShaderStorageBuffers)
428
429
        setShaderStorageBuffer(kvp.first, kvp.second);

430
431
    // rebuild cache
    auto prevCache = mUniformCache; // copy
Philip Trettner's avatar
Philip Trettner committed
432
    mUniformCache.clear();
433
434
    GLint cnt;
    glGetProgramiv(mObjectName, GL_ACTIVE_UNIFORMS, &cnt);
435
436
    mUniformCache.reserve(size_t(cnt));

437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    // Receive all indices of uniforms that are inside uniform blocks (and thus do not need to get cached)
    auto constexpr maxUboUniformIndices = 2048;
    std::array<GLint, maxUboUniformIndices> uboUniformIndices;
    size_t numUboUniformIndices = 0;
    {
        GLint activeUniformBlocks;
        glGetProgramiv(mObjectName, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks);

        for (auto i = 0; i < activeUniformBlocks; ++i)
        {
            GLint numUboParameters;
            glGetActiveUniformBlockiv(mObjectName, GLuint(i), GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numUboParameters);

            // Skip empty UBOs
            if (numUboParameters <= 0)
                continue;

            if (numUboUniformIndices + GLuint(numUboParameters) >= maxUboUniformIndices)
            {
                // The indices buffer would overflow, stop caching
                glow::warning() << "Shader UBOs contains high number of UBO parameters, exceeding cache size " << to_string(this);
                glow::warning() << "False-positive unchanged uniform warnings might follow";
                break;
            }

            // Write the indices into the buffer
            glGetActiveUniformBlockiv(mObjectName, GLuint(i), GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &uboUniformIndices[numUboUniformIndices]);
            numUboUniformIndices += GLuint(numUboParameters);
        }
    }

468
469
470
    GLchar uniformName[2048];
    for (auto i = 0; i < cnt; ++i)
    {
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
        // Skip this uniform if it is part of a UBO
        {
            bool uniformIsInUbo = false;
            for (auto j = 0u; j < numUboUniformIndices; ++j)
            {
                if (i == uboUniformIndices[j])
                {
                    uniformIsInUbo = true;
                    break;
                }
            }

            if (uniformIsInUbo)
                continue;
        }

487
488
489
        GLsizei length;
        GLint size;
        GLenum type;
490
491
        glGetActiveUniform(mObjectName, GLuint(i), sizeof(uniformName), &length, &size, &type, uniformName);
        std::string uniformNameStd = uniformName;
492

493
494
495
496
        // Skip reserved identifiers
        if (uniformNameStd.find("gl_") == 0)
            continue;

497
        // Cut down uArray[0] to uArray
498
        auto const bracketPos = uniformNameStd.find('[');
499
500
501
502
        if (bracketPos != std::string::npos)
            uniformNameStd = uniformNameStd.substr(0, bracketPos);

        mUniformCache.emplace_back(uniformNameStd, glGetUniformLocation(mObjectName, uniformNameStd.c_str()), size, type);
503
    }
504

505
506
507
    // restore uniforms
    if (uniforms)
        uniforms->restore();
508
509

    // restore "wasSet" information
510
    for (auto& info : mUniformCache)
511
        info.wasSet = false; // uniforms->restore will set it to true
512
    for (auto const& info : prevCache)
513
514
515
516
517
518
    {
        auto e = getUniformInfo(info.name);
        if (e)
            e->wasSet = info.wasSet;
    }
    mCheckedUnchangedUniforms = false;
519
520

    auto now = std::chrono::system_clock::now().time_since_epoch();
521
    mLastTimeLinked = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
522
523
524
525
526
527
528
529
530
}

void Program::checkUnchangedUniforms()
{
    if (mCheckedUnchangedUniforms)
        return; // already checked
    if (!mWarnOnUnchangedUniforms)
        return; // warning disabled

531
    for (auto const& info : mUniformCache)
532
533
        if (!info.wasSet                  //
            && !isImageUniform(info.type) // not an image
534
        )
535
536
537
538
539
540
        {
            warning() << "Uniform `" << info.name << "' is used in the shader but was not set via GLOW. " << to_string(this);
            warning() << "  (This also applies for uniforms with default values. We recommend making them non-uniform "
                         "if you don't set them.) ";
            warning() << "  (This warning can be disabled via setWarnOnUnchangedUniforms(false).) ";
        }
541
542

    mCheckedUnchangedUniforms = true;
543
544
}

545
void Program::configureTransformFeedback(const std::vector<std::string>& varyings, GLenum bufferMode)
546
547
548
{
    if (sCurrentProgram != nullptr && sCurrentProgram->program == this)
    {
549
        glow::error() << "Cannot set configureTransformFeedback while same program is active. " << to_string(this);
550
551
552
553
554
555
556
557
558
        assert(0);
        return;
    }

    mTransformFeedbackMode = bufferMode;
    mTransformFeedbackVaryings = varyings;
    mIsLinkedForTransformFeedback = false;
}

559
SharedUniformState Program::getUniforms() const { return UniformState::create(this); }
560

561
void UsedProgram::compute(GLuint groupsX, GLuint groupsY, GLuint groupsZ)
562
563
564
{
    if (!isCurrent())
        return;
Philip Trettner's avatar
Philip Trettner committed
565

566
    checkValidGLOW();
567
    glDispatchCompute(groupsX, groupsY, groupsZ);
568

569
570
    // notify shader exection
    notifyShaderExecuted();
571
572
}

573
void UsedProgram::beginTransformFeedback(GLenum primitiveMode, const SharedBuffer& feedbackBuffer)
574
575
576
577
578
579
580
{
    if (!isCurrent())
        return;

    if (!program->isConfiguredForTransformFeedback())
    {
        error() << "Cannot call beginTransformFeedback before it is configured (see "
581
582
                   "Program::configureTransformFeedback) "
                << to_string(program);
583
584
585
586
587
588
589
590
591
592
593
594
595
596
        return;
    }

    if (!program->mIsLinkedForTransformFeedback)
        program->link(); // relink

    checkValidGLOW();

    if (feedbackBuffer != nullptr)
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, feedbackBuffer->getObjectName());

    glBeginTransformFeedback(primitiveMode);
}

597
void UsedProgram::endTransformFeedback()
598
599
600
601
602
603
604
605
606
607
{
    if (!isCurrent())
        return;

    checkValidGLOW();
    glEndTransformFeedback();

    // TODO: error handling!
}

608
SharedProgram Program::create(const std::vector<SharedShader>& shader)
609
{
610
    auto program = std::make_shared<Program>();
611
    for (auto const& s : shader)
612
613
614
        program->attachShader(s);
    program->link();
    return program;
615
616
}

617
SharedProgram Program::createFromFile(const std::string& fileOrBaseName)
618
{
619
620
    GLOW_ACTION();

621
    auto program = std::make_shared<Program>();
622
    program->setObjectLabel(fileOrBaseName);
623
624
625
626
    GLenum type;
    std::string content;
    std::string realFileName;

627
    // try to resolve file directly
628
    if (Shader::resolveFile(fileOrBaseName, type, content, realFileName))
629
    {
630
631
632
633
        if (!realFileName.empty())
            program->attachShader(Shader::createFromFile(type, realFileName));
        else
            program->attachShader(Shader::createFromSource(type, content));
634
    }
635
636
    else // otherwise check endings
    {
637
638
        auto foundAnyWithoutStripping = false;

639
        for (auto const& kvp : glow::shaderEndingToType)
640
641
        {
            auto fname = fileOrBaseName;
642
            auto wasStripped = false;
643
            while (fname != "")
644
            {
645
646
647
648
649
650
651
                if (Shader::resolveFile(fname + kvp.first, type, content, realFileName))
                {
                    if (!realFileName.empty())
                        program->attachShader(Shader::createFromFile(type, realFileName));
                    else
                        program->attachShader(Shader::createFromSource(type, content));

652
653
654
                    if (!wasStripped)
                        foundAnyWithoutStripping = true;

655
656
657
658
659
                    break; // found file
                }

                // try to find "base" versions
                fname = util::stripFileDot(fname);
660
                wasStripped = true;
661
            }
662
        }
663
664
665
666

        if (!foundAnyWithoutStripping)
            warning() << "No shader found for `" << fileOrBaseName
                      << "' that directly matched (without stripping parts of the filename). This most likely "
667
                         "indicates a typo or missing file. ";
668
    }
669

670
671
672
    if (program->mShader.empty())
        warning() << "No shaders attached from '" << fileOrBaseName << "'. This may indicate a path error.";

673
674
675
676
677
678
    if (program->hasShaderType(GL_COMPUTE_SHADER) && program->mShader.size() > 1)
        warning() << "Shader `" << fileOrBaseName << "' has compute shader and other shaders attached. This is not supported.";

    if (!program->hasShaderType(GL_VERTEX_SHADER) && program->hasShaderType(GL_FRAGMENT_SHADER))
        warning() << "Shader `" << fileOrBaseName << "' has a fragment shader but no vertex shader. This may indicate a path error.";

679
680
681
682
    program->link();
    return program;
}

683
SharedProgram Program::createFromFiles(const std::vector<std::string>& filenames)
684
{
685
686
    GLOW_ACTION();

687
    auto program = std::make_shared<Program>();
688
    program->setObjectLabel(util::joinToString(filenames));
689
690
691
    GLenum type;
    std::string content;
    std::string realFileName;
692

693
    for (auto const& filename : filenames)
694
        if (Shader::resolveFile(filename, type, content, realFileName))
695
        {
696
697
698
699
            if (!realFileName.empty())
                program->attachShader(Shader::createFromFile(type, realFileName));
            else
                program->attachShader(Shader::createFromSource(type, content));
700
        }
701
        else
702
            error() << "Unable to resolve shader file '" << filename << "'. " << to_string(program.get());
703
704
705
706
707

    program->link();
    return program;
}

708
UsedProgram::UsedProgram(Program* program) : program(program)
709
{
710
    checkValidGLOW();
711
712
713
714
    glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram);
    glUseProgram(program->mObjectName);

    previousProgramPtr = sCurrentProgram;
715
    sCurrentProgram = this;
Philip Trettner's avatar
Philip Trettner committed
716
717

    checkShaderReload();
Philip Trettner's avatar
Philip Trettner committed
718

719
    program->restoreExtendedState();
Philip Trettner's avatar
Philip Trettner committed
720
721
}

722
void UsedProgram::checkShaderReload()
Philip Trettner's avatar
Philip Trettner committed
723
724
725
726
727
728
{
    if (!isCurrent())
        return;

    // check for reloading every 100ms
    auto now = std::chrono::system_clock::now().time_since_epoch();
729
    if (Program::sCheckShaderReloading && now - std::chrono::milliseconds(program->mLastReloadCheck) > std::chrono::milliseconds(200))
Philip Trettner's avatar
Philip Trettner committed
730
731
732
733
    {
        program->mLastReloadCheck = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();

        auto needsReload = false;
734
        for (auto const& shader : program->mShader)
Philip Trettner's avatar
Philip Trettner committed
735
736
737
738
739
740
741
742
            if (shader->newerVersionAvailable())
            {
                needsReload = true;
            }

        if (needsReload)
        {
            log() << "Auto-reloading shader program.";
743
            for (auto const& shader : program->mShader)
Philip Trettner's avatar
Philip Trettner committed
744
745
746
747
748
749
            {
                log() << "  reloading " << shader->getFileName();
                shader->reload();
            }

            log() << "  linking.";
750
            // relink (uniform state is restored)
Philip Trettner's avatar
Philip Trettner committed
751
752
753
            program->link();
        }
    }
754
755
}

Philip Trettner's avatar
Philip Trettner committed
756

757
bool UsedProgram::isCurrent() const
758
{
759
    GLOW_RUNTIME_ASSERT(sCurrentProgram == this, "Currently bound Program does NOT match represented program " << to_string(program), return false);
760
761
762
    return true;
}

763
UsedProgram::UsedProgram(UsedProgram&& rhs) : program(rhs.program), previousProgram(rhs.previousProgram), previousProgramPtr(rhs.previousProgramPtr)
764
765
766
{
    // invalidate rhs
    rhs.previousProgram = -1;
767
    sCurrentProgram = this;
768
769
}

770
UsedProgram::~UsedProgram()
771
772
773
{
    if (previousProgram != -1) // only if valid
    {
774
        checkValidGLOW();
775
776
        glUseProgram(previousProgram);
        sCurrentProgram = previousProgramPtr;
777
778
779
780

        // re-restore prev state
        if (sCurrentProgram)
            sCurrentProgram->program->restoreExtendedState();
781
782
    }
}
783

784
void Program::implGetUniform(detail::glBaseType type, GLint loc, void* data) const
785
{
786
787
    checkValidGLOW();

788
789
    switch (type)
    {
790
    case detail::glBaseType::Float:
791
        glGetUniformfv(mObjectName, loc, (GLfloat*)data);
792
        break;
793
    case detail::glBaseType::Int:
794
        glGetUniformiv(mObjectName, loc, (GLint*)data);
795
        break;
796
    case detail::glBaseType::UInt:
797
        glGetUniformuiv(mObjectName, loc, (GLuint*)data);
798
        break;
799
    case detail::glBaseType::Double:
800
        glGetUniformdv(mObjectName, loc, (GLdouble*)data);
801
802
803
804
805
806
        break;
    default:
        assert(0);
        break;
    }
}