This makes it possible to disable all the extensions when forcing workarounds. Also it will allow future options to selectively disable each extension to know which one is buggy.
542 lines
14 KiB
C++
542 lines
14 KiB
C++
/*
|
|
* 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(void) : GPUStateManager()
|
|
{
|
|
/* 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(void)
|
|
{
|
|
this->set_state(this->state);
|
|
this->set_mutable_state(this->mutable_state);
|
|
this->texture_bind_apply();
|
|
active_fb->apply_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 (changed.point_size != 0) {
|
|
if (state.point_size > 0.0f) {
|
|
glEnable(GL_PROGRAM_POINT_SIZE);
|
|
glPointSize(state.point_size);
|
|
}
|
|
else {
|
|
glDisable(GL_PROGRAM_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_CUSTOM: {
|
|
src_rgb = GL_ONE;
|
|
dst_rgb = GL_SRC1_COLOR;
|
|
src_alpha = GL_ONE;
|
|
dst_alpha = GL_SRC1_ALPHA;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 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<GLTexture *>(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<GLTexture *>(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(void)
|
|
{
|
|
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(void)
|
|
{
|
|
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(void)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::gpu
|