Browse code

Add texture

Robert Cranston authored on 01/07/2021 00:25:58
Showing 5 changed files

... ...
@@ -39,6 +39,8 @@ int main()
39 39
         std::array<float, 4> value1;
40 40
         std::array<float, 4> value2;
41 41
     } values = {};
42
+    auto texture0 = GLuint{};
43
+    glGenTextures(1, &texture0);
42 44
 
43 45
     // Global settings.
44 46
     Shader::root("assets/shaders");
... ...
@@ -65,6 +67,7 @@ int main()
65 67
         .uniform("light_count", light_count)
66 68
         .uniform("color", color)
67 69
         .uniform("values", values)
70
+        .texture("texture0", texture0, GL_TEXTURE_2D)
68 71
         .validate();
69 72
     Shader::uniform_buffer("values", values);
70 73
 }
... ...
@@ -319,6 +322,26 @@ Bare arrays are not supported, wrap them in uniform blocks.
319 322
 [memory layout]: https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout
320 323
 [avoid `vec3`/`mat3`]: https://stackoverflow.com/questions/38172696/should-i-ever-use-a-vec3-inside-of-a-uniform-buffer-or-shader-storage-buffer-object
321 324
 
325
+#### Textures
326
+
327
+As a special case, uniform texture samplers can be set by calling
328
+`texture(std::string const & name, GLuint texture, GLenum target, bool required
329
+= true)`. Unless a texture unit is already correctly set up as a result of a
330
+previous call, one is automatically allocated and its `target` bound to
331
+`texture`. If a texture unit needs to be allocated but no unused one is
332
+available (the limit can be queried with
333
+[`glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, ...)`][glGet] the least
334
+recently used one is reused. [`uniform`](#uniforms) is then called with the
335
+given `name` (and `required`) and the allocated texture unit as the `value`.
336
+
337
+Subsequent calls with a given `texture` are very cheap provided that the number
338
+of other textures set since the previous call stay below the limit.
339
+
340
+If a subsequent call for a given `texture` uses a different `target` an error
341
+is thrown.
342
+
343
+[glGet]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGet.xhtml
344
+
322 345
 ## Dependencies
323 346
 
324 347
 Public (interface):
325 348
new file mode 100644
... ...
@@ -0,0 +1,10 @@
1
+#version 110
2
+
3
+
4
+uniform sampler2D texture0;
5
+
6
+
7
+void main()
8
+{
9
+    gl_Position = texture2D(texture0, vec2(0)) * gl_Vertex;
10
+}
... ...
@@ -62,6 +62,13 @@ public:
62 62
         bool                required = true
63 63
     ) = delete;
64 64
 
65
+    Shader & texture(
66
+        std::string const & name,
67
+        GLuint              texture,
68
+        GLenum              target,
69
+        bool                required = true
70
+    );
71
+
65 72
 protected:
66 73
 
67 74
     struct Uniform
... ...
@@ -6,6 +6,7 @@
6 6
 #include <cstring>
7 7
 #include <fstream>
8 8
 #include <ios>
9
+#include <iterator>
9 10
 #include <list>
10 11
 #include <memory>
11 12
 #include <regex>
... ...
@@ -866,3 +867,104 @@ Shader::UniformBuffer * Shader::uniform_buffer_(
866 867
     // Return
867 868
     return &uniform_buffer;
868 869
 }
870
+
871
+
872
+Shader & Shader::texture(
873
+    std::string const & name,
874
+    GLuint              texture,
875
+    GLenum              target,
876
+    bool                required
877
+)
878
+{
879
+    // Get limits.
880
+    static auto const max_combined_texture_image_units = get_integer_<GLuint>(
881
+        GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
882
+    );
883
+
884
+    // Define data.
885
+    struct Unit
886
+    {
887
+        GLuint unit;
888
+        GLenum target;
889
+        GLuint texture;
890
+    };
891
+    using Units = std::list<Unit>;
892
+    using Textures = std::unordered_map<GLuint, Units::iterator>;
893
+    static auto units = Units{};
894
+    static auto textures = Textures{};
895
+
896
+    // Define helpers.
897
+    static auto const unit_to_front = [&](Units::iterator const & unit_it)
898
+    {
899
+        if (unit_it != units.begin())
900
+            units.splice(units.begin(), units, unit_it, std::next(unit_it));
901
+    };
902
+    static auto const target_name = [](GLenum target)
903
+    {
904
+        return
905
+            STR_COND(target, GL_TEXTURE_1D)
906
+            STR_COND(target, GL_TEXTURE_2D)
907
+            STR_COND(target, GL_TEXTURE_3D)
908
+            STR_COND(target, GL_TEXTURE_1D_ARRAY)
909
+            STR_COND(target, GL_TEXTURE_2D_ARRAY)
910
+            STR_COND(target, GL_TEXTURE_RECTANGLE)
911
+            STR_COND(target, GL_TEXTURE_CUBE_MAP)
912
+            STR_COND(target, GL_TEXTURE_CUBE_MAP_ARRAY)
913
+            STR_COND(target, GL_TEXTURE_BUFFER)
914
+            STR_COND(target, GL_TEXTURE_2D_MULTISAMPLE)
915
+            STR_COND(target, GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
916
+            STR(std::hex << std::showbase << target);
917
+    };
918
+
919
+    // Check textures.
920
+    auto texture_it = textures.find(texture);
921
+    if (texture_it != textures.end() && texture_it->second->texture == texture)
922
+    {
923
+        // Check for errors.
924
+        if (texture_it->second->target != target)
925
+            throw std::runtime_error{STR(
926
+                "Failed to set texture " << texture << "; " <<
927
+                "expected target " << target_name(texture_it->second->target)
928
+                << " but got " << target_name(target) << "."
929
+            )};
930
+
931
+        // Update units.
932
+        unit_to_front(texture_it->second);
933
+    }
934
+    else
935
+    {
936
+        // Update units.
937
+        if (units.size() < max_combined_texture_image_units)
938
+        {
939
+            units.emplace_front(Unit{(GLuint)units.size(), target, texture});
940
+        }
941
+        else
942
+        {
943
+            unit_to_front(std::prev(units.end()));
944
+            units.front().target = target;
945
+            units.front().texture = texture;
946
+        }
947
+
948
+        // Update textures.
949
+        if (texture_it == textures.end())
950
+            textures.emplace(texture, units.begin());
951
+        else
952
+            texture_it->second = units.begin();
953
+
954
+        // Bind.
955
+        auto const error = STR(
956
+            "Failed to bind texture " << texture << " to target " <<
957
+            target_name(target)
958
+        );
959
+        error_(error, "unprocessed previous error");
960
+        glActiveTexture(GL_TEXTURE0 + units.front().unit);
961
+        glBindTexture(target, texture);
962
+        error_(error, "wrong target?");
963
+    }
964
+
965
+    // Set uniform.
966
+    uniform(name, (GLint)units.front().unit, required);
967
+
968
+    // Return.
969
+    return *this;
970
+}
... ...
@@ -208,6 +208,24 @@ GLTEST(2, 0, 640, 480, glshader)
208 208
         "uniform block required but not found (did you mean the uniform?)."
209 209
     )
210 210
 
211
+    // Texture.
212
+    auto texture0 = GLuint{};
213
+    glGenTextures(1, &texture0);
214
+    GLTEST_EXPECT_EXCEPTION(false,
215
+        Shader({"tests/texture.vert"}).use().texture("texture0", texture0, GL_TEXTURE_2D).texture("texture0", texture0, GL_TEXTURE_3D),
216
+        "Failed to set texture 1; "
217
+        "expected target GL_TEXTURE_2D but got GL_TEXTURE_3D."
218
+    )
219
+    auto texture1 = GLuint{};
220
+    glGenTextures(1, &texture1);
221
+    glActiveTexture(GL_TEXTURE0);
222
+    glBindTexture(GL_TEXTURE_3D, texture1);
223
+    GLTEST_EXPECT_EXCEPTION(false,
224
+        Shader({"tests/texture.vert"}).use().texture("texture0", texture1, GL_TEXTURE_2D),
225
+        "Failed to bind texture 2 to target GL_TEXTURE_2D; "
226
+        "got error GL_INVALID_OPERATION (wrong target?)."
227
+    )
228
+
211 229
     auto all = Shader({
212 230
         "tests/all.vert",
213 231
         "tests/all.frag",