... | ... |
@@ -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): |
... | ... |
@@ -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", |