State tracking works in pretty much all cases but calling the clear command does change the write mask outside the draw manager. For now we just reset the write mask before each pass. Fix T62203 The selected bone is not highlighted inside the other bone.
1377 lines
41 KiB
C
1377 lines
41 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 2016, Blender Foundation.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup draw
|
|
*/
|
|
|
|
#include "draw_manager.h"
|
|
|
|
#include "BLI_mempool.h"
|
|
|
|
|
|
#include "BKE_global.h"
|
|
|
|
#include "GPU_draw.h"
|
|
#include "GPU_extensions.h"
|
|
#include "intern/gpu_shader_private.h"
|
|
|
|
#ifdef USE_GPU_SELECT
|
|
# include "GPU_select.h"
|
|
#endif
|
|
|
|
#ifdef USE_GPU_SELECT
|
|
void DRW_select_load_id(uint id)
|
|
{
|
|
BLI_assert(G.f & G_FLAG_PICKSEL);
|
|
DST.select_id = id;
|
|
}
|
|
#endif
|
|
|
|
#define DEBUG_UBO_BINDING
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Draw State (DRW_state)
|
|
* \{ */
|
|
|
|
void drw_state_set(DRWState state)
|
|
{
|
|
if (DST.state == state) {
|
|
return;
|
|
}
|
|
|
|
#define CHANGED_TO(f) \
|
|
((DST.state_lock & (f)) ? 0 : \
|
|
(((DST.state & (f)) ? \
|
|
((state & (f)) ? 0 : -1) : \
|
|
((state & (f)) ? 1 : 0))))
|
|
|
|
#define CHANGED_ANY(f) \
|
|
(((DST.state & (f)) != (state & (f))) && \
|
|
((DST.state_lock & (f)) == 0))
|
|
|
|
#define CHANGED_ANY_STORE_VAR(f, enabled) \
|
|
(((DST.state & (f)) != (enabled = (state & (f)))) && \
|
|
(((DST.state_lock & (f)) == 0)))
|
|
|
|
/* Depth Write */
|
|
{
|
|
int test;
|
|
if ((test = CHANGED_TO(DRW_STATE_WRITE_DEPTH))) {
|
|
if (test == 1) {
|
|
glDepthMask(GL_TRUE);
|
|
}
|
|
else {
|
|
glDepthMask(GL_FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Color Write */
|
|
{
|
|
int test;
|
|
if ((test = CHANGED_TO(DRW_STATE_WRITE_COLOR))) {
|
|
if (test == 1) {
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
}
|
|
else {
|
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Raster Discard */
|
|
{
|
|
if (CHANGED_ANY(DRW_STATE_RASTERIZER_ENABLED)) {
|
|
if ((state & DRW_STATE_RASTERIZER_ENABLED) != 0) {
|
|
glDisable(GL_RASTERIZER_DISCARD);
|
|
}
|
|
else {
|
|
glEnable(GL_RASTERIZER_DISCARD);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Cull */
|
|
{
|
|
DRWState test;
|
|
if (CHANGED_ANY_STORE_VAR(
|
|
DRW_STATE_CULL_BACK | DRW_STATE_CULL_FRONT,
|
|
test))
|
|
{
|
|
if (test) {
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
if ((state & DRW_STATE_CULL_BACK) != 0) {
|
|
glCullFace(GL_BACK);
|
|
}
|
|
else if ((state & DRW_STATE_CULL_FRONT) != 0) {
|
|
glCullFace(GL_FRONT);
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
else {
|
|
glDisable(GL_CULL_FACE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Depth Test */
|
|
{
|
|
DRWState test;
|
|
if (CHANGED_ANY_STORE_VAR(
|
|
DRW_STATE_DEPTH_LESS | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_DEPTH_EQUAL |
|
|
DRW_STATE_DEPTH_GREATER | DRW_STATE_DEPTH_GREATER_EQUAL | DRW_STATE_DEPTH_ALWAYS,
|
|
test))
|
|
{
|
|
if (test) {
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
if (state & DRW_STATE_DEPTH_LESS) {
|
|
glDepthFunc(GL_LESS);
|
|
}
|
|
else if (state & DRW_STATE_DEPTH_LESS_EQUAL) {
|
|
glDepthFunc(GL_LEQUAL);
|
|
}
|
|
else if (state & DRW_STATE_DEPTH_EQUAL) {
|
|
glDepthFunc(GL_EQUAL);
|
|
}
|
|
else if (state & DRW_STATE_DEPTH_GREATER) {
|
|
glDepthFunc(GL_GREATER);
|
|
}
|
|
else if (state & DRW_STATE_DEPTH_GREATER_EQUAL) {
|
|
glDepthFunc(GL_GEQUAL);
|
|
}
|
|
else if (state & DRW_STATE_DEPTH_ALWAYS) {
|
|
glDepthFunc(GL_ALWAYS);
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
else {
|
|
glDisable(GL_DEPTH_TEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Wire Width */
|
|
{
|
|
int test;
|
|
if (CHANGED_ANY_STORE_VAR(
|
|
DRW_STATE_WIRE | DRW_STATE_WIRE_WIDE | DRW_STATE_WIRE_SMOOTH,
|
|
test))
|
|
{
|
|
if (test & DRW_STATE_WIRE_WIDE) {
|
|
GPU_line_width(3.0f);
|
|
}
|
|
else if (test & DRW_STATE_WIRE_SMOOTH) {
|
|
GPU_line_width(2.0f);
|
|
GPU_line_smooth(true);
|
|
}
|
|
else if (test & DRW_STATE_WIRE) {
|
|
GPU_line_width(1.0f);
|
|
}
|
|
else {
|
|
GPU_line_width(1.0f);
|
|
GPU_line_smooth(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Points Size */
|
|
{
|
|
int test;
|
|
if ((test = CHANGED_TO(DRW_STATE_POINT))) {
|
|
if (test == 1) {
|
|
GPU_enable_program_point_size();
|
|
glPointSize(5.0f);
|
|
}
|
|
else {
|
|
GPU_disable_program_point_size();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Blending (all buffer) */
|
|
{
|
|
int test;
|
|
if (CHANGED_ANY_STORE_VAR(
|
|
DRW_STATE_BLEND | DRW_STATE_BLEND_PREMUL | DRW_STATE_ADDITIVE |
|
|
DRW_STATE_MULTIPLY | DRW_STATE_ADDITIVE_FULL |
|
|
DRW_STATE_BLEND_OIT,
|
|
test))
|
|
{
|
|
if (test) {
|
|
glEnable(GL_BLEND);
|
|
|
|
if ((state & DRW_STATE_BLEND) != 0) {
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, /* RGB */
|
|
GL_ONE, GL_ONE_MINUS_SRC_ALPHA); /* Alpha */
|
|
}
|
|
else if ((state & DRW_STATE_BLEND_PREMUL) != 0) {
|
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
else if ((state & DRW_STATE_MULTIPLY) != 0) {
|
|
glBlendFunc(GL_DST_COLOR, GL_ZERO);
|
|
}
|
|
else if ((state & DRW_STATE_BLEND_OIT) != 0) {
|
|
glBlendFuncSeparate(GL_ONE, GL_ONE, /* RGB */
|
|
GL_ZERO, GL_ONE_MINUS_SRC_ALPHA); /* Alpha */
|
|
}
|
|
else if ((state & DRW_STATE_ADDITIVE) != 0) {
|
|
/* Do not let alpha accumulate but premult the source RGB by it. */
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, /* RGB */
|
|
GL_ZERO, GL_ONE); /* Alpha */
|
|
}
|
|
else if ((state & DRW_STATE_ADDITIVE_FULL) != 0) {
|
|
/* Let alpha accumulate. */
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
else {
|
|
glDisable(GL_BLEND);
|
|
glBlendFunc(GL_ONE, GL_ONE); /* Don't multiply incoming color by alpha. */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clip Planes */
|
|
{
|
|
int test;
|
|
if ((test = CHANGED_TO(DRW_STATE_CLIP_PLANES))) {
|
|
if (test == 1) {
|
|
for (int i = 0; i < DST.clip_planes_len; ++i) {
|
|
glEnable(GL_CLIP_DISTANCE0 + i);
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < MAX_CLIP_PLANES; ++i) {
|
|
glDisable(GL_CLIP_DISTANCE0 + i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Stencil */
|
|
{
|
|
DRWState test;
|
|
if (CHANGED_ANY_STORE_VAR(
|
|
DRW_STATE_WRITE_STENCIL |
|
|
DRW_STATE_WRITE_STENCIL_SHADOW_PASS |
|
|
DRW_STATE_WRITE_STENCIL_SHADOW_FAIL |
|
|
DRW_STATE_STENCIL_EQUAL |
|
|
DRW_STATE_STENCIL_NEQUAL,
|
|
test))
|
|
{
|
|
if (test) {
|
|
glEnable(GL_STENCIL_TEST);
|
|
/* Stencil Write */
|
|
if ((state & DRW_STATE_WRITE_STENCIL) != 0) {
|
|
glStencilMask(0xFF);
|
|
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
|
}
|
|
else if ((state & DRW_STATE_WRITE_STENCIL_SHADOW_PASS) != 0) {
|
|
glStencilMask(0xFF);
|
|
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
|
|
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
|
|
}
|
|
else if ((state & DRW_STATE_WRITE_STENCIL_SHADOW_FAIL) != 0) {
|
|
glStencilMask(0xFF);
|
|
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_KEEP);
|
|
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
|
|
}
|
|
/* Stencil Test */
|
|
else if ((state & (DRW_STATE_STENCIL_EQUAL | DRW_STATE_STENCIL_NEQUAL)) != 0) {
|
|
glStencilMask(0x00); /* disable write */
|
|
DST.stencil_mask = STENCIL_UNDEFINED;
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
else {
|
|
/* disable write & test */
|
|
DST.stencil_mask = 0;
|
|
glStencilMask(0x00);
|
|
glStencilFunc(GL_ALWAYS, 0, 0xFF);
|
|
glDisable(GL_STENCIL_TEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Provoking Vertex */
|
|
{
|
|
int test;
|
|
if ((test = CHANGED_TO(DRW_STATE_FIRST_VERTEX_CONVENTION))) {
|
|
if (test == 1) {
|
|
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
|
|
}
|
|
else {
|
|
glProvokingVertex(GL_LAST_VERTEX_CONVENTION);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Polygon Offset */
|
|
{
|
|
int test;
|
|
if (CHANGED_ANY_STORE_VAR(
|
|
DRW_STATE_OFFSET_POSITIVE |
|
|
DRW_STATE_OFFSET_NEGATIVE,
|
|
test))
|
|
{
|
|
if (test) {
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glEnable(GL_POLYGON_OFFSET_LINE);
|
|
glEnable(GL_POLYGON_OFFSET_POINT);
|
|
/* Stencil Write */
|
|
if ((state & DRW_STATE_OFFSET_POSITIVE) != 0) {
|
|
glPolygonOffset(1.0f, 1.0f);
|
|
}
|
|
else if ((state & DRW_STATE_OFFSET_NEGATIVE) != 0) {
|
|
glPolygonOffset(-1.0f, -1.0f);
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
else {
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
glDisable(GL_POLYGON_OFFSET_LINE);
|
|
glDisable(GL_POLYGON_OFFSET_POINT);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef CHANGED_TO
|
|
#undef CHANGED_ANY
|
|
#undef CHANGED_ANY_STORE_VAR
|
|
|
|
DST.state = state;
|
|
}
|
|
|
|
static void drw_stencil_set(uint mask)
|
|
{
|
|
if (DST.stencil_mask != mask) {
|
|
DST.stencil_mask = mask;
|
|
/* Stencil Write */
|
|
if ((DST.state & DRW_STATE_WRITE_STENCIL) != 0) {
|
|
glStencilFunc(GL_ALWAYS, mask, 0xFF);
|
|
}
|
|
/* Stencil Test */
|
|
else if ((DST.state & DRW_STATE_STENCIL_EQUAL) != 0) {
|
|
glStencilFunc(GL_EQUAL, mask, 0xFF);
|
|
}
|
|
else if ((DST.state & DRW_STATE_STENCIL_NEQUAL) != 0) {
|
|
glStencilFunc(GL_NOTEQUAL, mask, 0xFF);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Reset state to not interfer with other UI drawcall */
|
|
void DRW_state_reset_ex(DRWState state)
|
|
{
|
|
DST.state = ~state;
|
|
drw_state_set(state);
|
|
}
|
|
|
|
/**
|
|
* Use with care, intended so selection code can override passes depth settings,
|
|
* which is important for selection to work properly.
|
|
*
|
|
* Should be set in main draw loop, cleared afterwards
|
|
*/
|
|
void DRW_state_lock(DRWState state)
|
|
{
|
|
DST.state_lock = state;
|
|
}
|
|
|
|
void DRW_state_reset(void)
|
|
{
|
|
DRW_state_reset_ex(DRW_STATE_DEFAULT);
|
|
|
|
/* Reset blending function */
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
/* NOTE : Make sure to reset after use! */
|
|
void DRW_state_invert_facing(void)
|
|
{
|
|
SWAP(GLenum, DST.backface, DST.frontface);
|
|
glFrontFace(DST.frontface);
|
|
}
|
|
|
|
/**
|
|
* This only works if DRWPasses have been tagged with DRW_STATE_CLIP_PLANES,
|
|
* and if the shaders have support for it (see usage of gl_ClipDistance).
|
|
* Be sure to call DRW_state_clip_planes_reset() after you finish drawing.
|
|
**/
|
|
void DRW_state_clip_planes_len_set(uint plane_len)
|
|
{
|
|
BLI_assert(plane_len <= MAX_CLIP_PLANES);
|
|
DST.clip_planes_len = plane_len;
|
|
}
|
|
|
|
void DRW_state_clip_planes_reset(void)
|
|
{
|
|
DST.clip_planes_len = 0;
|
|
}
|
|
|
|
void DRW_state_clip_planes_set_from_rv3d(RegionView3D *rv3d)
|
|
{
|
|
int max_len = 6;
|
|
int real_len = (rv3d->viewlock & RV3D_BOXCLIP) ? 4 : max_len;
|
|
while (real_len < max_len) {
|
|
/* Fill in dummy values that wont change results (6 is hard coded in shaders). */
|
|
copy_v4_v4(rv3d->clip[real_len], rv3d->clip[3]);
|
|
real_len++;
|
|
}
|
|
|
|
DRW_state_clip_planes_len_set(max_len);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Clipping (DRW_clipping)
|
|
* \{ */
|
|
|
|
/* Extract the 8 corners from a Projection Matrix.
|
|
* Although less accurate, this solution can be simplified as follows:
|
|
* BKE_boundbox_init_from_minmax(&bbox, (const float[3]){-1.0f, -1.0f, -1.0f}, (const float[3]){1.0f, 1.0f, 1.0f});
|
|
* for (int i = 0; i < 8; i++) {mul_project_m4_v3(projinv, bbox.vec[i]);}
|
|
*/
|
|
static void draw_frustum_boundbox_calc(const float(*projmat)[4], BoundBox *r_bbox)
|
|
{
|
|
float near, far, left, right, bottom, top;
|
|
bool is_persp = projmat[3][3] == 0.0f;
|
|
|
|
if (is_persp) {
|
|
near = projmat[3][2] / (projmat[2][2] - 1.0f);
|
|
far = projmat[3][2] / (projmat[2][2] + 1.0f);
|
|
left = near * (projmat[2][0] - 1.0f) / projmat[0][0];
|
|
right = near * (projmat[2][0] + 1.0f) / projmat[0][0];
|
|
bottom = near * (projmat[2][1] - 1.0f) / projmat[1][1];
|
|
top = near * (projmat[2][1] + 1.0f) / projmat[1][1];
|
|
}
|
|
else {
|
|
near = ( projmat[3][2] + 1.0f) / projmat[2][2];
|
|
far = ( projmat[3][2] - 1.0f) / projmat[2][2];
|
|
left = (-projmat[3][0] - 1.0f) / projmat[0][0];
|
|
right = (-projmat[3][0] + 1.0f) / projmat[0][0];
|
|
bottom = (-projmat[3][1] - 1.0f) / projmat[1][1];
|
|
top = (-projmat[3][1] + 1.0f) / projmat[1][1];
|
|
}
|
|
|
|
r_bbox->vec[0][2] = r_bbox->vec[3][2] = r_bbox->vec[7][2] = r_bbox->vec[4][2] = -near;
|
|
r_bbox->vec[0][0] = r_bbox->vec[3][0] = left;
|
|
r_bbox->vec[4][0] = r_bbox->vec[7][0] = right;
|
|
r_bbox->vec[0][1] = r_bbox->vec[4][1] = bottom;
|
|
r_bbox->vec[7][1] = r_bbox->vec[3][1] = top;
|
|
|
|
/* Get the coordinates of the far plane. */
|
|
if (is_persp) {
|
|
float sca_far = far / near;
|
|
left *= sca_far;
|
|
bottom *= sca_far;
|
|
right *= sca_far;
|
|
top *= sca_far;
|
|
}
|
|
|
|
r_bbox->vec[1][2] = r_bbox->vec[2][2] = r_bbox->vec[6][2] = r_bbox->vec[5][2] = -far;
|
|
r_bbox->vec[1][0] = r_bbox->vec[2][0] = left;
|
|
r_bbox->vec[6][0] = r_bbox->vec[5][0] = right;
|
|
r_bbox->vec[1][1] = r_bbox->vec[5][1] = bottom;
|
|
r_bbox->vec[2][1] = r_bbox->vec[6][1] = top;
|
|
}
|
|
|
|
static void draw_clipping_setup_from_view(void)
|
|
{
|
|
if (DST.clipping.updated) {
|
|
return;
|
|
}
|
|
|
|
float (*viewinv)[4] = DST.view_data.matstate.mat[DRW_MAT_VIEWINV];
|
|
float (*projmat)[4] = DST.view_data.matstate.mat[DRW_MAT_WIN];
|
|
float (*projinv)[4] = DST.view_data.matstate.mat[DRW_MAT_WININV];
|
|
BoundSphere *bsphere = &DST.clipping.frustum_bsphere;
|
|
|
|
/* Extract Clipping Planes */
|
|
BoundBox bbox;
|
|
#if 0 /* It has accuracy problems. */
|
|
BKE_boundbox_init_from_minmax(&bbox, (const float[3]){-1.0f, -1.0f, -1.0f}, (const float[3]){1.0f, 1.0f, 1.0f});
|
|
for (int i = 0; i < 8; i++) {
|
|
mul_project_m4_v3(projinv, bbox.vec[i]);
|
|
}
|
|
#else
|
|
draw_frustum_boundbox_calc(projmat, &bbox);
|
|
#endif
|
|
/* Transform into world space. */
|
|
for (int i = 0; i < 8; i++) {
|
|
mul_m4_v3(viewinv, bbox.vec[i]);
|
|
}
|
|
|
|
memcpy(&DST.clipping.frustum_corners, &bbox, sizeof(BoundBox));
|
|
|
|
/* Compute clip planes using the world space frustum corners. */
|
|
for (int p = 0; p < 6; p++) {
|
|
int q, r, s;
|
|
switch (p) {
|
|
case 0: q = 1; r = 2; s = 3; break; /* -X */
|
|
case 1: q = 0; r = 4; s = 5; break; /* -Y */
|
|
case 2: q = 1; r = 5; s = 6; break; /* +Z (far) */
|
|
case 3: q = 2; r = 6; s = 7; break; /* +Y */
|
|
case 4: q = 0; r = 3; s = 7; break; /* -Z (near) */
|
|
default: q = 4; r = 7; s = 6; break; /* +X */
|
|
}
|
|
if (DST.frontface == GL_CW) {
|
|
SWAP(int, q, s);
|
|
}
|
|
|
|
normal_quad_v3(DST.clipping.frustum_planes[p], bbox.vec[p], bbox.vec[q], bbox.vec[r], bbox.vec[s]);
|
|
/* Increase precision and use the mean of all 4 corners. */
|
|
DST.clipping.frustum_planes[p][3] = -dot_v3v3(DST.clipping.frustum_planes[p], bbox.vec[p]);
|
|
DST.clipping.frustum_planes[p][3] += -dot_v3v3(DST.clipping.frustum_planes[p], bbox.vec[q]);
|
|
DST.clipping.frustum_planes[p][3] += -dot_v3v3(DST.clipping.frustum_planes[p], bbox.vec[r]);
|
|
DST.clipping.frustum_planes[p][3] += -dot_v3v3(DST.clipping.frustum_planes[p], bbox.vec[s]);
|
|
DST.clipping.frustum_planes[p][3] *= 0.25f;
|
|
}
|
|
|
|
/* Extract Bounding Sphere */
|
|
if (projmat[3][3] != 0.0f) {
|
|
/* Orthographic */
|
|
/* The most extreme points on the near and far plane. (normalized device coords). */
|
|
float *nearpoint = bbox.vec[0];
|
|
float *farpoint = bbox.vec[6];
|
|
|
|
/* just use median point */
|
|
mid_v3_v3v3(bsphere->center, farpoint, nearpoint);
|
|
bsphere->radius = len_v3v3(bsphere->center, farpoint);
|
|
}
|
|
else if (projmat[2][0] == 0.0f && projmat[2][1] == 0.0f) {
|
|
/* Perspective with symmetrical frustum. */
|
|
|
|
/* We obtain the center and radius of the circumscribed circle of the
|
|
* isosceles trapezoid composed by the diagonals of the near and far clipping plane */
|
|
|
|
/* center of each clipping plane */
|
|
float mid_min[3], mid_max[3];
|
|
mid_v3_v3v3(mid_min, bbox.vec[3], bbox.vec[4]);
|
|
mid_v3_v3v3(mid_max, bbox.vec[2], bbox.vec[5]);
|
|
|
|
/* square length of the diagonals of each clipping plane */
|
|
float a_sq = len_squared_v3v3(bbox.vec[3], bbox.vec[4]);
|
|
float b_sq = len_squared_v3v3(bbox.vec[2], bbox.vec[5]);
|
|
|
|
/* distance squared between clipping planes */
|
|
float h_sq = len_squared_v3v3(mid_min, mid_max);
|
|
|
|
float fac = (4 * h_sq + b_sq - a_sq) / (8 * h_sq);
|
|
|
|
/* The goal is to get the smallest sphere,
|
|
* not the sphere that passes through each corner */
|
|
CLAMP(fac, 0.0f, 1.0f);
|
|
|
|
interp_v3_v3v3(bsphere->center, mid_min, mid_max, fac);
|
|
|
|
/* distance from the center to one of the points of the far plane (1, 2, 5, 6) */
|
|
bsphere->radius = len_v3v3(bsphere->center, bbox.vec[1]);
|
|
}
|
|
else {
|
|
/* Perspective with asymmetrical frustum. */
|
|
|
|
/* We put the sphere center on the line that goes from origin
|
|
* to the center of the far clipping plane. */
|
|
|
|
/* Detect which of the corner of the far clipping plane is the farthest to the origin */
|
|
float nfar[4]; /* most extreme far point in NDC space */
|
|
float farxy[2]; /* farpoint projection onto the near plane */
|
|
float farpoint[3] = {0.0f}; /* most extreme far point in camera coordinate */
|
|
float nearpoint[3]; /* most extreme near point in camera coordinate */
|
|
float farcenter[3] = {0.0f}; /* center of far cliping plane in camera coordinate */
|
|
float F = -1.0f, N; /* square distance of far and near point to origin */
|
|
float f, n; /* distance of far and near point to z axis. f is always > 0 but n can be < 0 */
|
|
float e, s; /* far and near clipping distance (<0) */
|
|
float c; /* slope of center line = distance of far clipping center to z axis / far clipping distance */
|
|
float z; /* projection of sphere center on z axis (<0) */
|
|
|
|
/* Find farthest corner and center of far clip plane. */
|
|
float corner[3] = {1.0f, 1.0f, 1.0f}; /* in clip space */
|
|
for (int i = 0; i < 4; i++) {
|
|
float point[3];
|
|
mul_v3_project_m4_v3(point, projinv, corner);
|
|
float len = len_squared_v3(point);
|
|
if (len > F) {
|
|
copy_v3_v3(nfar, corner);
|
|
copy_v3_v3(farpoint, point);
|
|
F = len;
|
|
}
|
|
add_v3_v3(farcenter, point);
|
|
/* rotate by 90 degree to walk through the 4 points of the far clip plane */
|
|
float tmp = corner[0];
|
|
corner[0] = -corner[1];
|
|
corner[1] = tmp;
|
|
}
|
|
|
|
/* the far center is the average of the far clipping points */
|
|
mul_v3_fl(farcenter, 0.25f);
|
|
/* the extreme near point is the opposite point on the near clipping plane */
|
|
copy_v3_fl3(nfar, -nfar[0], -nfar[1], -1.0f);
|
|
mul_v3_project_m4_v3(nearpoint, projinv, nfar);
|
|
/* this is a frustum projection */
|
|
N = len_squared_v3(nearpoint);
|
|
e = farpoint[2];
|
|
s = nearpoint[2];
|
|
/* distance to view Z axis */
|
|
f = len_v2(farpoint);
|
|
/* get corresponding point on the near plane */
|
|
mul_v2_v2fl(farxy, farpoint, s / e);
|
|
/* this formula preserve the sign of n */
|
|
sub_v2_v2(nearpoint, farxy);
|
|
n = f * s / e - len_v2(nearpoint);
|
|
c = len_v2(farcenter) / e;
|
|
/* the big formula, it simplifies to (F-N)/(2(e-s)) for the symmetric case */
|
|
z = (F - N) / (2.0f * (e - s + c * (f - n)));
|
|
|
|
bsphere->center[0] = farcenter[0] * z / e;
|
|
bsphere->center[1] = farcenter[1] * z / e;
|
|
bsphere->center[2] = z;
|
|
bsphere->radius = len_v3v3(bsphere->center, farpoint);
|
|
|
|
/* Transform to world space. */
|
|
mul_m4_v3(viewinv, bsphere->center);
|
|
}
|
|
|
|
DST.clipping.updated = true;
|
|
}
|
|
|
|
/* Return True if the given BoundSphere intersect the current view frustum */
|
|
bool DRW_culling_sphere_test(BoundSphere *bsphere)
|
|
{
|
|
draw_clipping_setup_from_view();
|
|
|
|
/* Bypass test if radius is negative. */
|
|
if (bsphere->radius < 0.0f) {
|
|
return true;
|
|
}
|
|
|
|
/* Do a rough test first: Sphere VS Sphere intersect. */
|
|
BoundSphere *frustum_bsphere = &DST.clipping.frustum_bsphere;
|
|
float center_dist = len_squared_v3v3(bsphere->center, frustum_bsphere->center);
|
|
if (center_dist > SQUARE(bsphere->radius + frustum_bsphere->radius)) {
|
|
return false;
|
|
}
|
|
|
|
/* Test against the 6 frustum planes. */
|
|
for (int p = 0; p < 6; p++) {
|
|
float dist = plane_point_side_v3(DST.clipping.frustum_planes[p], bsphere->center);
|
|
if (dist < -bsphere->radius) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Return True if the given BoundBox intersect the current view frustum.
|
|
* bbox must be in world space. */
|
|
bool DRW_culling_box_test(BoundBox *bbox)
|
|
{
|
|
draw_clipping_setup_from_view();
|
|
|
|
/* 6 view frustum planes */
|
|
for (int p = 0; p < 6; p++) {
|
|
/* 8 box vertices. */
|
|
for (int v = 0; v < 8 ; v++) {
|
|
float dist = plane_point_side_v3(DST.clipping.frustum_planes[p], bbox->vec[v]);
|
|
if (dist > 0.0f) {
|
|
/* At least one point in front of this plane.
|
|
* Go to next plane. */
|
|
break;
|
|
}
|
|
else if (v == 7) {
|
|
/* 8 points behind this plane. */
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Return True if the current view frustum is inside or intersect the given plane */
|
|
bool DRW_culling_plane_test(float plane[4])
|
|
{
|
|
draw_clipping_setup_from_view();
|
|
|
|
/* Test against the 8 frustum corners. */
|
|
for (int c = 0; c < 8; c++) {
|
|
float dist = plane_point_side_v3(plane, DST.clipping.frustum_corners.vec[c]);
|
|
if (dist < 0.0f) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DRW_culling_frustum_corners_get(BoundBox *corners)
|
|
{
|
|
draw_clipping_setup_from_view();
|
|
memcpy(corners, &DST.clipping.frustum_corners, sizeof(BoundBox));
|
|
}
|
|
|
|
/* See draw_clipping_setup_from_view() for the plane order. */
|
|
void DRW_culling_frustum_planes_get(float planes[6][4])
|
|
{
|
|
draw_clipping_setup_from_view();
|
|
memcpy(planes, &DST.clipping.frustum_planes, sizeof(DST.clipping.frustum_planes));
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Draw (DRW_draw)
|
|
* \{ */
|
|
|
|
static void draw_visibility_eval(DRWCallState *st)
|
|
{
|
|
bool culled = st->flag & DRW_CALL_CULLED;
|
|
|
|
if (st->cache_id != DST.state_cache_id) {
|
|
/* Update culling result for this view. */
|
|
culled = !DRW_culling_sphere_test(&st->bsphere);
|
|
}
|
|
|
|
if (st->visibility_cb) {
|
|
culled = !st->visibility_cb(!culled, st->user_data);
|
|
}
|
|
|
|
SET_FLAG_FROM_TEST(st->flag, culled, DRW_CALL_CULLED);
|
|
}
|
|
|
|
static void draw_matrices_model_prepare(DRWCallState *st)
|
|
{
|
|
if (st->cache_id == DST.state_cache_id) {
|
|
/* Values are already updated for this view. */
|
|
return;
|
|
}
|
|
else {
|
|
st->cache_id = DST.state_cache_id;
|
|
}
|
|
|
|
/* No need to go further the call will not be used. */
|
|
if ((st->flag & DRW_CALL_CULLED) != 0 &&
|
|
(st->flag & DRW_CALL_BYPASS_CULLING) == 0)
|
|
{
|
|
return;
|
|
}
|
|
/* Order matters */
|
|
if (st->matflag & (DRW_CALL_MODELVIEW | DRW_CALL_MODELVIEWINVERSE |
|
|
DRW_CALL_NORMALVIEW | DRW_CALL_EYEVEC))
|
|
{
|
|
mul_m4_m4m4(st->modelview, DST.view_data.matstate.mat[DRW_MAT_VIEW], st->model);
|
|
}
|
|
if (st->matflag & DRW_CALL_MODELVIEWINVERSE) {
|
|
invert_m4_m4(st->modelviewinverse, st->modelview);
|
|
}
|
|
if (st->matflag & DRW_CALL_MODELVIEWPROJECTION) {
|
|
mul_m4_m4m4(st->modelviewprojection, DST.view_data.matstate.mat[DRW_MAT_PERS], st->model);
|
|
}
|
|
if (st->matflag & (DRW_CALL_NORMALVIEW | DRW_CALL_EYEVEC)) {
|
|
copy_m3_m4(st->normalview, st->modelview);
|
|
invert_m3(st->normalview);
|
|
transpose_m3(st->normalview);
|
|
}
|
|
if (st->matflag & DRW_CALL_EYEVEC) {
|
|
/* Used by orthographic wires */
|
|
float tmp[3][3];
|
|
copy_v3_fl3(st->eyevec, 0.0f, 0.0f, 1.0f);
|
|
invert_m3_m3(tmp, st->normalview);
|
|
/* set eye vector, transformed to object coords */
|
|
mul_m3_v3(tmp, st->eyevec);
|
|
}
|
|
/* Non view dependent */
|
|
if (st->matflag & DRW_CALL_MODELINVERSE) {
|
|
invert_m4_m4(st->modelinverse, st->model);
|
|
st->matflag &= ~DRW_CALL_MODELINVERSE;
|
|
}
|
|
if (st->matflag & DRW_CALL_NORMALWORLD) {
|
|
copy_m3_m4(st->normalworld, st->model);
|
|
invert_m3(st->normalworld);
|
|
transpose_m3(st->normalworld);
|
|
st->matflag &= ~DRW_CALL_NORMALWORLD;
|
|
}
|
|
}
|
|
|
|
static void draw_geometry_prepare(DRWShadingGroup *shgroup, DRWCall *call)
|
|
{
|
|
/* step 1 : bind object dependent matrices */
|
|
if (call != NULL) {
|
|
DRWCallState *state = call->state;
|
|
float objectinfo[3];
|
|
objectinfo[0] = state->objectinfo[0];
|
|
objectinfo[1] = call->single.ma_index; /* WATCH this is only valid for single drawcalls. */
|
|
objectinfo[2] = state->objectinfo[1];
|
|
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->model, 16, 1, (float *)state->model);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelinverse, 16, 1, (float *)state->modelinverse);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelview, 16, 1, (float *)state->modelview);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelviewinverse, 16, 1, (float *)state->modelviewinverse);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelviewprojection, 16, 1, (float *)state->modelviewprojection);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->normalview, 9, 1, (float *)state->normalview);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->normalworld, 9, 1, (float *)state->normalworld);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->objectinfo, 3, 1, (float *)objectinfo);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->orcotexfac, 3, 2, (float *)state->orcotexfac);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->eye, 3, 1, (float *)state->eyevec);
|
|
}
|
|
else {
|
|
BLI_assert((shgroup->normalview == -1) && (shgroup->normalworld == -1) && (shgroup->eye == -1));
|
|
/* For instancing and batching. */
|
|
float unitmat[4][4];
|
|
unit_m4(unitmat);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->model, 16, 1, (float *)unitmat);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelinverse, 16, 1, (float *)unitmat);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelview, 16, 1, (float *)DST.view_data.matstate.mat[DRW_MAT_VIEW]);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelviewinverse, 16, 1, (float *)DST.view_data.matstate.mat[DRW_MAT_VIEWINV]);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->modelviewprojection, 16, 1, (float *)DST.view_data.matstate.mat[DRW_MAT_PERS]);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->objectinfo, 3, 1, (float *)unitmat);
|
|
GPU_shader_uniform_vector(shgroup->shader, shgroup->orcotexfac, 3, 2, (float *)shgroup->instance_orcofac);
|
|
}
|
|
}
|
|
|
|
static void draw_geometry_execute_ex(
|
|
DRWShadingGroup *shgroup, GPUBatch *geom, uint start, uint count, bool draw_instance)
|
|
{
|
|
/* Special case: empty drawcall, placement is done via shader, don't bind anything. */
|
|
/* TODO use DRW_CALL_PROCEDURAL instead */
|
|
if (geom == NULL) {
|
|
BLI_assert(shgroup->type == DRW_SHG_TRIANGLE_BATCH); /* Add other type if needed. */
|
|
/* Shader is already bound. */
|
|
GPU_draw_primitive(GPU_PRIM_TRIS, count);
|
|
return;
|
|
}
|
|
|
|
/* step 2 : bind vertex array & draw */
|
|
GPU_batch_program_set_no_use(
|
|
geom, GPU_shader_get_program(shgroup->shader), GPU_shader_get_interface(shgroup->shader));
|
|
/* XXX hacking gawain. we don't want to call glUseProgram! (huge performance loss) */
|
|
geom->program_in_use = true;
|
|
|
|
GPU_batch_draw_range_ex(geom, start, count, draw_instance);
|
|
|
|
geom->program_in_use = false; /* XXX hacking gawain */
|
|
}
|
|
|
|
static void draw_geometry_execute(DRWShadingGroup *shgroup, GPUBatch *geom)
|
|
{
|
|
draw_geometry_execute_ex(shgroup, geom, 0, 0, false);
|
|
}
|
|
|
|
enum {
|
|
BIND_NONE = 0,
|
|
BIND_TEMP = 1, /* Release slot after this shading group. */
|
|
BIND_PERSIST = 2, /* Release slot only after the next shader change. */
|
|
};
|
|
|
|
static void bind_texture(GPUTexture *tex, char bind_type)
|
|
{
|
|
int index;
|
|
char *slot_flags = DST.RST.bound_tex_slots;
|
|
int bind_num = GPU_texture_bound_number(tex);
|
|
if (bind_num == -1) {
|
|
for (int i = 0; i < GPU_max_textures(); ++i) {
|
|
index = DST.RST.bind_tex_inc = (DST.RST.bind_tex_inc + 1) % GPU_max_textures();
|
|
if (slot_flags[index] == BIND_NONE) {
|
|
if (DST.RST.bound_texs[index] != NULL) {
|
|
GPU_texture_unbind(DST.RST.bound_texs[index]);
|
|
}
|
|
GPU_texture_bind(tex, index);
|
|
DST.RST.bound_texs[index] = tex;
|
|
slot_flags[index] = bind_type;
|
|
// printf("Binds Texture %d %p\n", DST.RST.bind_tex_inc, tex);
|
|
return;
|
|
}
|
|
}
|
|
printf("Not enough texture slots! Reduce number of textures used by your shader.\n");
|
|
}
|
|
slot_flags[bind_num] = bind_type;
|
|
}
|
|
|
|
static void bind_ubo(GPUUniformBuffer *ubo, char bind_type)
|
|
{
|
|
int index;
|
|
char *slot_flags = DST.RST.bound_ubo_slots;
|
|
int bind_num = GPU_uniformbuffer_bindpoint(ubo);
|
|
if (bind_num == -1) {
|
|
for (int i = 0; i < GPU_max_ubo_binds(); ++i) {
|
|
index = DST.RST.bind_ubo_inc = (DST.RST.bind_ubo_inc + 1) % GPU_max_ubo_binds();
|
|
if (slot_flags[index] == BIND_NONE) {
|
|
if (DST.RST.bound_ubos[index] != NULL) {
|
|
GPU_uniformbuffer_unbind(DST.RST.bound_ubos[index]);
|
|
}
|
|
GPU_uniformbuffer_bind(ubo, index);
|
|
DST.RST.bound_ubos[index] = ubo;
|
|
slot_flags[index] = bind_type;
|
|
return;
|
|
}
|
|
}
|
|
/* printf so user can report bad behavior */
|
|
printf("Not enough ubo slots! This should not happen!\n");
|
|
/* This is not depending on user input.
|
|
* It is our responsibility to make sure there is enough slots. */
|
|
BLI_assert(0);
|
|
}
|
|
slot_flags[bind_num] = bind_type;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
/**
|
|
* Opengl specification is strict on buffer binding.
|
|
*
|
|
* " If any active uniform block is not backed by a
|
|
* sufficiently large buffer object, the results of shader
|
|
* execution are undefined, and may result in GL interruption or
|
|
* termination. " - Opengl 3.3 Core Specification
|
|
*
|
|
* For now we only check if the binding is correct. Not the size of
|
|
* the bound ubo.
|
|
*
|
|
* See T55475.
|
|
* */
|
|
static bool ubo_bindings_validate(DRWShadingGroup *shgroup)
|
|
{
|
|
bool valid = true;
|
|
# ifdef DEBUG_UBO_BINDING
|
|
/* Check that all active uniform blocks have a non-zero buffer bound. */
|
|
GLint program = 0;
|
|
GLint active_blocks = 0;
|
|
|
|
glGetIntegerv(GL_CURRENT_PROGRAM, &program);
|
|
glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &active_blocks);
|
|
|
|
for (uint i = 0; i < active_blocks; ++i) {
|
|
int binding = 0;
|
|
int buffer = 0;
|
|
|
|
glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_BINDING, &binding);
|
|
glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, binding, &buffer);
|
|
|
|
if (buffer == 0) {
|
|
char blockname[64];
|
|
glGetActiveUniformBlockName(program, i, sizeof(blockname), NULL, blockname);
|
|
|
|
if (valid) {
|
|
printf("Trying to draw with missing UBO binding.\n");
|
|
valid = false;
|
|
}
|
|
printf("Pass : %s, Shader : %s, Block : %s\n", shgroup->pass_parent->name, shgroup->shader->name, blockname);
|
|
}
|
|
}
|
|
# endif
|
|
return valid;
|
|
}
|
|
#endif
|
|
|
|
static void release_texture_slots(bool with_persist)
|
|
{
|
|
if (with_persist) {
|
|
memset(DST.RST.bound_tex_slots, 0x0, sizeof(*DST.RST.bound_tex_slots) * GPU_max_textures());
|
|
}
|
|
else {
|
|
for (int i = 0; i < GPU_max_textures(); ++i) {
|
|
if (DST.RST.bound_tex_slots[i] != BIND_PERSIST) {
|
|
DST.RST.bound_tex_slots[i] = BIND_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Reset so that slots are consistently assigned for different shader
|
|
* draw calls, to avoid shader specialization/patching by the driver. */
|
|
DST.RST.bind_tex_inc = 0;
|
|
}
|
|
|
|
static void release_ubo_slots(bool with_persist)
|
|
{
|
|
if (with_persist) {
|
|
memset(DST.RST.bound_ubo_slots, 0x0, sizeof(*DST.RST.bound_ubo_slots) * GPU_max_ubo_binds());
|
|
}
|
|
else {
|
|
for (int i = 0; i < GPU_max_ubo_binds(); ++i) {
|
|
if (DST.RST.bound_ubo_slots[i] != BIND_PERSIST) {
|
|
DST.RST.bound_ubo_slots[i] = BIND_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Reset so that slots are consistently assigned for different shader
|
|
* draw calls, to avoid shader specialization/patching by the driver. */
|
|
DST.RST.bind_ubo_inc = 0;
|
|
}
|
|
|
|
static void draw_shgroup(DRWShadingGroup *shgroup, DRWState pass_state)
|
|
{
|
|
BLI_assert(shgroup->shader);
|
|
|
|
GPUTexture *tex;
|
|
GPUUniformBuffer *ubo;
|
|
int val;
|
|
float fval;
|
|
const bool shader_changed = (DST.shader != shgroup->shader);
|
|
bool use_tfeedback = false;
|
|
|
|
if (shader_changed) {
|
|
if (DST.shader) GPU_shader_unbind();
|
|
GPU_shader_bind(shgroup->shader);
|
|
DST.shader = shgroup->shader;
|
|
}
|
|
|
|
if ((pass_state & DRW_STATE_TRANS_FEEDBACK) != 0 &&
|
|
(shgroup->type == DRW_SHG_FEEDBACK_TRANSFORM))
|
|
{
|
|
use_tfeedback = GPU_shader_transform_feedback_enable(shgroup->shader,
|
|
shgroup->tfeedback_target->vbo_id);
|
|
}
|
|
|
|
release_ubo_slots(shader_changed);
|
|
release_texture_slots(shader_changed);
|
|
|
|
drw_state_set((pass_state & shgroup->state_extra_disable) | shgroup->state_extra);
|
|
drw_stencil_set(shgroup->stencil_mask);
|
|
|
|
/* Binding Uniform */
|
|
for (DRWUniform *uni = shgroup->uniforms; uni; uni = uni->next) {
|
|
if (uni->location == -2) {
|
|
uni->location = GPU_shader_get_uniform_ensure(shgroup->shader, DST.uniform_names.buffer + uni->name_ofs);
|
|
if (uni->location == -1) {
|
|
continue;
|
|
}
|
|
}
|
|
switch (uni->type) {
|
|
case DRW_UNIFORM_SHORT_TO_INT:
|
|
val = (int)*((short *)uni->pvalue);
|
|
GPU_shader_uniform_vector_int(
|
|
shgroup->shader, uni->location, uni->length, uni->arraysize, &val);
|
|
break;
|
|
case DRW_UNIFORM_SHORT_TO_FLOAT:
|
|
fval = (float)*((short *)uni->pvalue);
|
|
GPU_shader_uniform_vector(
|
|
shgroup->shader, uni->location, uni->length, uni->arraysize, (float *)&fval);
|
|
break;
|
|
case DRW_UNIFORM_BOOL_COPY:
|
|
case DRW_UNIFORM_INT_COPY:
|
|
GPU_shader_uniform_vector_int(
|
|
shgroup->shader, uni->location, uni->length, uni->arraysize, &uni->ivalue);
|
|
break;
|
|
case DRW_UNIFORM_BOOL:
|
|
case DRW_UNIFORM_INT:
|
|
GPU_shader_uniform_vector_int(
|
|
shgroup->shader, uni->location, uni->length, uni->arraysize, (int *)uni->pvalue);
|
|
break;
|
|
case DRW_UNIFORM_FLOAT_COPY:
|
|
GPU_shader_uniform_vector(
|
|
shgroup->shader, uni->location, uni->length, uni->arraysize, &uni->fvalue);
|
|
break;
|
|
case DRW_UNIFORM_FLOAT:
|
|
GPU_shader_uniform_vector(
|
|
shgroup->shader, uni->location, uni->length, uni->arraysize, (float *)uni->pvalue);
|
|
break;
|
|
case DRW_UNIFORM_TEXTURE:
|
|
tex = (GPUTexture *)uni->pvalue;
|
|
BLI_assert(tex);
|
|
bind_texture(tex, BIND_TEMP);
|
|
GPU_shader_uniform_texture(shgroup->shader, uni->location, tex);
|
|
break;
|
|
case DRW_UNIFORM_TEXTURE_PERSIST:
|
|
tex = (GPUTexture *)uni->pvalue;
|
|
BLI_assert(tex);
|
|
bind_texture(tex, BIND_PERSIST);
|
|
GPU_shader_uniform_texture(shgroup->shader, uni->location, tex);
|
|
break;
|
|
case DRW_UNIFORM_TEXTURE_REF:
|
|
tex = *((GPUTexture **)uni->pvalue);
|
|
BLI_assert(tex);
|
|
bind_texture(tex, BIND_TEMP);
|
|
GPU_shader_uniform_texture(shgroup->shader, uni->location, tex);
|
|
break;
|
|
case DRW_UNIFORM_BLOCK:
|
|
ubo = (GPUUniformBuffer *)uni->pvalue;
|
|
bind_ubo(ubo, BIND_TEMP);
|
|
GPU_shader_uniform_buffer(shgroup->shader, uni->location, ubo);
|
|
break;
|
|
case DRW_UNIFORM_BLOCK_PERSIST:
|
|
ubo = (GPUUniformBuffer *)uni->pvalue;
|
|
bind_ubo(ubo, BIND_PERSIST);
|
|
GPU_shader_uniform_buffer(shgroup->shader, uni->location, ubo);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_GPU_SELECT
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL(_select_id) \
|
|
if (G.f & G_FLAG_PICKSEL) { \
|
|
GPU_select_load_id(_select_id); \
|
|
} ((void)0)
|
|
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL_CALL(_call) \
|
|
if ((G.f & G_FLAG_PICKSEL) && (_call)) { \
|
|
GPU_select_load_id((_call)->select_id); \
|
|
} ((void)0)
|
|
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL_LIST(_shgroup, _start, _count) \
|
|
_start = 0; \
|
|
_count = _shgroup->instance_count; \
|
|
int *select_id = NULL; \
|
|
if (G.f & G_FLAG_PICKSEL) { \
|
|
if (_shgroup->override_selectid == -1) { \
|
|
/* Hack : get vbo data without actually drawing. */ \
|
|
GPUVertBufRaw raw; \
|
|
GPU_vertbuf_attr_get_raw_data(_shgroup->inst_selectid, 0, &raw); \
|
|
select_id = GPU_vertbuf_raw_step(&raw); \
|
|
switch (_shgroup->type) { \
|
|
case DRW_SHG_TRIANGLE_BATCH: _count = 3; break; \
|
|
case DRW_SHG_LINE_BATCH: _count = 2; break; \
|
|
default: _count = 1; break; \
|
|
} \
|
|
} \
|
|
else { \
|
|
GPU_select_load_id(_shgroup->override_selectid); \
|
|
} \
|
|
} \
|
|
while (_start < _shgroup->instance_count) { \
|
|
if (select_id) { \
|
|
GPU_select_load_id(select_id[_start]); \
|
|
}
|
|
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL_LIST_END(_start, _count) \
|
|
_start += _count; \
|
|
}
|
|
|
|
#else
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL(select_id)
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL_CALL(call)
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL_LIST_END(start, count)
|
|
# define GPU_SELECT_LOAD_IF_PICKSEL_LIST(_shgroup, _start, _count) \
|
|
_start = 0; \
|
|
_count = _shgroup->instance_count;
|
|
|
|
#endif
|
|
|
|
BLI_assert(ubo_bindings_validate(shgroup));
|
|
|
|
/* Rendering Calls */
|
|
if (!ELEM(shgroup->type, DRW_SHG_NORMAL, DRW_SHG_FEEDBACK_TRANSFORM)) {
|
|
/* Replacing multiple calls with only one */
|
|
if (ELEM(shgroup->type, DRW_SHG_INSTANCE, DRW_SHG_INSTANCE_EXTERNAL)) {
|
|
if (shgroup->type == DRW_SHG_INSTANCE_EXTERNAL) {
|
|
if (shgroup->instance_geom != NULL) {
|
|
GPU_SELECT_LOAD_IF_PICKSEL(shgroup->override_selectid);
|
|
draw_geometry_prepare(shgroup, NULL);
|
|
draw_geometry_execute_ex(shgroup, shgroup->instance_geom, 0, 0, true);
|
|
}
|
|
}
|
|
else {
|
|
if (shgroup->instance_count > 0) {
|
|
uint count, start;
|
|
draw_geometry_prepare(shgroup, NULL);
|
|
GPU_SELECT_LOAD_IF_PICKSEL_LIST(shgroup, start, count)
|
|
{
|
|
draw_geometry_execute_ex(shgroup, shgroup->instance_geom, start, count, true);
|
|
}
|
|
GPU_SELECT_LOAD_IF_PICKSEL_LIST_END(start, count)
|
|
}
|
|
}
|
|
}
|
|
else { /* DRW_SHG_***_BATCH */
|
|
/* Some dynamic batch can have no geom (no call to aggregate) */
|
|
if (shgroup->instance_count > 0) {
|
|
uint count, start;
|
|
draw_geometry_prepare(shgroup, NULL);
|
|
GPU_SELECT_LOAD_IF_PICKSEL_LIST(shgroup, start, count)
|
|
{
|
|
draw_geometry_execute_ex(shgroup, shgroup->batch_geom, start, count, false);
|
|
}
|
|
GPU_SELECT_LOAD_IF_PICKSEL_LIST_END(start, count)
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
bool prev_neg_scale = false;
|
|
int callid = 0;
|
|
for (DRWCall *call = shgroup->calls.first; call; call = call->next) {
|
|
|
|
/* OPTI/IDEA(clem): Do this preparation in another thread. */
|
|
draw_visibility_eval(call->state);
|
|
draw_matrices_model_prepare(call->state);
|
|
|
|
if ((call->state->flag & DRW_CALL_CULLED) != 0 &&
|
|
(call->state->flag & DRW_CALL_BYPASS_CULLING) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* XXX small exception/optimisation for outline rendering. */
|
|
if (shgroup->callid != -1) {
|
|
GPU_shader_uniform_vector_int(shgroup->shader, shgroup->callid, 1, 1, &callid);
|
|
callid += 1;
|
|
}
|
|
|
|
/* Negative scale objects */
|
|
bool neg_scale = call->state->flag & DRW_CALL_NEGSCALE;
|
|
if (neg_scale != prev_neg_scale) {
|
|
glFrontFace((neg_scale) ? DST.backface : DST.frontface);
|
|
prev_neg_scale = neg_scale;
|
|
}
|
|
|
|
GPU_SELECT_LOAD_IF_PICKSEL_CALL(call);
|
|
draw_geometry_prepare(shgroup, call);
|
|
|
|
switch (call->type) {
|
|
case DRW_CALL_SINGLE:
|
|
draw_geometry_execute(shgroup, call->single.geometry);
|
|
break;
|
|
case DRW_CALL_RANGE:
|
|
draw_geometry_execute_ex(shgroup, call->range.geometry, call->range.start, call->range.count, false);
|
|
break;
|
|
case DRW_CALL_INSTANCES:
|
|
draw_geometry_execute_ex(shgroup, call->instances.geometry, 0, *call->instances.count, true);
|
|
break;
|
|
case DRW_CALL_GENERATE:
|
|
call->generate.geometry_fn(shgroup, draw_geometry_execute, call->generate.user_data);
|
|
break;
|
|
case DRW_CALL_PROCEDURAL:
|
|
GPU_draw_primitive(call->procedural.prim_type, call->procedural.vert_count);
|
|
break;
|
|
default:
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
/* Reset state */
|
|
glFrontFace(DST.frontface);
|
|
}
|
|
|
|
if (use_tfeedback) {
|
|
GPU_shader_transform_feedback_disable(shgroup->shader);
|
|
}
|
|
}
|
|
|
|
static void drw_update_view(void)
|
|
{
|
|
if (DST.dirty_mat) {
|
|
DST.state_cache_id++;
|
|
DST.dirty_mat = false;
|
|
|
|
DRW_uniformbuffer_update(G_draw.view_ubo, &DST.view_data);
|
|
|
|
/* Catch integer wrap around. */
|
|
if (UNLIKELY(DST.state_cache_id == 0)) {
|
|
DST.state_cache_id = 1;
|
|
/* We must reset all CallStates to ensure that not
|
|
* a single one stayed with cache_id equal to 1. */
|
|
BLI_mempool_iter iter;
|
|
DRWCallState *state;
|
|
BLI_mempool_iternew(DST.vmempool->states, &iter);
|
|
while ((state = BLI_mempool_iterstep(&iter))) {
|
|
state->cache_id = 0;
|
|
}
|
|
}
|
|
|
|
/* TODO dispatch threads to compute matrices/culling */
|
|
}
|
|
|
|
draw_clipping_setup_from_view();
|
|
}
|
|
|
|
static void drw_draw_pass_ex(DRWPass *pass, DRWShadingGroup *start_group, DRWShadingGroup *end_group)
|
|
{
|
|
if (start_group == NULL) {
|
|
return;
|
|
}
|
|
|
|
DST.shader = NULL;
|
|
|
|
BLI_assert(DST.buffer_finish_called && "DRW_render_instance_buffer_finish had not been called before drawing");
|
|
|
|
drw_update_view();
|
|
|
|
/* GPU_framebuffer_clear calls can change the state outside the DRW module.
|
|
* Force reset the affected states to avoid problems later. */
|
|
drw_state_set(DST.state | DRW_STATE_WRITE_DEPTH | DRW_STATE_WRITE_COLOR);
|
|
|
|
drw_state_set(pass->state);
|
|
|
|
DRW_stats_query_start(pass->name);
|
|
|
|
for (DRWShadingGroup *shgroup = start_group; shgroup; shgroup = shgroup->next) {
|
|
draw_shgroup(shgroup, pass->state);
|
|
/* break if upper limit */
|
|
if (shgroup == end_group) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Clear Bound textures */
|
|
for (int i = 0; i < GPU_max_textures(); i++) {
|
|
if (DST.RST.bound_texs[i] != NULL) {
|
|
GPU_texture_unbind(DST.RST.bound_texs[i]);
|
|
DST.RST.bound_texs[i] = NULL;
|
|
}
|
|
}
|
|
|
|
/* Clear Bound Ubos */
|
|
for (int i = 0; i < GPU_max_ubo_binds(); i++) {
|
|
if (DST.RST.bound_ubos[i] != NULL) {
|
|
GPU_uniformbuffer_unbind(DST.RST.bound_ubos[i]);
|
|
DST.RST.bound_ubos[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (DST.shader) {
|
|
GPU_shader_unbind();
|
|
DST.shader = NULL;
|
|
}
|
|
|
|
/* HACK: Rasterized discard can affect clear commands which are not
|
|
* part of a DRWPass (as of now). So disable rasterized discard here
|
|
* if it has been enabled. */
|
|
if ((DST.state & DRW_STATE_RASTERIZER_ENABLED) == 0) {
|
|
drw_state_set((DST.state & ~DRW_STATE_RASTERIZER_ENABLED) | DRW_STATE_DEFAULT);
|
|
}
|
|
|
|
DRW_stats_query_end();
|
|
}
|
|
|
|
void DRW_draw_pass(DRWPass *pass)
|
|
{
|
|
drw_draw_pass_ex(pass, pass->shgroups.first, pass->shgroups.last);
|
|
}
|
|
|
|
/* Draw only a subset of shgroups. Used in special situations as grease pencil strokes */
|
|
void DRW_draw_pass_subset(DRWPass *pass, DRWShadingGroup *start_group, DRWShadingGroup *end_group)
|
|
{
|
|
drw_draw_pass_ex(pass, start_group, end_group);
|
|
}
|
|
|
|
/** \} */
|