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