libs/render/src/render/shader.cc
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "render/shader.h" | ||
| 2 | |||
| 3 | #include "base/cint.h" | ||
| 4 | |||
| 5 | #include "render/constants.h" | ||
| 6 | #include "base/mat4.h" | ||
| 7 | #include "base/vec2.h" | ||
| 8 | #include "render/opengl_utils.h" | ||
| 9 | #include "render/uniform_buffer.h" | ||
| 10 | |||
| 11 | namespace eu::render | ||
| 12 | { | ||
| 13 | |||
| 14 | // internal "header", defined later | ||
| 15 | void set_shader_program(unsigned int new_program, const VertexTypes& types); | ||
| 16 | |||
| 17 | constexpr GLsizei max_log_length = 1024; | ||
| 18 | |||
| 19 | ✗ | bool check_shader_compilation_error(const char* name, unsigned int shader) | |
| 20 | { | ||
| 21 | ✗ | GLint success = 0; | |
| 22 | ✗ | char log[max_log_length] = { | |
| 23 | 0, | ||
| 24 | }; | ||
| 25 | ✗ | glGetShaderiv(shader, GL_COMPILE_STATUS, &success); | |
| 26 | |||
| 27 | ✗ | if (success == GL_FALSE) | |
| 28 | { | ||
| 29 | ✗ | glGetShaderInfoLog(shader, max_log_length, nullptr, log); | |
| 30 | ✗ | log[max_log_length-1] = '\0'; | |
| 31 | ✗ | LOG_ERR("%s shader compilation failed\n{}\n", name, log); | |
| 32 | ✗ | return false; | |
| 33 | } | ||
| 34 | |||
| 35 | ✗ | return true; | |
| 36 | } | ||
| 37 | |||
| 38 | ✗ | bool check_shader_link_error(unsigned int program) | |
| 39 | { | ||
| 40 | ✗ | GLint success = 0; | |
| 41 | ✗ | char log[max_log_length] = { | |
| 42 | 0, | ||
| 43 | }; | ||
| 44 | ✗ | glGetProgramiv(program, GL_LINK_STATUS, &success); | |
| 45 | ✗ | if (success == GL_FALSE) | |
| 46 | { | ||
| 47 | ✗ | glGetProgramInfoLog(program, max_log_length, nullptr, log); | |
| 48 | ✗ | log[max_log_length - 1] = '\0'; | |
| 49 | ✗ | LOG_ERR("shader linking failed\n{}\n", log); | |
| 50 | ✗ | return false; | |
| 51 | } | ||
| 52 | |||
| 53 | ✗ | return true; | |
| 54 | } | ||
| 55 | |||
| 56 | ✗ | void upload_shader_source(unsigned int shader, std::string_view source) | |
| 57 | { | ||
| 58 | ✗ | const char* const s = source.data(); | |
| 59 | ✗ | const int length = int_from_sizet(source.length()); // should be GLint | |
| 60 | ✗ | glShaderSource(shader, 1, &s, &length); | |
| 61 | ✗ | } | |
| 62 | |||
| 63 | ✗ | void bind_shader_attribute_location(unsigned int shader_program, const CompiledShaderVertexAttributes& layout) | |
| 64 | { | ||
| 65 | ✗ | for (const auto& b: layout.elements) | |
| 66 | { | ||
| 67 | ✗ | glBindAttribLocation(shader_program, gluint_from_int(b.index), b.name.c_str()); | |
| 68 | } | ||
| 69 | ✗ | } | |
| 70 | |||
| 71 | ✗ | void verify_shader_attribute_location(unsigned int shader_program, const CompiledShaderVertexAttributes& layout) | |
| 72 | { | ||
| 73 | ✗ | for (const auto& b: layout.elements) | |
| 74 | { | ||
| 75 | ✗ | const auto actual_index = glGetAttribLocation(shader_program, b.name.c_str()); | |
| 76 | |||
| 77 | ✗ | if (actual_index != b.index) | |
| 78 | { | ||
| 79 | ✗ | LOG_ERR("ERROR: {} was bound to {} but requested at {}", b.name.c_str(), actual_index, b.index); | |
| 80 | } | ||
| 81 | } | ||
| 82 | ✗ | } | |
| 83 | |||
| 84 | ✗ | void log_source(const std::string& src) | |
| 85 | { | ||
| 86 | ✗ | std::istringstream ss(src); | |
| 87 | ✗ | std::string line; | |
| 88 | ✗ | int index = 1; | |
| 89 | ✗ | while (std::getline(ss, line)) | |
| 90 | { | ||
| 91 | ✗ | LOG_INFO("%d: %s", index, line.c_str()); | |
| 92 | ✗ | index += 1; | |
| 93 | } | ||
| 94 | ✗ | } | |
| 95 | |||
| 96 | ✗ | void load_shader_source( | |
| 97 | DEBUG_LABEL_ARG_MANY | ||
| 98 | ShaderProgram* self, | ||
| 99 | const std::string& vertex_source, | ||
| 100 | const std::string& fragment_source, | ||
| 101 | const CompiledShaderVertexAttributes& layout | ||
| 102 | ) | ||
| 103 | { | ||
| 104 | ✗ | const auto vertex_shader = glCreateShader(GL_VERTEX_SHADER); | |
| 105 | ✗ | SET_DEBUG_LABEL_NAMED(vertex_shader, DebugLabelFor::Shader, fmt::format("SHADER {} VERT", debug_label)); | |
| 106 | ✗ | upload_shader_source(vertex_shader, vertex_source); | |
| 107 | ✗ | glCompileShader(vertex_shader); | |
| 108 | ✗ | const auto vertex_ok = check_shader_compilation_error("vertex", vertex_shader); | |
| 109 | ✗ | if (vertex_ok == false) | |
| 110 | { | ||
| 111 | ✗ | LOG_INFO("Vertex source:"); | |
| 112 | ✗ | log_source(vertex_source); | |
| 113 | } | ||
| 114 | |||
| 115 | ✗ | const auto fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); | |
| 116 | ✗ | SET_DEBUG_LABEL_NAMED(fragment_shader, DebugLabelFor::Shader, fmt::format("SHADER {} FRAG", debug_label)); | |
| 117 | ✗ | upload_shader_source(fragment_shader, fragment_source); | |
| 118 | ✗ | glCompileShader(fragment_shader); | |
| 119 | ✗ | const auto fragment_ok = check_shader_compilation_error("fragment", fragment_shader); | |
| 120 | ✗ | if (fragment_ok == false) | |
| 121 | { | ||
| 122 | ✗ | LOG_INFO("Fragment source:"); | |
| 123 | ✗ | log_source(fragment_source); | |
| 124 | } | ||
| 125 | |||
| 126 | ✗ | glAttachShader(self->shader_program, vertex_shader); | |
| 127 | ✗ | glAttachShader(self->shader_program, fragment_shader); | |
| 128 | ✗ | bind_shader_attribute_location(self->shader_program, layout); | |
| 129 | ✗ | glLinkProgram(self->shader_program); | |
| 130 | ✗ | const auto link_ok = check_shader_link_error(self->shader_program); | |
| 131 | |||
| 132 | ✗ | glDeleteShader(vertex_shader); | |
| 133 | ✗ | glDeleteShader(fragment_shader); | |
| 134 | |||
| 135 | ✗ | clear_shader_program(); | |
| 136 | |||
| 137 | ✗ | if (vertex_ok && fragment_ok && link_ok) | |
| 138 | { | ||
| 139 | // nop | ||
| 140 | ✗ | verify_shader_attribute_location(self->shader_program, layout); | |
| 141 | } | ||
| 142 | else | ||
| 143 | { | ||
| 144 | ✗ | clear_shader_program(); | |
| 145 | ✗ | glDeleteProgram(self->shader_program); | |
| 146 | ✗ | self->shader_program = 0; | |
| 147 | } | ||
| 148 | ✗ | } | |
| 149 | |||
| 150 | ✗ | ShaderProgram::ShaderProgram( | |
| 151 | DEBUG_LABEL_ARG_MANY const std::string& vertex_source, const std::string& fragment_source, const CompiledShaderVertexAttributes& layout | ||
| 152 | ✗ | ) | |
| 153 | ✗ | : shader_program(glCreateProgram()) | |
| 154 | ✗ | , debug_vertex_types(layout.debug_types) | |
| 155 | { | ||
| 156 | ✗ | SET_DEBUG_LABEL_NAMED(shader_program, DebugLabelFor::Program, fmt::format("PROGRAM {}", debug_label)); | |
| 157 | ✗ | load_shader_source(USE_DEBUG_LABEL_MANY(debug_label) this, vertex_source, fragment_source, layout); | |
| 158 | ✗ | } | |
| 159 | |||
| 160 | ✗ | void ShaderProgram::use() const | |
| 161 | { | ||
| 162 | ✗ | set_shader_program(shader_program, debug_vertex_types); | |
| 163 | ✗ | } | |
| 164 | |||
| 165 | ✗ | ShaderProgram::ShaderProgram(ShaderProgram&& other) noexcept | |
| 166 | ✗ | : shader_program(other.shader_program) | |
| 167 | ✗ | , debug_vertex_types(std::move(other.debug_vertex_types)) | |
| 168 | { | ||
| 169 | ✗ | other.shader_program = 0; | |
| 170 | ✗ | other.debug_vertex_types = {}; | |
| 171 | ✗ | } | |
| 172 | |||
| 173 | ✗ | ShaderProgram& ShaderProgram::operator=(ShaderProgram&& rhs) noexcept | |
| 174 | { | ||
| 175 | ✗ | clear(); | |
| 176 | |||
| 177 | ✗ | shader_program = rhs.shader_program; | |
| 178 | ✗ | debug_vertex_types = rhs.debug_vertex_types; | |
| 179 | |||
| 180 | ✗ | rhs.shader_program = 0; | |
| 181 | ✗ | rhs.debug_vertex_types = {}; | |
| 182 | |||
| 183 | ✗ | return *this; | |
| 184 | } | ||
| 185 | |||
| 186 | ✗ | ShaderProgram::~ShaderProgram() | |
| 187 | { | ||
| 188 | ✗ | clear(); | |
| 189 | ✗ | } | |
| 190 | |||
| 191 | ✗ | bool ShaderProgram::is_loaded() const | |
| 192 | { | ||
| 193 | ✗ | return shader_program != 0; | |
| 194 | } | ||
| 195 | |||
| 196 | ✗ | void ShaderProgram::clear() | |
| 197 | { | ||
| 198 | ✗ | clear_shader_program(); | |
| 199 | ✗ | glDeleteProgram(shader_program); | |
| 200 | ✗ | shader_program = 0; | |
| 201 | ✗ | } | |
| 202 | |||
| 203 | ✗ | Uniform ShaderProgram::get_uniform(const std::string& name) const | |
| 204 | { | ||
| 205 | ✗ | const auto uni = Uniform{name, glGetUniformLocation(shader_program, name.c_str()), shader_program}; | |
| 206 | ✗ | if (uni.is_valid() == false) | |
| 207 | { | ||
| 208 | ✗ | LOG_ERR("Uniform {} not found", name); | |
| 209 | } | ||
| 210 | ✗ | return uni; | |
| 211 | ✗ | } | |
| 212 | |||
| 213 | ✗ | void ShaderProgram::set_float(const Uniform& uniform, float value) // NOLINT(readability-make-member-function-const) | |
| 214 | { | ||
| 215 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 216 | ✗ | if (uniform.is_valid() == false) | |
| 217 | { | ||
| 218 | ✗ | return; | |
| 219 | } | ||
| 220 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 221 | |||
| 222 | ✗ | ASSERT(uniform.texture == -1 && "uniform is a texture not a float"); | |
| 223 | ✗ | glUniform1f(uniform.location, value); | |
| 224 | } | ||
| 225 | |||
| 226 | ✗ | void ShaderProgram::set_bool(const Uniform& uniform, bool value) // NOLINT(readability-make-member-function-const) | |
| 227 | { | ||
| 228 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 229 | ✗ | if (uniform.is_valid() == false) | |
| 230 | { | ||
| 231 | ✗ | return; | |
| 232 | } | ||
| 233 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 234 | |||
| 235 | ✗ | ASSERT(uniform.texture == -1 && "uniform is a texture not a float"); | |
| 236 | ✗ | glUniform1i(uniform.location, value ? 1 : 0); | |
| 237 | } | ||
| 238 | |||
| 239 | ✗ | void ShaderProgram::set_vec2(const Uniform& uniform, float x, float y) // NOLINT(readability-make-member-function-const) | |
| 240 | { | ||
| 241 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 242 | ✗ | if (uniform.is_valid() == false) | |
| 243 | { | ||
| 244 | ✗ | return; | |
| 245 | } | ||
| 246 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 247 | |||
| 248 | ✗ | ASSERT(uniform.texture == -1 && "uniform is a texture not a vec3"); | |
| 249 | ✗ | glUniform2f(uniform.location, x, y); | |
| 250 | } | ||
| 251 | |||
| 252 | ✗ | void ShaderProgram::set_vec2(const Uniform& uniform, const v2& v) | |
| 253 | { | ||
| 254 | ✗ | set_vec2(uniform, v.x, v.y); | |
| 255 | ✗ | } | |
| 256 | |||
| 257 | ✗ | void ShaderProgram::set_vec3(const Uniform& uniform, float x, float y, float z) // NOLINT(readability-make-member-function-const) | |
| 258 | { | ||
| 259 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 260 | ✗ | if (uniform.is_valid() == false) | |
| 261 | { | ||
| 262 | ✗ | return; | |
| 263 | } | ||
| 264 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 265 | |||
| 266 | ✗ | ASSERT(uniform.texture == -1 && "uniform is a texture not a vec3"); | |
| 267 | ✗ | glUniform3f(uniform.location, x, y, z); | |
| 268 | } | ||
| 269 | |||
| 270 | ✗ | void ShaderProgram::set_vec3(const Uniform& uniform, const v3& v) | |
| 271 | { | ||
| 272 | ✗ | set_vec3(uniform, v.x, v.y, v.z); | |
| 273 | ✗ | } | |
| 274 | |||
| 275 | ✗ | void ShaderProgram::set_vec4(const Uniform& uniform, float x, float y, float z, float w) // NOLINT(readability-make-member-function-const) | |
| 276 | { | ||
| 277 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 278 | ✗ | if (uniform.is_valid() == false) | |
| 279 | { | ||
| 280 | ✗ | return; | |
| 281 | } | ||
| 282 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 283 | |||
| 284 | ✗ | ASSERT(uniform.texture == -1 && "uniform is a texture not a vec4"); | |
| 285 | ✗ | glUniform4f(uniform.location, x, y, z, w); | |
| 286 | } | ||
| 287 | |||
| 288 | ✗ | void ShaderProgram::set_vec4(const Uniform& uniform, const v4& v) | |
| 289 | { | ||
| 290 | ✗ | set_vec4(uniform, v.x, v.y, v.z, v.w); | |
| 291 | ✗ | } | |
| 292 | |||
| 293 | ✗ | void ShaderProgram::set_texture(const Uniform& uniform) // NOLINT(readability-make-member-function-const) | |
| 294 | { | ||
| 295 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 296 | ✗ | if (uniform.is_valid() == false) | |
| 297 | { | ||
| 298 | ✗ | return; | |
| 299 | } | ||
| 300 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 301 | |||
| 302 | ✗ | ASSERT(uniform.texture >= 0 && "uniform needs to be a texture"); | |
| 303 | ✗ | glUniform1i(uniform.location, uniform.texture); | |
| 304 | } | ||
| 305 | |||
| 306 | ✗ | void ShaderProgram::set_mat(const Uniform& uniform, const m4& mat) // NOLINT(readability-make-member-function-const) | |
| 307 | { | ||
| 308 | ✗ | ASSERT(is_shader_bound(shader_program)); | |
| 309 | ✗ | if (uniform.is_valid() == false) | |
| 310 | { | ||
| 311 | ✗ | return; | |
| 312 | } | ||
| 313 | ✗ | ASSERT(uniform.debug_shader_program == shader_program); | |
| 314 | |||
| 315 | ✗ | ASSERT(uniform.texture == -1 && "uniform is a texture not a matrix4"); | |
| 316 | ✗ | glUniformMatrix4fv(uniform.location, 1, GL_FALSE, mat.get_column_major_data_ptr()); | |
| 317 | } | ||
| 318 | |||
| 319 | ✗ | void ShaderProgram::setup_uniform_block(const UniformBufferSetup& setup) // NOLINT(readability-make-member-function-const) | |
| 320 | { | ||
| 321 | ✗ | const unsigned int shader_block_index = glGetUniformBlockIndex(shader_program, setup.name.c_str()); | |
| 322 | ✗ | if (shader_block_index == GL_INVALID_INDEX) | |
| 323 | { | ||
| 324 | ✗ | LOG_ERR("Shader missing uniform block %s", setup.name.c_str()); | |
| 325 | ✗ | return; | |
| 326 | } | ||
| 327 | ✗ | glUniformBlockBinding(shader_program, shader_block_index, gluint_from_int(setup.binding_point)); | |
| 328 | } | ||
| 329 | |||
| 330 | ✗ | void setup_textures(ShaderProgram* shader, const std::vector<Uniform*>& uniform_list) | |
| 331 | { | ||
| 332 | ✗ | ASSERT(uniform_list.size() <= MAX_TEXTURES_SUPPORTED); | |
| 333 | |||
| 334 | ✗ | shader->use(); | |
| 335 | |||
| 336 | ✗ | int index = 0; | |
| 337 | ✗ | for (const auto& uniform: uniform_list) | |
| 338 | { | ||
| 339 | ✗ | uniform->texture = index; | |
| 340 | ✗ | index += 1; | |
| 341 | ✗ | shader->set_texture(*uniform); | |
| 342 | } | ||
| 343 | ✗ | } | |
| 344 | |||
| 345 | namespace | ||
| 346 | { | ||
| 347 | VertexTypes debug_current_shader_types; | ||
| 348 | unsigned int debug_current_shader_program = 0; | ||
| 349 | } // namespace | ||
| 350 | |||
| 351 | ✗ | void set_shader_program(unsigned int new_program, const VertexTypes& types) | |
| 352 | { | ||
| 353 | ✗ | debug_current_shader_program = new_program; | |
| 354 | ✗ | debug_current_shader_types = types; | |
| 355 | ✗ | glUseProgram(new_program); | |
| 356 | ✗ | } | |
| 357 | |||
| 358 | ✗ | bool is_bound_for_shader(const std::unordered_set<VertexType>& debug_geom_shader_types) | |
| 359 | { | ||
| 360 | ✗ | for (auto t: debug_current_shader_types) | |
| 361 | { | ||
| 362 | ✗ | if (debug_geom_shader_types.find(t) == debug_geom_shader_types.end()) | |
| 363 | { | ||
| 364 | // if shader type isn't found in geom | ||
| 365 | // then error out | ||
| 366 | ✗ | return false; | |
| 367 | } | ||
| 368 | } | ||
| 369 | ✗ | return true; | |
| 370 | } | ||
| 371 | |||
| 372 | ✗ | bool is_shader_bound(unsigned int program) | |
| 373 | { | ||
| 374 | ✗ | return debug_current_shader_program == program; | |
| 375 | } | ||
| 376 | |||
| 377 | ✗ | void clear_shader_program() | |
| 378 | { | ||
| 379 | ✗ | set_shader_program(0, {}); | |
| 380 | ✗ | } | |
| 381 | |||
| 382 | } | ||
| 383 |