| ... | ... |
@@ -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 |
+} |
| ... | ... |
@@ -33,6 +33,7 @@ GLSHADER_INIT_(Shader::root_, {})
|
| 33 | 33 |
GLSHADER_INIT_(Shader::defines_, {})
|
| 34 | 34 |
GLSHADER_INIT_(Shader::verts_, {})
|
| 35 | 35 |
GLSHADER_INIT_(Shader::frags_, {})
|
| 36 |
+GLSHADER_INIT_(Shader::uniform_buffers_, {})
|
|
| 36 | 37 |
|
| 37 | 38 |
|
| 38 | 39 |
template<typename Type> |
| ... | ... |
@@ -402,7 +403,8 @@ Shader::Shader(Paths const & paths) |
| 402 | 403 |
program_name_{STR(
|
| 403 | 404 |
"shader program " << STR_JOIN(", ", it, "'" << it << "'", paths)
|
| 404 | 405 |
)}, |
| 405 |
- uniforms_{}
|
|
| 406 |
+ uniforms_{},
|
|
| 407 |
+ uniform_blocks_{}
|
|
| 406 | 408 |
{
|
| 407 | 409 |
// Get label limits. |
| 408 | 410 |
static auto const max_label_length = get_integer_<GLsizei>( |
| ... | ... |
@@ -560,6 +562,39 @@ Shader::Shader(Paths const & paths) |
| 560 | 562 |
uniforms_.emplace(name, Uniform{location, false});
|
| 561 | 563 |
} |
| 562 | 564 |
); |
| 565 |
+ |
|
| 566 |
+ // Initialize uniform blocks. |
|
| 567 |
+ for_variable_( |
|
| 568 |
+ program_, |
|
| 569 |
+ GL_ACTIVE_UNIFORM_BLOCKS, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, |
|
| 570 |
+ [&](GLuint index, GLsizei max_length, GLchar * name) |
|
| 571 |
+ {
|
|
| 572 |
+ if (!(GLEW_VERSION_3_1 || GLEW_ARB_uniform_buffer_object)) |
|
| 573 |
+ throw std::runtime_error{STR(
|
|
| 574 |
+ "Failed to initialize uniform block; " << |
|
| 575 |
+ "ARB_uniform_buffer_object not available." |
|
| 576 |
+ )}; |
|
| 577 |
+ glGetActiveUniformBlockName( |
|
| 578 |
+ program_, index, max_length, |
|
| 579 |
+ nullptr, name |
|
| 580 |
+ ); |
|
| 581 |
+ auto error = STR( |
|
| 582 |
+ "Failed to initialize uniform block '" << name << "' of " |
|
| 583 |
+ << program_name_ |
|
| 584 |
+ ); |
|
| 585 |
+ auto size = GLsizei{};
|
|
| 586 |
+ glGetActiveUniformBlockiv( |
|
| 587 |
+ program_, index, GL_UNIFORM_BLOCK_DATA_SIZE, (GLint *)&size |
|
| 588 |
+ ); |
|
| 589 |
+ auto & uniform_buffer = *uniform_buffer_( |
|
| 590 |
+ error, name, false, size |
|
| 591 |
+ ); |
|
| 592 |
+ glUniformBlockBinding(program_, index, uniform_buffer.binding); |
|
| 593 |
+ uniform_blocks_.emplace( |
|
| 594 |
+ name, UniformBlock{uniform_buffer}
|
|
| 595 |
+ ); |
|
| 596 |
+ } |
|
| 597 |
+ ); |
|
| 563 | 598 |
} |
| 564 | 599 |
catch (...) |
| 565 | 600 |
{
|
| ... | ... |
@@ -573,9 +608,10 @@ Shader::Shader(Paths const & paths) |
| 573 | 608 |
|
| 574 | 609 |
Shader::Shader(Shader && other) noexcept |
| 575 | 610 |
: |
| 576 |
- program_ {other.program_},
|
|
| 577 |
- program_name_{std::move(other.program_name_)},
|
|
| 578 |
- uniforms_ {std::move(other.uniforms_)}
|
|
| 611 |
+ program_ {other.program_},
|
|
| 612 |
+ program_name_ {std::move(other.program_name_)},
|
|
| 613 |
+ uniforms_ {std::move(other.uniforms_)},
|
|
| 614 |
+ uniform_blocks_{std::move(other.uniform_blocks_)}
|
|
| 579 | 615 |
{
|
| 580 | 616 |
other.program_ = 0; |
| 581 | 617 |
} |
| ... | ... |
@@ -635,6 +671,14 @@ void Shader::validate_() const |
| 635 | 671 |
return uniform.set; |
| 636 | 672 |
} |
| 637 | 673 |
); |
| 674 |
+ |
|
| 675 |
+ // Validate uniform blocks. |
|
| 676 |
+ uniforms_validate_(validate_error, "uniform block", uniform_blocks_, |
|
| 677 |
+ [](UniformBlock const & uniform_block) |
|
| 678 |
+ {
|
|
| 679 |
+ return uniform_block.buffer.set; |
|
| 680 |
+ } |
|
| 681 |
+ ); |
|
| 638 | 682 |
} |
| 639 | 683 |
|
| 640 | 684 |
|
| ... | ... |
@@ -665,11 +709,160 @@ Shader::Uniform * Shader::uniform_( |
| 665 | 709 |
|
| 666 | 710 |
// Error if required. |
| 667 | 711 |
if (required) |
| 712 |
+ {
|
|
| 713 |
+ auto error_hint = std::string{};
|
|
| 714 |
+ if (uniform_blocks_.find(name) != uniform_blocks_.end()) |
|
| 715 |
+ error_hint = " (did you mean the uniform block?)"; |
|
| 716 |
+ throw std::runtime_error{STR(
|
|
| 717 |
+ error << "; " << |
|
| 718 |
+ "uniform required but not found" << error_hint << "." |
|
| 719 |
+ )}; |
|
| 720 |
+ } |
|
| 721 |
+ |
|
| 722 |
+ // Return. |
|
| 723 |
+ return nullptr; |
|
| 724 |
+} |
|
| 725 |
+ |
|
| 726 |
+ |
|
| 727 |
+Shader::UniformBlock * Shader::uniform_block_( |
|
| 728 |
+ std::string const & error, |
|
| 729 |
+ std::string const & name, |
|
| 730 |
+ bool required, |
|
| 731 |
+ GLsizeiptr size |
|
| 732 |
+) |
|
| 733 |
+{
|
|
| 734 |
+ // Return if found. |
|
| 735 |
+ auto it = uniform_blocks_.find(name); |
|
| 736 |
+ if (it != uniform_blocks_.end()) |
|
| 737 |
+ {
|
|
| 738 |
+ auto & uniform_block = it->second; |
|
| 739 |
+ if (size != uniform_block.buffer.size) |
|
| 740 |
+ throw std::runtime_error{STR(
|
|
| 741 |
+ error << "; " << |
|
| 742 |
+ "expected size " << uniform_block.buffer.size << " but got " << |
|
| 743 |
+ size << "." |
|
| 744 |
+ )}; |
|
| 745 |
+ return &uniform_block; |
|
| 746 |
+ } |
|
| 747 |
+ |
|
| 748 |
+ // Error if required. |
|
| 749 |
+ if (required) |
|
| 750 |
+ {
|
|
| 751 |
+ auto error_hint = std::string{};
|
|
| 752 |
+ if (uniforms_.find(name) != uniforms_.end()) |
|
| 753 |
+ error_hint = " (did you mean the uniform?)"; |
|
| 668 | 754 |
throw std::runtime_error{STR(
|
| 669 | 755 |
error << "; " << |
| 670 |
- "uniform required but not found." |
|
| 756 |
+ "uniform block required but not found" << error_hint << "." |
|
| 671 | 757 |
)}; |
| 758 |
+ } |
|
| 672 | 759 |
|
| 673 | 760 |
// Return. |
| 674 | 761 |
return nullptr; |
| 675 | 762 |
} |
| 763 |
+ |
|
| 764 |
+ |
|
| 765 |
+Shader::UniformBuffer * Shader::uniform_buffer_( |
|
| 766 |
+ std::string const & error, |
|
| 767 |
+ std::string const & name, |
|
| 768 |
+ bool required, |
|
| 769 |
+ GLsizeiptr size |
|
| 770 |
+) |
|
| 771 |
+{
|
|
| 772 |
+ // Get uniform block / buffer limits. |
|
| 773 |
+ static auto const max_uniform_block_size = get_integer_<GLuint>( |
|
| 774 |
+ GL_MAX_UNIFORM_BLOCK_SIZE |
|
| 775 |
+ ); |
|
| 776 |
+ static auto const max_uniform_buffer_bindings = get_integer_<GLuint>( |
|
| 777 |
+ GL_MAX_UNIFORM_BUFFER_BINDINGS |
|
| 778 |
+ ); |
|
| 779 |
+ |
|
| 780 |
+ // Define next binding. |
|
| 781 |
+ static auto next_uniform_buffer_binding_ = GLuint{0};
|
|
| 782 |
+ |
|
| 783 |
+ // Return if found. |
|
| 784 |
+ auto it = uniform_buffers_.find(name); |
|
| 785 |
+ if (it != uniform_buffers_.end()) |
|
| 786 |
+ {
|
|
| 787 |
+ auto & uniform_buffer = it->second; |
|
| 788 |
+ if (size != uniform_buffer.size) |
|
| 789 |
+ throw std::runtime_error{STR(
|
|
| 790 |
+ error << "; " << |
|
| 791 |
+ "expected size " << uniform_buffer.size << " but got " << size |
|
| 792 |
+ << "." |
|
| 793 |
+ )}; |
|
| 794 |
+ return &uniform_buffer; |
|
| 795 |
+ } |
|
| 796 |
+ |
|
| 797 |
+ // Error if required. |
|
| 798 |
+ if (required) |
|
| 799 |
+ throw std::runtime_error{STR(
|
|
| 800 |
+ error << "; " << |
|
| 801 |
+ "uniform buffer required but not found." |
|
| 802 |
+ )}; |
|
| 803 |
+ |
|
| 804 |
+ // Set create error. |
|
| 805 |
+ auto create_error = STR( |
|
| 806 |
+ error << ":\n" << |
|
| 807 |
+ "Failed to create uniform buffer '" << name << "'" |
|
| 808 |
+ ); |
|
| 809 |
+ |
|
| 810 |
+ // Check availability. |
|
| 811 |
+ if (!(GLEW_VERSION_3_1 || GLEW_ARB_uniform_buffer_object)) |
|
| 812 |
+ throw std::runtime_error{STR(
|
|
| 813 |
+ create_error << "; " << |
|
| 814 |
+ "ARB_uniform_buffer_object not available." |
|
| 815 |
+ )}; |
|
| 816 |
+ |
|
| 817 |
+ // Create storage. |
|
| 818 |
+ auto emplace = uniform_buffers_.emplace(name, UniformBuffer{});
|
|
| 819 |
+ if (!emplace.second) |
|
| 820 |
+ throw std::runtime_error{STR(
|
|
| 821 |
+ create_error << "; " << |
|
| 822 |
+ "already exists." |
|
| 823 |
+ )}; |
|
| 824 |
+ auto & uniform_buffer = emplace.first->second; |
|
| 825 |
+ |
|
| 826 |
+ // Check for errors. |
|
| 827 |
+ if (size > max_uniform_block_size) |
|
| 828 |
+ throw std::runtime_error{STR(
|
|
| 829 |
+ create_error << "; " << |
|
| 830 |
+ "buffer has size " << size << " but max size is " << |
|
| 831 |
+ max_uniform_block_size << "." |
|
| 832 |
+ )}; |
|
| 833 |
+ if (next_uniform_buffer_binding_ >= max_uniform_buffer_bindings) |
|
| 834 |
+ throw std::runtime_error{STR(
|
|
| 835 |
+ create_error << "; " << |
|
| 836 |
+ "buffer would have binding " << next_uniform_buffer_binding_ << |
|
| 837 |
+ " but max bindings is " << max_uniform_buffer_bindings << "." |
|
| 838 |
+ )}; |
|
| 839 |
+ |
|
| 840 |
+ // Generate and bind. |
|
| 841 |
+ glGenBuffers(1, &uniform_buffer.buffer); |
|
| 842 |
+ glBindBuffer(GL_UNIFORM_BUFFER, uniform_buffer.buffer), |
|
| 843 |
+ |
|
| 844 |
+ // Allocate size. |
|
| 845 |
+ uniform_buffer.size = size; |
|
| 846 |
+ error_(create_error, "unprocessed previous error"); |
|
| 847 |
+ glBufferData( |
|
| 848 |
+ GL_UNIFORM_BUFFER, |
|
| 849 |
+ uniform_buffer.size, |
|
| 850 |
+ nullptr, |
|
| 851 |
+ GL_DYNAMIC_DRAW |
|
| 852 |
+ ); |
|
| 853 |
+ error_(create_error); |
|
| 854 |
+ |
|
| 855 |
+ // Allocate binding and bind. |
|
| 856 |
+ uniform_buffer.binding = next_uniform_buffer_binding_++; |
|
| 857 |
+ glBindBufferBase( |
|
| 858 |
+ GL_UNIFORM_BUFFER, |
|
| 859 |
+ uniform_buffer.binding, |
|
| 860 |
+ uniform_buffer.buffer |
|
| 861 |
+ ); |
|
| 862 |
+ |
|
| 863 |
+ // Mark as unset. |
|
| 864 |
+ uniform_buffer.set = false; |
|
| 865 |
+ |
|
| 866 |
+ // Return |
|
| 867 |
+ return &uniform_buffer; |
|
| 868 |
+} |
| ... | ... |
@@ -5,6 +5,7 @@ |
| 5 | 5 |
#include <cerrno> |
| 6 | 6 |
#include <cstring> |
| 7 | 7 |
#include <fstream> |
| 8 |
+#include <ios> |
|
| 8 | 9 |
#include <list> |
| 9 | 10 |
#include <memory> |
| 10 | 11 |
#include <regex> |
| ... | ... |
@@ -44,6 +45,28 @@ static Type get_integer_(GLenum name, bool supported = true) |
| 44 | 45 |
} |
| 45 | 46 |
|
| 46 | 47 |
|
| 48 |
+void Shader::error_( |
|
| 49 |
+ std::string const & error, |
|
| 50 |
+ std::string const & error_hint |
|
| 51 |
+) |
|
| 52 |
+{
|
|
| 53 |
+ auto gl_error = glGetError(); |
|
| 54 |
+ if (gl_error != GL_NO_ERROR) |
|
| 55 |
+ throw std::runtime_error{STR(
|
|
| 56 |
+ error << "; " << |
|
| 57 |
+ "got error " << ( |
|
| 58 |
+ STR_COND(gl_error, GL_INVALID_ENUM) |
|
| 59 |
+ STR_COND(gl_error, GL_INVALID_VALUE) |
|
| 60 |
+ STR_COND(gl_error, GL_INVALID_OPERATION) |
|
| 61 |
+ STR_COND(gl_error, GL_OUT_OF_MEMORY) |
|
| 62 |
+ STR(std::hex << std::showbase << gl_error) |
|
| 63 |
+ ) << |
|
| 64 |
+ (error_hint.empty() ? "" : STR(" (" << error_hint << ")")) <<
|
|
| 65 |
+ "." |
|
| 66 |
+ )}; |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+ |
|
| 47 | 70 |
static void info_log_action_( |
| 48 | 71 |
std::string const & error, |
| 49 | 72 |
void (action)(GLuint object), |
| ... | ... |
@@ -378,7 +401,8 @@ Shader::Shader(Paths const & paths) |
| 378 | 401 |
program_{0},
|
| 379 | 402 |
program_name_{STR(
|
| 380 | 403 |
"shader program " << STR_JOIN(", ", it, "'" << it << "'", paths)
|
| 381 |
- )} |
|
| 404 |
+ )}, |
|
| 405 |
+ uniforms_{}
|
|
| 382 | 406 |
{
|
| 383 | 407 |
// Get label limits. |
| 384 | 408 |
static auto const max_label_length = get_integer_<GLsizei>( |
| ... | ... |
@@ -518,6 +542,24 @@ Shader::Shader(Paths const & paths) |
| 518 | 542 |
)}; |
| 519 | 543 |
} |
| 520 | 544 |
); |
| 545 |
+ |
|
| 546 |
+ // Initialize uniforms. |
|
| 547 |
+ for_variable_( |
|
| 548 |
+ program_, |
|
| 549 |
+ GL_ACTIVE_UNIFORMS, GL_ACTIVE_UNIFORM_MAX_LENGTH, |
|
| 550 |
+ [&](GLuint index, GLsizei max_length, GLchar * name) |
|
| 551 |
+ {
|
|
| 552 |
+ GLint size{};
|
|
| 553 |
+ GLenum type{};
|
|
| 554 |
+ glGetActiveUniform( |
|
| 555 |
+ program_, index, max_length, |
|
| 556 |
+ nullptr, &size, &type, name |
|
| 557 |
+ ); |
|
| 558 |
+ auto location = glGetUniformLocation(program_, name); |
|
| 559 |
+ if (location != -1) |
|
| 560 |
+ uniforms_.emplace(name, Uniform{location, false});
|
|
| 561 |
+ } |
|
| 562 |
+ ); |
|
| 521 | 563 |
} |
| 522 | 564 |
catch (...) |
| 523 | 565 |
{
|
| ... | ... |
@@ -532,7 +574,8 @@ Shader::Shader(Paths const & paths) |
| 532 | 574 |
Shader::Shader(Shader && other) noexcept |
| 533 | 575 |
: |
| 534 | 576 |
program_ {other.program_},
|
| 535 |
- program_name_{std::move(other.program_name_)}
|
|
| 577 |
+ program_name_{std::move(other.program_name_)},
|
|
| 578 |
+ uniforms_ {std::move(other.uniforms_)}
|
|
| 536 | 579 |
{
|
| 537 | 580 |
other.program_ = 0; |
| 538 | 581 |
} |
| ... | ... |
@@ -545,6 +588,31 @@ Shader::~Shader() |
| 545 | 588 |
} |
| 546 | 589 |
|
| 547 | 590 |
|
| 591 |
+template<typename Uniforms, typename Set> |
|
| 592 |
+static void uniforms_validate_( |
|
| 593 |
+ std::string const & error, |
|
| 594 |
+ std::string const & uniform_type, |
|
| 595 |
+ Uniforms uniforms, |
|
| 596 |
+ Set set |
|
| 597 |
+) |
|
| 598 |
+{
|
|
| 599 |
+ // Find. |
|
| 600 |
+ auto it = std::find_if(uniforms.begin(), uniforms.end(), |
|
| 601 |
+ [&](typename Uniforms::value_type const & it) |
|
| 602 |
+ {
|
|
| 603 |
+ return !set(it.second); |
|
| 604 |
+ } |
|
| 605 |
+ ); |
|
| 606 |
+ |
|
| 607 |
+ // Error if not found. |
|
| 608 |
+ if (it != uniforms.end()) |
|
| 609 |
+ throw std::runtime_error{STR(
|
|
| 610 |
+ error << "; " << |
|
| 611 |
+ uniform_type << " '" << it->first << "' not set." |
|
| 612 |
+ )}; |
|
| 613 |
+} |
|
| 614 |
+ |
|
| 615 |
+ |
|
| 548 | 616 |
void Shader::validate_() const |
| 549 | 617 |
{
|
| 550 | 618 |
// Set error. |
| ... | ... |
@@ -559,6 +627,14 @@ void Shader::validate_() const |
| 559 | 627 |
|
| 560 | 628 |
// Assert current. |
| 561 | 629 |
current_(validate_error); |
| 630 |
+ |
|
| 631 |
+ // Validate uniforms. |
|
| 632 |
+ uniforms_validate_(validate_error, "uniform", uniforms_, |
|
| 633 |
+ [](Uniform const & uniform) |
|
| 634 |
+ {
|
|
| 635 |
+ return uniform.set; |
|
| 636 |
+ } |
|
| 637 |
+ ); |
|
| 562 | 638 |
} |
| 563 | 639 |
|
| 564 | 640 |
|
| ... | ... |
@@ -571,3 +647,29 @@ void Shader::current_(std::string const & error) const |
| 571 | 647 |
"shader program not current." |
| 572 | 648 |
)}; |
| 573 | 649 |
} |
| 650 |
+ |
|
| 651 |
+ |
|
| 652 |
+Shader::Uniform * Shader::uniform_( |
|
| 653 |
+ std::string const & error, |
|
| 654 |
+ std::string const & name, |
|
| 655 |
+ bool required |
|
| 656 |
+) |
|
| 657 |
+{
|
|
| 658 |
+ // Return if found. |
|
| 659 |
+ auto it = uniforms_.find(name); |
|
| 660 |
+ if (it != uniforms_.end()) |
|
| 661 |
+ {
|
|
| 662 |
+ auto & uniform = it->second; |
|
| 663 |
+ return &uniform; |
|
| 664 |
+ } |
|
| 665 |
+ |
|
| 666 |
+ // Error if required. |
|
| 667 |
+ if (required) |
|
| 668 |
+ throw std::runtime_error{STR(
|
|
| 669 |
+ error << "; " << |
|
| 670 |
+ "uniform required but not found." |
|
| 671 |
+ )}; |
|
| 672 |
+ |
|
| 673 |
+ // Return. |
|
| 674 |
+ return nullptr; |
|
| 675 |
+} |
| ... | ... |
@@ -23,10 +23,15 @@ |
| 23 | 23 |
using Here = std::tuple<std::string, int, std::string>; |
| 24 | 24 |
|
| 25 | 25 |
|
| 26 |
+constexpr auto max_length_workaround = 4096; |
|
| 27 |
+ |
|
| 28 |
+ |
|
| 26 | 29 |
// NOLINTNEXTLINE |
| 27 | 30 |
#define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT; |
| 28 | 31 |
GLSHADER_INIT_(Shader::root_, {})
|
| 29 | 32 |
GLSHADER_INIT_(Shader::defines_, {})
|
| 33 |
+GLSHADER_INIT_(Shader::verts_, {})
|
|
| 34 |
+GLSHADER_INIT_(Shader::frags_, {})
|
|
| 30 | 35 |
|
| 31 | 36 |
|
| 32 | 37 |
template<typename Type> |
| ... | ... |
@@ -340,6 +345,34 @@ static std::string source_( |
| 340 | 345 |
} |
| 341 | 346 |
|
| 342 | 347 |
|
| 348 |
+template<typename Function> |
|
| 349 |
+static void for_variable_( |
|
| 350 |
+ GLuint program, |
|
| 351 |
+ GLenum count_enum, |
|
| 352 |
+ GLenum max_length_enum, |
|
| 353 |
+ Function function |
|
| 354 |
+) |
|
| 355 |
+{
|
|
| 356 |
+ // Get count. |
|
| 357 |
+ auto count = GLuint{};
|
|
| 358 |
+ glGetProgramiv(program, count_enum, (GLint *)&count); |
|
| 359 |
+ |
|
| 360 |
+ // Get max length. |
|
| 361 |
+ auto max_length = GLsizei{};
|
|
| 362 |
+ glGetProgramiv(program, max_length_enum, &max_length); |
|
| 363 |
+ |
|
| 364 |
+ // Work around driver bugs. |
|
| 365 |
+ if (max_length == 0 && count != 0) |
|
| 366 |
+ max_length = max_length_workaround; |
|
| 367 |
+ |
|
| 368 |
+ // Allocate and call function. |
|
| 369 |
+ // NOLINTNEXTLINE |
|
| 370 |
+ auto name = std::unique_ptr<GLchar[]>(new GLchar[max_length]); |
|
| 371 |
+ for (auto index = GLuint{0}; index < count; ++index)
|
|
| 372 |
+ function(index, max_length, &name[0]); |
|
| 373 |
+} |
|
| 374 |
+ |
|
| 375 |
+ |
|
| 343 | 376 |
Shader::Shader(Paths const & paths) |
| 344 | 377 |
: |
| 345 | 378 |
program_{0},
|
| ... | ... |
@@ -441,6 +474,19 @@ Shader::Shader(Paths const & paths) |
| 441 | 474 |
); |
| 442 | 475 |
} |
| 443 | 476 |
|
| 477 |
+ // Set vertex input locations. |
|
| 478 |
+ for (auto const & vert : verts_) |
|
| 479 |
+ glBindAttribLocation( |
|
| 480 |
+ program_, vert.second, vert.first.c_str() |
|
| 481 |
+ ); |
|
| 482 |
+ |
|
| 483 |
+ // Set fragment output locations. |
|
| 484 |
+ if (GLEW_VERSION_3_0) |
|
| 485 |
+ for (auto const & frag : frags_) |
|
| 486 |
+ glBindFragDataLocation( |
|
| 487 |
+ program_, frag.second, frag.first.c_str() |
|
| 488 |
+ ); |
|
| 489 |
+ |
|
| 444 | 490 |
// Link program. |
| 445 | 491 |
info_log_action_( |
| 446 | 492 |
STR("Failed to link " << program_name_),
|
| ... | ... |
@@ -451,6 +497,27 @@ Shader::Shader(Paths const & paths) |
| 451 | 497 |
// Detach shaders. |
| 452 | 498 |
for (auto const & shader : shaders) |
| 453 | 499 |
glDetachShader(program_, shader); |
| 500 |
+ |
|
| 501 |
+ // Initialize vertex inputs. |
|
| 502 |
+ for_variable_( |
|
| 503 |
+ program_, |
|
| 504 |
+ GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, |
|
| 505 |
+ [&](GLuint index, GLsizei max_length, GLchar * name) |
|
| 506 |
+ {
|
|
| 507 |
+ GLint size{};
|
|
| 508 |
+ GLenum type{};
|
|
| 509 |
+ glGetActiveAttrib( |
|
| 510 |
+ program_, index, max_length, |
|
| 511 |
+ nullptr, &size, &type, name |
|
| 512 |
+ ); |
|
| 513 |
+ auto location = glGetAttribLocation(program_, name); |
|
| 514 |
+ if (location != -1 && verts_.find(name) == verts_.end()) |
|
| 515 |
+ throw std::runtime_error{STR(
|
|
| 516 |
+ "Failed to initialize vertex input '" << name << |
|
| 517 |
+ "' of " << program_name_ << "." |
|
| 518 |
+ )}; |
|
| 519 |
+ } |
|
| 520 |
+ ); |
|
| 454 | 521 |
} |
| 455 | 522 |
catch (...) |
| 456 | 523 |
{
|
| ... | ... |
@@ -5,6 +5,7 @@ |
| 5 | 5 |
#include <cerrno> |
| 6 | 6 |
#include <cstring> |
| 7 | 7 |
#include <fstream> |
| 8 |
+#include <list> |
|
| 8 | 9 |
#include <memory> |
| 9 | 10 |
#include <regex> |
| 10 | 11 |
#include <sstream> |
| ... | ... |
@@ -81,21 +82,48 @@ static std::string source_( |
| 81 | 82 |
std::string const & error, |
| 82 | 83 |
std::string const & path, |
| 83 | 84 |
std::string const & root, |
| 84 |
- Shader::Defines const & defines |
|
| 85 |
+ Shader::Defines const & defines, |
|
| 86 |
+ std::string extension_behavior = {},
|
|
| 87 |
+ std::list<Here> included_by = {}
|
|
| 85 | 88 |
) |
| 86 | 89 |
{
|
| 87 | 90 |
// Set here error. |
| 88 |
- auto const here_error = [](Here const & here) |
|
| 91 |
+ auto const here_error = [&](std::list<Here> const & include_here) |
|
| 89 | 92 |
{
|
| 90 |
- return STR( |
|
| 91 |
- std::get<0>(here) << ":" << |
|
| 92 |
- std::get<1>(here) << ": " << |
|
| 93 |
- std::get<2>(here) |
|
| 93 |
+ return STR_JOIN( |
|
| 94 |
+ "\n", |
|
| 95 |
+ it, |
|
| 96 |
+ std::get<0>(it) << ":" << |
|
| 97 |
+ std::get<1>(it) << ": " << |
|
| 98 |
+ std::get<2>(it), |
|
| 99 |
+ include_here |
|
| 94 | 100 |
); |
| 95 | 101 |
}; |
| 96 | 102 |
|
| 103 |
+ // Set include helper. |
|
| 104 |
+ auto const include_here = [&](Here const & here) |
|
| 105 |
+ {
|
|
| 106 |
+ auto include_here = included_by; |
|
| 107 |
+ include_here.push_front(here); |
|
| 108 |
+ return include_here; |
|
| 109 |
+ }; |
|
| 110 |
+ |
|
| 97 | 111 |
// Set full path. |
| 98 |
- auto path_full = path; |
|
| 112 |
+ auto path_full = std::string{};
|
|
| 113 |
+ {
|
|
| 114 |
+ auto istream = std::istringstream(path); |
|
| 115 |
+ auto part = std::string{};
|
|
| 116 |
+ auto parts = std::vector<std::string>{};
|
|
| 117 |
+ parts.reserve((size_t)std::count(path.begin(), path.end(), '/') + 1); |
|
| 118 |
+ while (std::getline(istream, part, '/')) |
|
| 119 |
+ {
|
|
| 120 |
+ if (part == ".." && !parts.empty()) |
|
| 121 |
+ parts.pop_back(); |
|
| 122 |
+ if (part != ".." && part != ".") |
|
| 123 |
+ parts.push_back(std::move(part)); |
|
| 124 |
+ } |
|
| 125 |
+ path_full = STR_JOIN('/', it, it, parts);
|
|
| 126 |
+ } |
|
| 99 | 127 |
if (!root.empty()) |
| 100 | 128 |
path_full = STR(root << "/" << path_full); |
| 101 | 129 |
|
| ... | ... |
@@ -105,6 +133,7 @@ static std::string source_( |
| 105 | 133 |
throw std::runtime_error{STR(
|
| 106 | 134 |
error << "; " << |
| 107 | 135 |
"could not open file '" << path_full << "':\n" << |
| 136 |
+ here_error(included_by) << (included_by.empty() ? "" : ":\n") << |
|
| 108 | 137 |
std::strerror(errno) |
| 109 | 138 |
)}; |
| 110 | 139 |
|
| ... | ... |
@@ -112,16 +141,21 @@ static std::string source_( |
| 112 | 141 |
auto ostream = std::ostringstream{};
|
| 113 | 142 |
|
| 114 | 143 |
// Define parse regexes. |
| 115 |
- static auto const re_ignored = std::regex{R"(\s*//.*$)"};
|
|
| 116 |
- static auto const re_words = std::regex{R"((\w+(?:\s+\w+)*))"};
|
|
| 117 |
- static auto const re_version = std::regex{R"(\s*#\s*version\s*(.*))"};
|
|
| 144 |
+ static auto const re_ignored = std::regex{R"(\s*//.*$)"};
|
|
| 145 |
+ static auto const re_words = std::regex{R"((\w+(?:\s+\w+)*))"};
|
|
| 146 |
+ static auto const re_spec = std::regex{R"((\w+)\s*:\s*(\w+))"};
|
|
| 147 |
+ static auto const re_quoted = std::regex{R"((["<])([^">]*)([">]))"};
|
|
| 148 |
+ static auto const re_version = std::regex{R"(\s*#\s*version\s*(.*))"};
|
|
| 149 |
+ static auto const re_extension = std::regex{R"(\s*#\s*extension\s*(.*))"};
|
|
| 150 |
+ static auto const re_include = std::regex{R"(\s*#\s*include\s*(.*))"};
|
|
| 118 | 151 |
|
| 119 | 152 |
// Parse. |
| 120 |
- auto version_number = 0; |
|
| 121 |
- auto line_number = 0; |
|
| 122 |
- auto line = std::string{};
|
|
| 123 |
- auto match = std::smatch{};
|
|
| 124 |
- auto here = [&]() |
|
| 153 |
+ auto version_number = 0; |
|
| 154 |
+ auto extension_enabled = false; |
|
| 155 |
+ auto line_number = 0; |
|
| 156 |
+ auto line = std::string{};
|
|
| 157 |
+ auto match = std::smatch{};
|
|
| 158 |
+ auto here = [&]() |
|
| 125 | 159 |
{
|
| 126 | 160 |
return Here{path_full, line_number, line};
|
| 127 | 161 |
}; |
| ... | ... |
@@ -130,6 +164,16 @@ static std::string source_( |
| 130 | 164 |
// Remove ignored. |
| 131 | 165 |
auto const content = std::regex_replace(line, re_ignored, ""); |
| 132 | 166 |
|
| 167 |
+ // Output `#line`. |
|
| 168 |
+ auto const line_number_offset = version_number < 330 ? -1 : 0; |
|
| 169 |
+ if (GLEW_ARB_shading_language_include) |
|
| 170 |
+ if (!extension_behavior.empty() && extension_behavior != "disable") |
|
| 171 |
+ ostream |
|
| 172 |
+ << "#line" << " " |
|
| 173 |
+ << line_number + line_number_offset << " " |
|
| 174 |
+ << "\"" << path_full << "\"" |
|
| 175 |
+ << "\n"; |
|
| 176 |
+ |
|
| 133 | 177 |
// Process version. |
| 134 | 178 |
if (std::regex_match(content, match, re_version)) |
| 135 | 179 |
{
|
| ... | ... |
@@ -139,7 +183,7 @@ static std::string source_( |
| 139 | 183 |
throw std::runtime_error{STR(
|
| 140 | 184 |
error << "; " << |
| 141 | 185 |
"malformed #version:\n" << |
| 142 |
- here_error(here()) |
|
| 186 |
+ here_error(include_here(here())) |
|
| 143 | 187 |
)}; |
| 144 | 188 |
auto const version = match.str(1); |
| 145 | 189 |
|
| ... | ... |
@@ -148,7 +192,13 @@ static std::string source_( |
| 148 | 192 |
throw std::runtime_error{STR(
|
| 149 | 193 |
error << "; " << |
| 150 | 194 |
"found repeated #version:\n" << |
| 151 |
- here_error(here()) |
|
| 195 |
+ here_error(include_here(here())) |
|
| 196 |
+ )}; |
|
| 197 |
+ if (!included_by.empty()) |
|
| 198 |
+ throw std::runtime_error{STR(
|
|
| 199 |
+ error << "; " << |
|
| 200 |
+ "found #version in #include:\n" << |
|
| 201 |
+ here_error(include_here(here())) |
|
| 152 | 202 |
)}; |
| 153 | 203 |
|
| 154 | 204 |
// Process. |
| ... | ... |
@@ -156,6 +206,16 @@ static std::string source_( |
| 156 | 206 |
|
| 157 | 207 |
// Output. |
| 158 | 208 |
ostream << line << "\n"; |
| 209 |
+ if (GLEW_ARB_shading_language_include) |
|
| 210 |
+ {
|
|
| 211 |
+ if (extension_behavior.empty()) |
|
| 212 |
+ {
|
|
| 213 |
+ extension_behavior = "enable"; |
|
| 214 |
+ ostream |
|
| 215 |
+ << "#extension GL_ARB_shading_language_include : " |
|
| 216 |
+ << extension_behavior << "\n"; |
|
| 217 |
+ } |
|
| 218 |
+ } |
|
| 159 | 219 |
for (auto const & define : defines) |
| 160 | 220 |
ostream |
| 161 | 221 |
<< "#define " |
| ... | ... |
@@ -163,6 +223,103 @@ static std::string source_( |
| 163 | 223 |
<< define.second << "\n"; |
| 164 | 224 |
} |
| 165 | 225 |
|
| 226 |
+ // Process extension. |
|
| 227 |
+ else if (std::regex_match(content, match, re_extension)) |
|
| 228 |
+ {
|
|
| 229 |
+ // Parse. |
|
| 230 |
+ auto const spec = match.str(1); |
|
| 231 |
+ if (!std::regex_match(spec, match, re_spec)) |
|
| 232 |
+ throw std::runtime_error{STR(
|
|
| 233 |
+ error << "; " << |
|
| 234 |
+ "malformed #extension:\n" << |
|
| 235 |
+ here_error(include_here(here())) |
|
| 236 |
+ )}; |
|
| 237 |
+ auto const extension = match.str(1); |
|
| 238 |
+ auto const behavior = match.str(2); |
|
| 239 |
+ |
|
| 240 |
+ if (extension == "GL_ARB_shading_language_include") |
|
| 241 |
+ {
|
|
| 242 |
+ // Check for errors. |
|
| 243 |
+ if (!included_by.empty()) |
|
| 244 |
+ throw std::runtime_error{STR(
|
|
| 245 |
+ error << "; " << |
|
| 246 |
+ "found #extension GL_ARB_shading_language_include " << |
|
| 247 |
+ "in #include:\n" << |
|
| 248 |
+ here_error(include_here(here())) |
|
| 249 |
+ )}; |
|
| 250 |
+ |
|
| 251 |
+ // Process. |
|
| 252 |
+ extension_enabled = behavior != "disable"; |
|
| 253 |
+ extension_behavior = behavior; |
|
| 254 |
+ line = ""; |
|
| 255 |
+ } |
|
| 256 |
+ |
|
| 257 |
+ // Output. |
|
| 258 |
+ ostream << line << "\n"; |
|
| 259 |
+ } |
|
| 260 |
+ |
|
| 261 |
+ // Process include. |
|
| 262 |
+ else if (std::regex_match(content, match, re_include)) |
|
| 263 |
+ {
|
|
| 264 |
+ // Parse. |
|
| 265 |
+ auto const quoted = match.str(1); |
|
| 266 |
+ if (!std::regex_match(quoted, match, re_quoted)) |
|
| 267 |
+ throw std::runtime_error{STR(
|
|
| 268 |
+ error << "; " << |
|
| 269 |
+ "malformed #include:\n" << |
|
| 270 |
+ here_error(include_here(here())) |
|
| 271 |
+ )}; |
|
| 272 |
+ auto const quote_open = match.str(1); |
|
| 273 |
+ auto const include_path = match.str(2); |
|
| 274 |
+ auto const quote_close = match.str(3); |
|
| 275 |
+ |
|
| 276 |
+ // Check for errors. |
|
| 277 |
+ if (!( |
|
| 278 |
+ (quote_open == "\"" && quote_close == "\"") || |
|
| 279 |
+ (quote_open == "<" && quote_close == ">" ) |
|
| 280 |
+ )) |
|
| 281 |
+ throw std::runtime_error{STR(
|
|
| 282 |
+ error << "; " << |
|
| 283 |
+ "mismatched #include quotes '" << quote_open << "' and '" |
|
| 284 |
+ << quote_close << "':\n" << |
|
| 285 |
+ here_error(include_here(here())) |
|
| 286 |
+ )}; |
|
| 287 |
+ if (!extension_enabled && included_by.empty()) |
|
| 288 |
+ throw std::runtime_error{STR(
|
|
| 289 |
+ error << "; " << |
|
| 290 |
+ "#include found but #extension " << |
|
| 291 |
+ "GL_ARB_shading_language_include not enabled:\n" << |
|
| 292 |
+ here_error(include_here(here())) |
|
| 293 |
+ )}; |
|
| 294 |
+ |
|
| 295 |
+ // Process. |
|
| 296 |
+ auto source = std::string{};
|
|
| 297 |
+ if (included_by.end() == std::find( |
|
| 298 |
+ included_by.begin(), included_by.end(), here() |
|
| 299 |
+ )) |
|
| 300 |
+ {
|
|
| 301 |
+ auto include_path_full = include_path; |
|
| 302 |
+ if (quote_open == "\"") |
|
| 303 |
+ {
|
|
| 304 |
+ auto const pos = path.rfind('/');
|
|
| 305 |
+ if (pos != path.npos && pos != 0) |
|
| 306 |
+ include_path_full = STR( |
|
| 307 |
+ path.substr(0, pos + 1) << include_path |
|
| 308 |
+ ); |
|
| 309 |
+ } |
|
| 310 |
+ source = source_( |
|
| 311 |
+ error, |
|
| 312 |
+ include_path_full, root, |
|
| 313 |
+ defines, |
|
| 314 |
+ extension_behavior, |
|
| 315 |
+ include_here(here()) |
|
| 316 |
+ ); |
|
| 317 |
+ } |
|
| 318 |
+ |
|
| 319 |
+ // Output. |
|
| 320 |
+ ostream << source << "\n"; |
|
| 321 |
+ } |
|
| 322 |
+ |
|
| 166 | 323 |
// Non-processed line. |
| 167 | 324 |
else |
| 168 | 325 |
{
|
| ... | ... |
@@ -172,7 +329,7 @@ static std::string source_( |
| 172 | 329 |
} |
| 173 | 330 |
|
| 174 | 331 |
// Check for version. |
| 175 |
- if (!version_number) |
|
| 332 |
+ if (!version_number && included_by.empty()) |
|
| 176 | 333 |
throw std::runtime_error{STR(
|
| 177 | 334 |
error << "; " << |
| 178 | 335 |
"found no #version." |
| ... | ... |
@@ -25,6 +25,7 @@ using Here = std::tuple<std::string, int, std::string>; |
| 25 | 25 |
// NOLINTNEXTLINE |
| 26 | 26 |
#define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT; |
| 27 | 27 |
GLSHADER_INIT_(Shader::root_, {})
|
| 28 |
+GLSHADER_INIT_(Shader::defines_, {})
|
|
| 28 | 29 |
|
| 29 | 30 |
|
| 30 | 31 |
template<typename Type> |
| ... | ... |
@@ -79,7 +80,8 @@ static void info_log_action_( |
| 79 | 80 |
static std::string source_( |
| 80 | 81 |
std::string const & error, |
| 81 | 82 |
std::string const & path, |
| 82 |
- std::string const & root |
|
| 83 |
+ std::string const & root, |
|
| 84 |
+ Shader::Defines const & defines |
|
| 83 | 85 |
) |
| 84 | 86 |
{
|
| 85 | 87 |
// Set here error. |
| ... | ... |
@@ -154,6 +156,11 @@ static std::string source_( |
| 154 | 156 |
|
| 155 | 157 |
// Output. |
| 156 | 158 |
ostream << line << "\n"; |
| 159 |
+ for (auto const & define : defines) |
|
| 160 |
+ ostream |
|
| 161 |
+ << "#define " |
|
| 162 |
+ << define.first << " " |
|
| 163 |
+ << define.second << "\n"; |
|
| 157 | 164 |
} |
| 158 | 165 |
|
| 159 | 166 |
// Non-processed line. |
| ... | ... |
@@ -263,7 +270,7 @@ Shader::Shader(Paths const & paths) |
| 263 | 270 |
|
| 264 | 271 |
// Set shader source. |
| 265 | 272 |
auto const source_error = STR("Failed to source " << shader_name);
|
| 266 |
- auto const source = source_(source_error, path, root_); |
|
| 273 |
+ auto const source = source_(source_error, path, root_, defines_); |
|
| 267 | 274 |
auto const sources = std::array<char const *, 1>{{
|
| 268 | 275 |
source.c_str() |
| 269 | 276 |
}}; |
| ... | ... |
@@ -6,8 +6,11 @@ |
| 6 | 6 |
#include <cstring> |
| 7 | 7 |
#include <fstream> |
| 8 | 8 |
#include <memory> |
| 9 |
+#include <regex> |
|
| 10 |
+#include <sstream> |
|
| 9 | 11 |
#include <stdexcept> |
| 10 | 12 |
#include <string> |
| 13 |
+#include <tuple> |
|
| 11 | 14 |
#include <utility> |
| 12 | 15 |
#include <vector> |
| 13 | 16 |
|
| ... | ... |
@@ -16,6 +19,9 @@ |
| 16 | 19 |
#include <str.hpp> |
| 17 | 20 |
|
| 18 | 21 |
|
| 22 |
+using Here = std::tuple<std::string, int, std::string>; |
|
| 23 |
+ |
|
| 24 |
+ |
|
| 19 | 25 |
// NOLINTNEXTLINE |
| 20 | 26 |
#define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT; |
| 21 | 27 |
GLSHADER_INIT_(Shader::root_, {})
|
| ... | ... |
@@ -70,6 +76,106 @@ static void info_log_action_( |
| 70 | 76 |
} |
| 71 | 77 |
|
| 72 | 78 |
|
| 79 |
+static std::string source_( |
|
| 80 |
+ std::string const & error, |
|
| 81 |
+ std::string const & path, |
|
| 82 |
+ std::string const & root |
|
| 83 |
+) |
|
| 84 |
+{
|
|
| 85 |
+ // Set here error. |
|
| 86 |
+ auto const here_error = [](Here const & here) |
|
| 87 |
+ {
|
|
| 88 |
+ return STR( |
|
| 89 |
+ std::get<0>(here) << ":" << |
|
| 90 |
+ std::get<1>(here) << ": " << |
|
| 91 |
+ std::get<2>(here) |
|
| 92 |
+ ); |
|
| 93 |
+ }; |
|
| 94 |
+ |
|
| 95 |
+ // Set full path. |
|
| 96 |
+ auto path_full = path; |
|
| 97 |
+ if (!root.empty()) |
|
| 98 |
+ path_full = STR(root << "/" << path_full); |
|
| 99 |
+ |
|
| 100 |
+ // Define and open input stream. |
|
| 101 |
+ auto istream = std::ifstream{path_full};
|
|
| 102 |
+ if (!istream) |
|
| 103 |
+ throw std::runtime_error{STR(
|
|
| 104 |
+ error << "; " << |
|
| 105 |
+ "could not open file '" << path_full << "':\n" << |
|
| 106 |
+ std::strerror(errno) |
|
| 107 |
+ )}; |
|
| 108 |
+ |
|
| 109 |
+ // Define output stream. |
|
| 110 |
+ auto ostream = std::ostringstream{};
|
|
| 111 |
+ |
|
| 112 |
+ // Define parse regexes. |
|
| 113 |
+ static auto const re_ignored = std::regex{R"(\s*//.*$)"};
|
|
| 114 |
+ static auto const re_words = std::regex{R"((\w+(?:\s+\w+)*))"};
|
|
| 115 |
+ static auto const re_version = std::regex{R"(\s*#\s*version\s*(.*))"};
|
|
| 116 |
+ |
|
| 117 |
+ // Parse. |
|
| 118 |
+ auto version_number = 0; |
|
| 119 |
+ auto line_number = 0; |
|
| 120 |
+ auto line = std::string{};
|
|
| 121 |
+ auto match = std::smatch{};
|
|
| 122 |
+ auto here = [&]() |
|
| 123 |
+ {
|
|
| 124 |
+ return Here{path_full, line_number, line};
|
|
| 125 |
+ }; |
|
| 126 |
+ while (++line_number, std::getline(istream, line)) |
|
| 127 |
+ {
|
|
| 128 |
+ // Remove ignored. |
|
| 129 |
+ auto const content = std::regex_replace(line, re_ignored, ""); |
|
| 130 |
+ |
|
| 131 |
+ // Process version. |
|
| 132 |
+ if (std::regex_match(content, match, re_version)) |
|
| 133 |
+ {
|
|
| 134 |
+ // Parse. |
|
| 135 |
+ auto const words = match.str(1); |
|
| 136 |
+ if (!std::regex_match(words, match, re_words)) |
|
| 137 |
+ throw std::runtime_error{STR(
|
|
| 138 |
+ error << "; " << |
|
| 139 |
+ "malformed #version:\n" << |
|
| 140 |
+ here_error(here()) |
|
| 141 |
+ )}; |
|
| 142 |
+ auto const version = match.str(1); |
|
| 143 |
+ |
|
| 144 |
+ // Check for errors. |
|
| 145 |
+ if (version_number) |
|
| 146 |
+ throw std::runtime_error{STR(
|
|
| 147 |
+ error << "; " << |
|
| 148 |
+ "found repeated #version:\n" << |
|
| 149 |
+ here_error(here()) |
|
| 150 |
+ )}; |
|
| 151 |
+ |
|
| 152 |
+ // Process. |
|
| 153 |
+ version_number = std::stoi(version); |
|
| 154 |
+ |
|
| 155 |
+ // Output. |
|
| 156 |
+ ostream << line << "\n"; |
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ // Non-processed line. |
|
| 160 |
+ else |
|
| 161 |
+ {
|
|
| 162 |
+ // Output. |
|
| 163 |
+ ostream << line << "\n"; |
|
| 164 |
+ } |
|
| 165 |
+ } |
|
| 166 |
+ |
|
| 167 |
+ // Check for version. |
|
| 168 |
+ if (!version_number) |
|
| 169 |
+ throw std::runtime_error{STR(
|
|
| 170 |
+ error << "; " << |
|
| 171 |
+ "found no #version." |
|
| 172 |
+ )}; |
|
| 173 |
+ |
|
| 174 |
+ // Return. |
|
| 175 |
+ return ostream.str(); |
|
| 176 |
+} |
|
| 177 |
+ |
|
| 178 |
+ |
|
| 73 | 179 |
Shader::Shader(Paths const & paths) |
| 74 | 180 |
: |
| 75 | 181 |
program_{0},
|
| ... | ... |
@@ -157,17 +263,7 @@ Shader::Shader(Paths const & paths) |
| 157 | 263 |
|
| 158 | 264 |
// Set shader source. |
| 159 | 265 |
auto const source_error = STR("Failed to source " << shader_name);
|
| 160 |
- auto path_full = path; |
|
| 161 |
- if (!root_.empty()) |
|
| 162 |
- path_full = STR(root_ << "/" << path_full); |
|
| 163 |
- auto source_istream = std::ifstream{path_full};
|
|
| 164 |
- if (!source_istream) |
|
| 165 |
- throw std::runtime_error{STR(
|
|
| 166 |
- source_error << "; " << |
|
| 167 |
- "could not open file '" << path_full << "':\n" << |
|
| 168 |
- std::strerror(errno) |
|
| 169 |
- )}; |
|
| 170 |
- auto const source = STR(source_istream.rdbuf()); |
|
| 266 |
+ auto const source = source_(source_error, path, root_); |
|
| 171 | 267 |
auto const sources = std::array<char const *, 1>{{
|
| 172 | 268 |
source.c_str() |
| 173 | 269 |
}}; |
| ... | ... |
@@ -229,4 +229,18 @@ void Shader::validate_() const |
| 229 | 229 |
glValidateProgram, program_, |
| 230 | 230 |
GL_VALIDATE_STATUS, glGetProgramiv, glGetProgramInfoLog |
| 231 | 231 |
); |
| 232 |
+ |
|
| 233 |
+ // Assert current. |
|
| 234 |
+ current_(validate_error); |
|
| 235 |
+} |
|
| 236 |
+ |
|
| 237 |
+ |
|
| 238 |
+void Shader::current_(std::string const & error) const |
|
| 239 |
+{
|
|
| 240 |
+ // Error if not current. |
|
| 241 |
+ if (get_integer_<GLuint>(GL_CURRENT_PROGRAM) != program_) |
|
| 242 |
+ throw std::runtime_error{STR(
|
|
| 243 |
+ error << "; " << |
|
| 244 |
+ "shader program not current." |
|
| 245 |
+ )}; |
|
| 232 | 246 |
} |
| ... | ... |
@@ -216,3 +216,17 @@ Shader::~Shader() |
| 216 | 216 |
if (program_) |
| 217 | 217 |
glDeleteProgram(program_); |
| 218 | 218 |
} |
| 219 |
+ |
|
| 220 |
+ |
|
| 221 |
+void Shader::validate_() const |
|
| 222 |
+{
|
|
| 223 |
+ // Set error. |
|
| 224 |
+ auto const validate_error = STR("Failed to validate " << program_name_);
|
|
| 225 |
+ |
|
| 226 |
+ // Validate program. |
|
| 227 |
+ info_log_action_( |
|
| 228 |
+ validate_error, |
|
| 229 |
+ glValidateProgram, program_, |
|
| 230 |
+ GL_VALIDATE_STATUS, glGetProgramiv, glGetProgramInfoLog |
|
| 231 |
+ ); |
|
| 232 |
+} |
| ... | ... |
@@ -0,0 +1,218 @@ |
| 1 |
+#include <glshader.hpp> |
|
| 2 |
+ |
|
| 3 |
+#include <algorithm> |
|
| 4 |
+#include <array> |
|
| 5 |
+#include <cerrno> |
|
| 6 |
+#include <cstring> |
|
| 7 |
+#include <fstream> |
|
| 8 |
+#include <memory> |
|
| 9 |
+#include <stdexcept> |
|
| 10 |
+#include <string> |
|
| 11 |
+#include <utility> |
|
| 12 |
+#include <vector> |
|
| 13 |
+ |
|
| 14 |
+#include <GL/glew.h> |
|
| 15 |
+ |
|
| 16 |
+#include <str.hpp> |
|
| 17 |
+ |
|
| 18 |
+ |
|
| 19 |
+// NOLINTNEXTLINE |
|
| 20 |
+#define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT; |
|
| 21 |
+GLSHADER_INIT_(Shader::root_, {})
|
|
| 22 |
+ |
|
| 23 |
+ |
|
| 24 |
+template<typename Type> |
|
| 25 |
+static Type get_integer_(GLenum name, bool supported = true) |
|
| 26 |
+{
|
|
| 27 |
+ auto data = GLint{};
|
|
| 28 |
+ if (supported) |
|
| 29 |
+ glGetIntegerv(name, &data); |
|
| 30 |
+ return (Type)data; |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+ |
|
| 34 |
+static void info_log_action_( |
|
| 35 |
+ std::string const & error, |
|
| 36 |
+ void (action)(GLuint object), |
|
| 37 |
+ GLuint object, |
|
| 38 |
+ GLenum status_enum, |
|
| 39 |
+ void (GLAPIENTRY * getObjectiv)( |
|
| 40 |
+ GLuint object, GLenum pname, GLint * params |
|
| 41 |
+ ), |
|
| 42 |
+ void (GLAPIENTRY * getObjectInfoLog)( |
|
| 43 |
+ GLuint object, GLsizei max_length, GLsizei * length, GLchar * info_log |
|
| 44 |
+ ) |
|
| 45 |
+) |
|
| 46 |
+{
|
|
| 47 |
+ // Perform action. |
|
| 48 |
+ action(object); |
|
| 49 |
+ |
|
| 50 |
+ // Check status. |
|
| 51 |
+ auto status = GLint{};
|
|
| 52 |
+ getObjectiv(object, status_enum, &status); |
|
| 53 |
+ if (status) |
|
| 54 |
+ return; |
|
| 55 |
+ |
|
| 56 |
+ // Get info log length. |
|
| 57 |
+ auto info_log_length = GLint{};
|
|
| 58 |
+ getObjectiv(object, GL_INFO_LOG_LENGTH, &info_log_length); |
|
| 59 |
+ |
|
| 60 |
+ // Get info log content. |
|
| 61 |
+ // NOLINTNEXTLINE |
|
| 62 |
+ auto info_log = std::unique_ptr<GLchar[]>(new GLchar[info_log_length]); |
|
| 63 |
+ if (info_log_length) |
|
| 64 |
+ getObjectInfoLog(object, info_log_length, nullptr, &info_log[0]); |
|
| 65 |
+ |
|
| 66 |
+ // Throw. |
|
| 67 |
+ throw std::runtime_error{STR(
|
|
| 68 |
+ error << (!info_log_length ? "." : STR(":\n" << &info_log[0]))
|
|
| 69 |
+ )}; |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+ |
|
| 73 |
+Shader::Shader(Paths const & paths) |
|
| 74 |
+: |
|
| 75 |
+ program_{0},
|
|
| 76 |
+ program_name_{STR(
|
|
| 77 |
+ "shader program " << STR_JOIN(", ", it, "'" << it << "'", paths)
|
|
| 78 |
+ )} |
|
| 79 |
+{
|
|
| 80 |
+ // Get label limits. |
|
| 81 |
+ static auto const max_label_length = get_integer_<GLsizei>( |
|
| 82 |
+ GL_MAX_LABEL_LENGTH, GLEW_VERSION_4_3 || GLEW_KHR_debug |
|
| 83 |
+ ); |
|
| 84 |
+ |
|
| 85 |
+ try |
|
| 86 |
+ {
|
|
| 87 |
+ // Create program. |
|
| 88 |
+ program_ = glCreateProgram(); |
|
| 89 |
+ if (!program_) |
|
| 90 |
+ throw std::runtime_error{STR(
|
|
| 91 |
+ "Failed to create " << program_name_ << "." |
|
| 92 |
+ )}; |
|
| 93 |
+ |
|
| 94 |
+ // Label program. |
|
| 95 |
+ if (GLEW_VERSION_4_3 || GLEW_KHR_debug) |
|
| 96 |
+ glObjectLabel( |
|
| 97 |
+ GL_PROGRAM, |
|
| 98 |
+ program_, |
|
| 99 |
+ std::min(max_label_length, (GLsizei)program_name_.length()), |
|
| 100 |
+ program_name_.c_str() |
|
| 101 |
+ ); |
|
| 102 |
+ |
|
| 103 |
+ // Process shader paths. |
|
| 104 |
+ auto shaders = std::vector<GLuint>{};
|
|
| 105 |
+ shaders.reserve(paths.size()); |
|
| 106 |
+ for (auto const & path : paths) |
|
| 107 |
+ {
|
|
| 108 |
+ // Set shader name. |
|
| 109 |
+ auto const shader_name = STR( |
|
| 110 |
+ "shader '" << path << "' of " << program_name_ |
|
| 111 |
+ ); |
|
| 112 |
+ |
|
| 113 |
+ // Infer shader type from path extension. |
|
| 114 |
+ auto const type_error = STR( |
|
| 115 |
+ "Failed to infer type of " << shader_name |
|
| 116 |
+ ); |
|
| 117 |
+ auto const type_pos = path.rfind('.');
|
|
| 118 |
+ if (type_pos == path.npos) |
|
| 119 |
+ throw std::runtime_error{STR(
|
|
| 120 |
+ type_error << "; " << |
|
| 121 |
+ "no file extension." |
|
| 122 |
+ )}; |
|
| 123 |
+ auto const type_name = path.substr(type_pos + 1); |
|
| 124 |
+ auto const type = |
|
| 125 |
+ type_name == "vert" ? GL_VERTEX_SHADER : |
|
| 126 |
+ type_name == "tesc" ? GL_TESS_CONTROL_SHADER : |
|
| 127 |
+ type_name == "tese" ? GL_TESS_EVALUATION_SHADER : |
|
| 128 |
+ type_name == "geom" ? GL_GEOMETRY_SHADER : |
|
| 129 |
+ type_name == "frag" ? GL_FRAGMENT_SHADER : |
|
| 130 |
+ type_name == "comp" ? GL_COMPUTE_SHADER : |
|
| 131 |
+ GLenum{0};
|
|
| 132 |
+ if (!type) |
|
| 133 |
+ throw std::runtime_error{STR(
|
|
| 134 |
+ type_error << "; " << |
|
| 135 |
+ "unknown file extension '" << type_name << "'." |
|
| 136 |
+ )}; |
|
| 137 |
+ |
|
| 138 |
+ // Create, attach, and flag shader for deletion when detached. |
|
| 139 |
+ auto const shader = glCreateShader(type); |
|
| 140 |
+ if (!shader) |
|
| 141 |
+ throw std::runtime_error{STR(
|
|
| 142 |
+ "Failed to create " << type_name << " shader for " << |
|
| 143 |
+ shader_name << "." |
|
| 144 |
+ )}; |
|
| 145 |
+ shaders.push_back(shader); |
|
| 146 |
+ glAttachShader(program_, shader); |
|
| 147 |
+ glDeleteShader(shader); |
|
| 148 |
+ |
|
| 149 |
+ // Label shader. |
|
| 150 |
+ if (GLEW_VERSION_4_3 || GLEW_KHR_debug) |
|
| 151 |
+ glObjectLabel( |
|
| 152 |
+ GL_SHADER, |
|
| 153 |
+ shader, |
|
| 154 |
+ std::min(max_label_length, (GLsizei)shader_name.length()), |
|
| 155 |
+ shader_name.c_str() |
|
| 156 |
+ ); |
|
| 157 |
+ |
|
| 158 |
+ // Set shader source. |
|
| 159 |
+ auto const source_error = STR("Failed to source " << shader_name);
|
|
| 160 |
+ auto path_full = path; |
|
| 161 |
+ if (!root_.empty()) |
|
| 162 |
+ path_full = STR(root_ << "/" << path_full); |
|
| 163 |
+ auto source_istream = std::ifstream{path_full};
|
|
| 164 |
+ if (!source_istream) |
|
| 165 |
+ throw std::runtime_error{STR(
|
|
| 166 |
+ source_error << "; " << |
|
| 167 |
+ "could not open file '" << path_full << "':\n" << |
|
| 168 |
+ std::strerror(errno) |
|
| 169 |
+ )}; |
|
| 170 |
+ auto const source = STR(source_istream.rdbuf()); |
|
| 171 |
+ auto const sources = std::array<char const *, 1>{{
|
|
| 172 |
+ source.c_str() |
|
| 173 |
+ }}; |
|
| 174 |
+ glShaderSource(shader, sources.size(), &sources[0], nullptr); |
|
| 175 |
+ |
|
| 176 |
+ // Compile shader. |
|
| 177 |
+ info_log_action_( |
|
| 178 |
+ STR("Failed to compile " << shader_name),
|
|
| 179 |
+ glCompileShader, shader, |
|
| 180 |
+ GL_COMPILE_STATUS, glGetShaderiv, glGetShaderInfoLog |
|
| 181 |
+ ); |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ // Link program. |
|
| 185 |
+ info_log_action_( |
|
| 186 |
+ STR("Failed to link " << program_name_),
|
|
| 187 |
+ glLinkProgram, program_, |
|
| 188 |
+ GL_LINK_STATUS, glGetProgramiv, glGetProgramInfoLog |
|
| 189 |
+ ); |
|
| 190 |
+ |
|
| 191 |
+ // Detach shaders. |
|
| 192 |
+ for (auto const & shader : shaders) |
|
| 193 |
+ glDetachShader(program_, shader); |
|
| 194 |
+ } |
|
| 195 |
+ catch (...) |
|
| 196 |
+ {
|
|
| 197 |
+ // Delete program (and detach and delete shaders). |
|
| 198 |
+ if (program_) |
|
| 199 |
+ glDeleteProgram(program_); |
|
| 200 |
+ throw; |
|
| 201 |
+ } |
|
| 202 |
+} |
|
| 203 |
+ |
|
| 204 |
+ |
|
| 205 |
+Shader::Shader(Shader && other) noexcept |
|
| 206 |
+: |
|
| 207 |
+ program_ {other.program_},
|
|
| 208 |
+ program_name_{std::move(other.program_name_)}
|
|
| 209 |
+{
|
|
| 210 |
+ other.program_ = 0; |
|
| 211 |
+} |
|
| 212 |
+ |
|
| 213 |
+ |
|
| 214 |
+Shader::~Shader() |
|
| 215 |
+{
|
|
| 216 |
+ if (program_) |
|
| 217 |
+ glDeleteProgram(program_); |
|
| 218 |
+} |