summaryrefslogtreecommitdiff
path: root/src/mbgl/gl/program.hpp
blob: f33501cd11e093e358118bc5b76edb7e200cc6a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#pragma once

#include <mbgl/gl/types.hpp>
#include <mbgl/gl/object.hpp>
#include <mbgl/gl/context.hpp>
#include <mbgl/gl/vertex_buffer.hpp>
#include <mbgl/gl/index_buffer.hpp>
#include <mbgl/gl/vertex_array.hpp>
#include <mbgl/gl/attribute.hpp>
#include <mbgl/gl/uniform.hpp>

#include <mbgl/util/io.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/programs/binary_program.hpp>
#include <mbgl/programs/program_parameters.hpp>
#include <mbgl/shaders/shaders.hpp>

#include <string>

namespace mbgl {
namespace gl {

template <class P, class As, class Us>
class Program {
public:
    using Primitive = P;
    using Attributes = As;
    using Uniforms = Us;

    using UniformValues = typename Uniforms::Values;
    using AttributeBindings = typename Attributes::Bindings;

    Program(Context& context, const std::string& vertexSource, const std::string& fragmentSource)
        : program(
              context.createProgram(context.createShader(ShaderType::Vertex, vertexSource),
                                    context.createShader(ShaderType::Fragment, fragmentSource))),
          uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))),
          attributeLocations(Attributes::bindLocations(context, program)) {

        // Re-link program after manually binding only active attributes in Attributes::bindLocations
        context.linkProgram(program);

        // We have to re-initialize the uniforms state from the bindings as the uniform locations
        // get shifted on some implementations
        uniformsState = Uniforms::bindLocations(program);
    }

    template <class BinaryProgram>
    Program(Context& context, const BinaryProgram& binaryProgram)
        : program(context.createProgram(binaryProgram.format(), binaryProgram.code())),
          uniformsState(Uniforms::loadNamedLocations(binaryProgram)),
          attributeLocations(Attributes::loadNamedLocations(binaryProgram)) {
    }
    
    static Program createProgram(gl::Context& context,
                                 const ProgramParameters& programParameters,
                                 const char* name,
                                 const char* vertexSource_,
                                 const char* fragmentSource_) {
        const std::string vertexSource = shaders::vertexSource(programParameters, vertexSource_);
        const std::string fragmentSource = shaders::fragmentSource(programParameters, fragmentSource_);

#if MBGL_HAS_BINARY_PROGRAMS
        optional<std::string> cachePath = programParameters.cachePath(name);
        if (cachePath && context.supportsProgramBinaries()) {
            const std::string identifier = shaders::programIdentifier(vertexSource, fragmentSource);

            try {
                if (auto cachedBinaryProgram = util::readFile(*cachePath)) {
                    const BinaryProgram binaryProgram(std::move(*cachedBinaryProgram));
                    if (binaryProgram.identifier() == identifier) {
                        return Program { context, binaryProgram };
                    } else {
                        Log::Warning(Event::OpenGL,
                                     "Cached program %s changed. Recompilation required.",
                                     name);
                    }
                }
            } catch (std::runtime_error& error) {
                Log::Warning(Event::OpenGL, "Could not load cached program: %s",
                             error.what());
            }

            // Compile the shader
            Program result{ context, vertexSource, fragmentSource };

            try {
                if (const auto binaryProgram =
                        result.template get<BinaryProgram>(context, identifier)) {
                    util::write_file(*cachePath, binaryProgram->serialize());
                    Log::Warning(Event::OpenGL, "Caching program in: %s", (*cachePath).c_str());
                }
            } catch (std::runtime_error& error) {
                Log::Warning(Event::OpenGL, "Failed to cache program: %s", error.what());
            }

            return std::move(result);
        }
#endif

        (void)name;
        return Program { context, vertexSource, fragmentSource };
    }

    template <class BinaryProgram>
    optional<BinaryProgram> get(Context& context, const std::string& identifier) const {
        if (auto binaryProgram = context.getBinaryProgram(program)) {
            return BinaryProgram{ binaryProgram->first, std::move(binaryProgram->second),
                                  identifier, Attributes::getNamedLocations(attributeLocations),
                                  Uniforms::getNamedLocations(uniformsState) };
        }
        return {};
    }

    template <class DrawMode>
    void draw(Context& context,
              DrawMode drawMode,
              DepthMode depthMode,
              StencilMode stencilMode,
              ColorMode colorMode,
              const UniformValues& uniformValues,
              VertexArray& vertexArray,
              const AttributeBindings& attributeBindings,
              const IndexBuffer<DrawMode>& indexBuffer,
              std::size_t indexOffset,
              std::size_t indexLength) {
        static_assert(std::is_same<Primitive, typename DrawMode::Primitive>::value, "incompatible draw mode");

        context.setDrawMode(drawMode);
        context.setDepthMode(depthMode);
        context.setStencilMode(stencilMode);
        context.setColorMode(colorMode);

        context.program = program;

        Uniforms::bind(uniformsState, uniformValues);

        vertexArray.bind(context,
                        indexBuffer.buffer,
                        Attributes::toBindingArray(attributeLocations, attributeBindings));

        context.draw(drawMode.primitiveType,
                     indexOffset,
                     indexLength);
    }

private:
    UniqueProgram program;

    typename Uniforms::State uniformsState;
    typename Attributes::Locations attributeLocations;
};

} // namespace gl
} // namespace mbgl