/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright 2020, Blender Foundation. */ /** \file * \ingroup gpu */ #include "BKE_global.h" #include "BLI_math_base.h" #include "BLI_math_bits.h" #include "GPU_capabilities.h" #include "glew-mx.h" #include "gl_context.hh" #include "gl_debug.hh" #include "gl_framebuffer.hh" #include "gl_texture.hh" #include "gl_state.hh" namespace blender::gpu { /* -------------------------------------------------------------------- */ /** \name GLStateManager * \{ */ GLStateManager::GLStateManager() { /* Set other states that never change. */ glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); glEnable(GL_MULTISAMPLE); glEnable(GL_PRIMITIVE_RESTART); glDisable(GL_DITHER); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPrimitiveRestartIndex((GLuint)0xFFFFFFFF); /* TODO: Should become default. But needs at least GL 4.3 */ if (GLContext::fixed_restart_index_support) { /* Takes precedence over #GL_PRIMITIVE_RESTART. */ glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); } /* Limits. */ glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, line_width_range_); /* Force update using default state. */ current_ = ~state; current_mutable_ = ~mutable_state; set_state(state); set_mutable_state(mutable_state); } void GLStateManager::apply_state() { if (!this->use_bgl) { this->set_state(this->state); this->set_mutable_state(this->mutable_state); this->texture_bind_apply(); this->image_bind_apply(); } /* This is needed by gpu_py_offscreen. */ active_fb->apply_state(); }; /* Will set all the states regardless of the current ones. */ void GLStateManager::force_state() { /* Little exception for clip distances since they need to keep the old count correct. */ uint32_t clip_distances = current_.clip_distances; current_ = ~this->state; current_.clip_distances = clip_distances; current_mutable_ = ~this->mutable_state; this->set_state(this->state); this->set_mutable_state(this->mutable_state); }; void GLStateManager::set_state(const GPUState &state) { GPUState changed = state ^ current_; if (changed.blend != 0) { set_blend((eGPUBlend)state.blend); } if (changed.write_mask != 0) { set_write_mask((eGPUWriteMask)state.write_mask); } if (changed.depth_test != 0) { set_depth_test((eGPUDepthTest)state.depth_test); } if (changed.stencil_test != 0 || changed.stencil_op != 0) { set_stencil_test((eGPUStencilTest)state.stencil_test, (eGPUStencilOp)state.stencil_op); set_stencil_mask((eGPUStencilTest)state.stencil_test, mutable_state); } if (changed.clip_distances != 0) { set_clip_distances(state.clip_distances, current_.clip_distances); } if (changed.culling_test != 0) { set_backface_culling((eGPUFaceCullTest)state.culling_test); } if (changed.logic_op_xor != 0) { set_logic_op(state.logic_op_xor); } if (changed.invert_facing != 0) { set_facing(state.invert_facing); } if (changed.provoking_vert != 0) { set_provoking_vert((eGPUProvokingVertex)state.provoking_vert); } if (changed.shadow_bias != 0) { set_shadow_bias(state.shadow_bias); } /* TODO remove */ if (changed.polygon_smooth) { if (state.polygon_smooth) { glEnable(GL_POLYGON_SMOOTH); } else { glDisable(GL_POLYGON_SMOOTH); } } if (changed.line_smooth) { if (state.line_smooth) { glEnable(GL_LINE_SMOOTH); } else { glDisable(GL_LINE_SMOOTH); } } current_ = state; } void GLStateManager::set_mutable_state(const GPUStateMutable &state) { GPUStateMutable changed = state ^ current_mutable_; /* TODO remove, should be uniform. */ if (float_as_uint(changed.point_size) != 0) { if (state.point_size > 0.0f) { glEnable(GL_PROGRAM_POINT_SIZE); } else { glDisable(GL_PROGRAM_POINT_SIZE); glPointSize(fabsf(state.point_size)); } } if (changed.line_width != 0) { /* TODO remove, should use wide line shader. */ glLineWidth(clamp_f(state.line_width, line_width_range_[0], line_width_range_[1])); } if (changed.depth_range[0] != 0 || changed.depth_range[1] != 0) { /* TODO remove, should modify the projection matrix instead. */ glDepthRange(UNPACK2(state.depth_range)); } if (changed.stencil_compare_mask != 0 || changed.stencil_reference != 0 || changed.stencil_write_mask != 0) { set_stencil_mask((eGPUStencilTest)current_.stencil_test, state); } current_mutable_ = state; } /** \} */ /* -------------------------------------------------------------------- */ /** \name State set functions * \{ */ void GLStateManager::set_write_mask(const eGPUWriteMask value) { glDepthMask((value & GPU_WRITE_DEPTH) != 0); glColorMask((value & GPU_WRITE_RED) != 0, (value & GPU_WRITE_GREEN) != 0, (value & GPU_WRITE_BLUE) != 0, (value & GPU_WRITE_ALPHA) != 0); if (value == GPU_WRITE_NONE) { glEnable(GL_RASTERIZER_DISCARD); } else { glDisable(GL_RASTERIZER_DISCARD); } } void GLStateManager::set_depth_test(const eGPUDepthTest value) { GLenum func; switch (value) { case GPU_DEPTH_LESS: func = GL_LESS; break; case GPU_DEPTH_LESS_EQUAL: func = GL_LEQUAL; break; case GPU_DEPTH_EQUAL: func = GL_EQUAL; break; case GPU_DEPTH_GREATER: func = GL_GREATER; break; case GPU_DEPTH_GREATER_EQUAL: func = GL_GEQUAL; break; case GPU_DEPTH_ALWAYS: default: func = GL_ALWAYS; break; } if (value != GPU_DEPTH_NONE) { glEnable(GL_DEPTH_TEST); glDepthFunc(func); } else { glDisable(GL_DEPTH_TEST); } } void GLStateManager::set_stencil_test(const eGPUStencilTest test, const eGPUStencilOp operation) { switch (operation) { case GPU_STENCIL_OP_REPLACE: glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); break; case GPU_STENCIL_OP_COUNT_DEPTH_PASS: glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP); glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_DECR_WRAP); break; case GPU_STENCIL_OP_COUNT_DEPTH_FAIL: glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_KEEP); glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_KEEP); break; case GPU_STENCIL_OP_NONE: default: glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); } if (test != GPU_STENCIL_NONE) { glEnable(GL_STENCIL_TEST); } else { glDisable(GL_STENCIL_TEST); } } void GLStateManager::set_stencil_mask(const eGPUStencilTest test, const GPUStateMutable state) { GLenum func; switch (test) { case GPU_STENCIL_NEQUAL: func = GL_NOTEQUAL; break; case GPU_STENCIL_EQUAL: func = GL_EQUAL; break; case GPU_STENCIL_ALWAYS: func = GL_ALWAYS; break; case GPU_STENCIL_NONE: default: glStencilMask(0x00); glStencilFunc(GL_ALWAYS, 0x00, 0x00); return; } glStencilMask(state.stencil_write_mask); glStencilFunc(func, state.stencil_reference, state.stencil_compare_mask); } void GLStateManager::set_clip_distances(const int new_dist_len, const int old_dist_len) { for (int i = 0; i < new_dist_len; i++) { glEnable(GL_CLIP_DISTANCE0 + i); } for (int i = new_dist_len; i < old_dist_len; i++) { glDisable(GL_CLIP_DISTANCE0 + i); } } void GLStateManager::set_logic_op(const bool enable) { if (enable) { glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_XOR); } else { glDisable(GL_COLOR_LOGIC_OP); } } void GLStateManager::set_facing(const bool invert) { glFrontFace((invert) ? GL_CW : GL_CCW); } void GLStateManager::set_backface_culling(const eGPUFaceCullTest test) { if (test != GPU_CULL_NONE) { glEnable(GL_CULL_FACE); glCullFace((test == GPU_CULL_FRONT) ? GL_FRONT : GL_BACK); } else { glDisable(GL_CULL_FACE); } } void GLStateManager::set_provoking_vert(const eGPUProvokingVertex vert) { GLenum value = (vert == GPU_VERTEX_FIRST) ? GL_FIRST_VERTEX_CONVENTION : GL_LAST_VERTEX_CONVENTION; glProvokingVertex(value); } void GLStateManager::set_shadow_bias(const bool enable) { if (enable) { glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_POLYGON_OFFSET_LINE); /* 2.0 Seems to be the lowest possible slope bias that works in every case. */ glPolygonOffset(2.0f, 1.0f); } else { glDisable(GL_POLYGON_OFFSET_FILL); glDisable(GL_POLYGON_OFFSET_LINE); } } void GLStateManager::set_blend(const eGPUBlend value) { /** * Factors to the equation. * SRC is fragment shader output. * DST is frame-buffer color. * final.rgb = SRC.rgb * src_rgb + DST.rgb * dst_rgb; * final.a = SRC.a * src_alpha + DST.a * dst_alpha; */ GLenum src_rgb, src_alpha, dst_rgb, dst_alpha; switch (value) { default: case GPU_BLEND_ALPHA: { src_rgb = GL_SRC_ALPHA; dst_rgb = GL_ONE_MINUS_SRC_ALPHA; src_alpha = GL_ONE; dst_alpha = GL_ONE_MINUS_SRC_ALPHA; break; } case GPU_BLEND_ALPHA_PREMULT: { src_rgb = GL_ONE; dst_rgb = GL_ONE_MINUS_SRC_ALPHA; src_alpha = GL_ONE; dst_alpha = GL_ONE_MINUS_SRC_ALPHA; break; } case GPU_BLEND_ADDITIVE: { /* Do not let alpha accumulate but premult the source RGB by it. */ src_rgb = GL_SRC_ALPHA; dst_rgb = GL_ONE; src_alpha = GL_ZERO; dst_alpha = GL_ONE; break; } case GPU_BLEND_SUBTRACT: case GPU_BLEND_ADDITIVE_PREMULT: { /* Let alpha accumulate. */ src_rgb = GL_ONE; dst_rgb = GL_ONE; src_alpha = GL_ONE; dst_alpha = GL_ONE; break; } case GPU_BLEND_MULTIPLY: { src_rgb = GL_DST_COLOR; dst_rgb = GL_ZERO; src_alpha = GL_DST_ALPHA; dst_alpha = GL_ZERO; break; } case GPU_BLEND_INVERT: { src_rgb = GL_ONE_MINUS_DST_COLOR; dst_rgb = GL_ZERO; src_alpha = GL_ZERO; dst_alpha = GL_ONE; break; } case GPU_BLEND_OIT: { src_rgb = GL_ONE; dst_rgb = GL_ONE; src_alpha = GL_ZERO; dst_alpha = GL_ONE_MINUS_SRC_ALPHA; break; } case GPU_BLEND_BACKGROUND: { src_rgb = GL_ONE_MINUS_DST_ALPHA; dst_rgb = GL_SRC_ALPHA; src_alpha = GL_ZERO; dst_alpha = GL_SRC_ALPHA; break; } case GPU_BLEND_ALPHA_UNDER_PREMUL: { src_rgb = GL_ONE_MINUS_DST_ALPHA; dst_rgb = GL_ONE; src_alpha = GL_ONE_MINUS_DST_ALPHA; dst_alpha = GL_ONE; break; } case GPU_BLEND_CUSTOM: { src_rgb = GL_ONE; dst_rgb = GL_SRC1_COLOR; src_alpha = GL_ONE; dst_alpha = GL_SRC1_ALPHA; break; } } if (value == GPU_BLEND_SUBTRACT) { glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); } else { glBlendEquation(GL_FUNC_ADD); } /* Always set the blend function. This avoid a rendering error when blending is disabled but * GPU_BLEND_CUSTOM was used just before and the frame-buffer is using more than 1 color target. */ glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha); if (value != GPU_BLEND_NONE) { glEnable(GL_BLEND); } else { glDisable(GL_BLEND); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Texture State Management * \{ */ void GLStateManager::texture_bind(Texture *tex_, eGPUSamplerState sampler_type, int unit) { BLI_assert(unit < GPU_max_textures()); GLTexture *tex = static_cast(tex_); if (G.debug & G_DEBUG_GPU) { tex->check_feedback_loop(); } /* Eliminate redundant binds. */ if ((textures_[unit] == tex->tex_id_) && (samplers_[unit] == GLTexture::samplers_[sampler_type])) { return; } targets_[unit] = tex->target_; textures_[unit] = tex->tex_id_; samplers_[unit] = GLTexture::samplers_[sampler_type]; tex->is_bound_ = true; dirty_texture_binds_ |= 1ULL << unit; } /* Bind the texture to slot 0 for editing purpose. Used by legacy pipeline. */ void GLStateManager::texture_bind_temp(GLTexture *tex) { glActiveTexture(GL_TEXTURE0); glBindTexture(tex->target_, tex->tex_id_); /* Will reset the first texture that was originally bound to slot 0 back before drawing. */ dirty_texture_binds_ |= 1ULL; /* NOTE: This might leave this texture attached to this target even after update. * In practice it is not causing problems as we have incorrect binding detection * at higher level. */ } void GLStateManager::texture_unbind(Texture *tex_) { GLTexture *tex = static_cast(tex_); if (!tex->is_bound_) { return; } GLuint tex_id = tex->tex_id_; for (int i = 0; i < ARRAY_SIZE(textures_); i++) { if (textures_[i] == tex_id) { textures_[i] = 0; samplers_[i] = 0; dirty_texture_binds_ |= 1ULL << i; } } tex->is_bound_ = false; } void GLStateManager::texture_unbind_all() { for (int i = 0; i < ARRAY_SIZE(textures_); i++) { if (textures_[i] != 0) { textures_[i] = 0; samplers_[i] = 0; dirty_texture_binds_ |= 1ULL << i; } } this->texture_bind_apply(); } void GLStateManager::texture_bind_apply() { if (dirty_texture_binds_ == 0) { return; } uint64_t dirty_bind = dirty_texture_binds_; dirty_texture_binds_ = 0; int first = bitscan_forward_uint64(dirty_bind); int last = 64 - bitscan_reverse_uint64(dirty_bind); int count = last - first; if (GLContext::multi_bind_support) { glBindTextures(first, count, textures_ + first); glBindSamplers(first, count, samplers_ + first); } else { for (int unit = first; unit < last; unit++) { if ((dirty_bind >> unit) & 1UL) { glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(targets_[unit], textures_[unit]); glBindSampler(unit, samplers_[unit]); } } } } void GLStateManager::texture_unpack_row_length_set(uint len) { glPixelStorei(GL_UNPACK_ROW_LENGTH, len); } uint64_t GLStateManager::bound_texture_slots() { uint64_t bound_slots = 0; for (int i = 0; i < ARRAY_SIZE(textures_); i++) { if (textures_[i] != 0) { bound_slots |= 1ULL << i; } } return bound_slots; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Image Binding (from image load store) * \{ */ void GLStateManager::image_bind(Texture *tex_, int unit) { /* Minimum support is 8 image in the fragment shader. No image for other stages. */ BLI_assert(GPU_shader_image_load_store_support() && unit < 8); GLTexture *tex = static_cast(tex_); if (G.debug & G_DEBUG_GPU) { tex->check_feedback_loop(); } images_[unit] = tex->tex_id_; formats_[unit] = to_gl_internal_format(tex->format_); tex->is_bound_ = true; dirty_image_binds_ |= 1ULL << unit; } void GLStateManager::image_unbind(Texture *tex_) { GLTexture *tex = static_cast(tex_); if (!tex->is_bound_) { return; } GLuint tex_id = tex->tex_id_; for (int i = 0; i < ARRAY_SIZE(images_); i++) { if (images_[i] == tex_id) { images_[i] = 0; dirty_image_binds_ |= 1ULL << i; } } tex->is_bound_ = false; } void GLStateManager::image_unbind_all() { for (int i = 0; i < ARRAY_SIZE(images_); i++) { if (images_[i] != 0) { images_[i] = 0; dirty_image_binds_ |= 1ULL << i; } } this->image_bind_apply(); } void GLStateManager::image_bind_apply() { if (dirty_image_binds_ == 0) { return; } uint32_t dirty_bind = dirty_image_binds_; dirty_image_binds_ = 0; int first = bitscan_forward_uint(dirty_bind); int last = 32 - bitscan_reverse_uint(dirty_bind); int count = last - first; if (GLContext::multi_bind_support) { glBindImageTextures(first, count, images_ + first); } else { for (int unit = first; unit < last; unit++) { if ((dirty_bind >> unit) & 1UL) { glBindImageTexture(unit, images_[unit], 0, GL_TRUE, 0, GL_READ_WRITE, formats_[unit]); } } } } uint8_t GLStateManager::bound_image_slots() { uint8_t bound_slots = 0; for (int i = 0; i < ARRAY_SIZE(images_); i++) { if (images_[i] != 0) { bound_slots |= 1ULL << i; } } return bound_slots; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Memory barrier * \{ */ void GLStateManager::issue_barrier(eGPUBarrier barrier_bits) { glMemoryBarrier(to_gl(barrier_bits)); } /** \} */ } // namespace blender::gpu