This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/gpu/intern/gpu_matrix.c
Sergey Sharybin ba4d23fe37 Fix node editor drawing when built with core profile
There are two major things in this commit.

First one is to have proper stack for projection matrices. This is
something what OpenGL specification grants to have at least 2 elements
for and what is required to have for proper editor drawing without
refactoring the way how we restore projection matrix.

Supporting this stack have following advantages:

- Our GPU stack is closer to OpenGL specs, making it easier to follow
  by other developers who are always familiar with OpenGL.

- Makes it easier to port all editors to a new API.

- Should help us getting rid of extra matrix push/pop added in
  various commits to 2.8 branch.

The new API follows the following convention:

- gpuPushMatrix/gpuPopMatrix ALWAYS deals with model view matrix
  and nothing more.

  While this name does not fully indicate that it's only model view
  matrix operator, it matches behavior of other matrix operations
  such as transform which also doesn't indicate what matrix type
  they are operating on.

- Projection matrix has dedicated calls for push/pop which are
  gpuPushProjectionMatrix/gpuPopProjectionMatrix.
2017-04-19 15:22:58 +02:00

850 lines
17 KiB
C

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* 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 ipmlied 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.
*
* The Original Code is Copyright (C) 2012 Blender Foundation.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Alexandr Kuznetsov, Jason Wilkins, Mike Erwin
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file source/blender/gpu/intern/gpu_matrix.c
* \ingroup gpu
*/
#define SUPPRESS_GENERIC_MATRIX_API
#include "GPU_matrix.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#define DEBUG_MATRIX_BIND 0
#define MATRIX_STACK_DEPTH 32
typedef float Mat4[4][4];
typedef float Mat3[3][3];
typedef struct MatrixStack {
Mat4 stack[MATRIX_STACK_DEPTH];
unsigned int top;
} MatrixStack;
typedef struct {
MatrixStack model_view_stack;
MatrixStack projection_stack;
bool dirty;
/* TODO: cache of derived matrices (Normal, MVP, inverse MVP, etc)
* generate as needed for shaders, invalidate when original matrices change
*
* TODO: separate Model from View transform? Batches/objects have model,
* camera/eye has view & projection
*/
} MatrixState;
#define MATRIX_4X4_IDENTITY {{1.0f, 0.0f, 0.0f, 0.0f}, \
{0.0f, 1.0f, 0.0f, 0.0f}, \
{0.0f, 0.0f, 1.0f, 0.0f}, \
{0.0f, 0.0f, 0.0f, 1.0f}}
static MatrixState state = {
.model_view_stack = {{MATRIX_4X4_IDENTITY}, 0},
.projection_stack = {{MATRIX_4X4_IDENTITY}, 0},
.dirty = true
};
#undef MATRIX_4X4_IDENTITY
#define ModelViewStack state.model_view_stack
#define ModelView ModelViewStack.stack[ModelViewStack.top]
#define ProjectionStack state.projection_stack
#define Projection ProjectionStack.stack[ProjectionStack.top]
void gpuMatrixReset(void)
{
state.model_view_stack.top = 0;
state.projection_stack.top = 0;
unit_m4(ModelView);
unit_m4(Projection);
state.dirty = true;
}
#ifdef WITH_GPU_SAFETY
/* Check if matrix is numerically good */
static void checkmat(cosnt float *m)
{
const int n = 16;
for (int i = 0; i < n; i++) {
#if _MSC_VER
BLI_assert(_finite(m[i]));
#else
BLI_assert(!isinf(m[i]));
#endif
}
}
#define CHECKMAT(m) checkmat((const float*)m)
#else
#define CHECKMAT(m)
#endif
void gpuPushMatrix(void)
{
#if SUPPORT_LEGACY_MATRIX
{
glPushMatrix();
state.dirty = true;
return;
}
#endif
BLI_assert(ModelViewStack.top < MATRIX_STACK_DEPTH);
ModelViewStack.top++;
copy_m4_m4(ModelView, ModelViewStack.stack[ModelViewStack.top - 1]);
}
void gpuPopMatrix(void)
{
#if SUPPORT_LEGACY_MATRIX
{
glPopMatrix();
state.dirty = true;
return;
}
#endif
BLI_assert(ModelViewStack.top > 0);
ModelViewStack.top--;
state.dirty = true;
}
void gpuPushProjectionMatrix(void)
{
#if SUPPORT_LEGACY_MATRIX
{
glPushMatrix();
state.dirty = true;
return;
}
#endif
BLI_assert(ProjectionStack.top < MATRIX_STACK_DEPTH);
ProjectionStack.top++;
copy_m4_m4(Projection, ProjectionStack.stack[ProjectionStack.top - 1]);
}
void gpuPopProjectionMatrix(void)
{
#if SUPPORT_LEGACY_MATRIX
{
glPopMatrix();
state.dirty = true;
return;
}
#endif
BLI_assert(ProjectionStack.top > 0);
ProjectionStack.top--;
state.dirty = true;
}
void gpuLoadMatrix(const float m[4][4])
{
#if SUPPORT_LEGACY_MATRIX
{
glLoadMatrixf((const float*) m);
state.dirty = true;
return;
}
#endif
copy_m4_m4(ModelView, m);
CHECKMAT(ModelView3D);
state.dirty = true;
}
void gpuLoadProjectionMatrix(const float m[4][4])
{
#if SUPPORT_LEGACY_MATRIX
{
GLenum mode;
glGetIntegerv(GL_MATRIX_MODE, (GLint*)&mode);
if (mode != GL_PROJECTION) {
glMatrixMode(GL_PROJECTION);
}
glLoadMatrixf((const float*) m);
if (mode != GL_PROJECTION_MATRIX) {
glMatrixMode(mode); /* restore */
}
state.dirty = true;
return;
}
#endif
copy_m4_m4(Projection, m);
CHECKMAT(Projection3D);
state.dirty = true;
}
void gpuLoadIdentity(void)
{
unit_m4(ModelView);
#if SUPPORT_LEGACY_MATRIX
glLoadIdentity();
#endif
state.dirty = true;
}
void gpuTranslate2f(float x, float y)
{
#if SUPPORT_LEGACY_MATRIX
{
glTranslatef(x, y, 0.0f);
state.dirty = true;
return;
}
#endif
Mat4 m;
unit_m4(m);
m[3][0] = x;
m[3][1] = y;
gpuMultMatrix(m);
}
void gpuTranslate2fv(const float vec[2])
{
gpuTranslate2f(vec[0], vec[1]);
}
void gpuTranslate3f(float x, float y, float z)
{
#if SUPPORT_LEGACY_MATRIX
{
glTranslatef(x, y, z);
state.dirty = true;
return;
}
#endif
#if 1
translate_m4(ModelView, x, y, z);
CHECKMAT(ModelView);
#else /* above works well in early testing, below is generic version */
Mat4 m;
unit_m4(m);
m[3][0] = x;
m[3][1] = y;
m[3][2] = z;
gpuMultMatrix(m);
#endif
state.dirty = true;
}
void gpuTranslate3fv(const float vec[3])
{
gpuTranslate3f(vec[0], vec[1], vec[2]);
}
void gpuScaleUniform(float factor)
{
Mat4 m;
scale_m4_fl(m, factor);
gpuMultMatrix(m);
#if SUPPORT_LEGACY_MATRIX
glScalef(factor, factor, factor); /* always scale Z since we can't distinguish 2D from 3D */
state.dirty = true;
#endif
}
void gpuScale2f(float x, float y)
{
#if SUPPORT_LEGACY_MATRIX
{
glScalef(x, y, 1.0f);
state.dirty = true;
return;
}
#endif
Mat4 m = {{0.0f}};
m[0][0] = x;
m[1][1] = y;
m[2][2] = 1.0f;
m[3][3] = 1.0f;
gpuMultMatrix(m);
}
void gpuScale2fv(const float vec[2])
{
gpuScale2f(vec[0], vec[1]);
}
void gpuScale3f(float x, float y, float z)
{
#if SUPPORT_LEGACY_MATRIX
{
glScalef(x, y, z);
state.dirty = true;
return;
}
#endif
Mat4 m = {{0.0f}};
m[0][0] = x;
m[1][1] = y;
m[2][2] = z;
m[3][3] = 1.0f;
gpuMultMatrix(m);
}
void gpuScale3fv(const float vec[3])
{
gpuScale3f(vec[0], vec[1], vec[2]);
}
void gpuMultMatrix(const float m[4][4])
{
#if SUPPORT_LEGACY_MATRIX
{
glMultMatrixf((const float*) m);
state.dirty = true;
return;
}
#endif
mul_m4_m4_post(ModelView, m);
CHECKMAT(ModelView);
state.dirty = true;
}
void gpuRotate2D(float deg)
{
#if SUPPORT_LEGACY_MATRIX
{
glRotatef(deg, 0.0f, 0.0f, 1.0f);
state.dirty = true;
return;
}
#endif
/* essentially RotateAxis('Z')
* TODO: simpler math for 2D case
*/
rotate_m4(ModelView, 'Z', DEG2RADF(deg));
}
void gpuRotate3f(float deg, float x, float y, float z)
{
const float axis[3] = {x, y, z};
gpuRotate3fv(deg, axis);
}
void gpuRotate3fv(float deg, const float axis[3])
{
#if SUPPORT_LEGACY_MATRIX
{
glRotatef(deg, axis[0], axis[1], axis[2]);
state.dirty = true;
return;
}
#endif
Mat4 m;
axis_angle_to_mat4(m, axis, DEG2RADF(deg));
gpuMultMatrix(m);
}
void gpuRotateAxis(float deg, char axis)
{
#if SUPPORT_LEGACY_MATRIX
{
float a[3] = { 0.0f };
switch (axis) {
case 'X': a[0] = 1.0f; break;
case 'Y': a[1] = 1.0f; break;
case 'Z': a[2] = 1.0f; break;
default: BLI_assert(false); /* bad axis */
}
glRotatef(deg, a[0], a[1], a[2]);
state.dirty = true;
return;
}
#endif
/* rotate_m4 works in place */
rotate_m4(ModelView, axis, DEG2RADF(deg));
CHECKMAT(ModelView);
state.dirty = true;
}
static void mat4_ortho_set(float m[4][4], float left, float right, float bottom, float top, float near, float far)
{
m[0][0] = 2.0f / (right - left);
m[1][0] = 0.0f;
m[2][0] = 0.0f;
m[3][0] = -(right + left) / (right - left);
m[0][1] = 0.0f;
m[1][1] = 2.0f / (top - bottom);
m[2][1] = 0.0f;
m[3][1] = -(top + bottom) / (top - bottom);
m[0][2] = 0.0f;
m[1][2] = 0.0f;
m[2][2] = -2.0f / (far - near);
m[3][2] = -(far + near) / (far - near);
m[0][3] = 0.0f;
m[1][3] = 0.0f;
m[2][3] = 0.0f;
m[3][3] = 1.0f;
state.dirty = true;
}
static void mat4_frustum_set(float m[4][4], float left, float right, float bottom, float top, float near, float far)
{
m[0][0] = 2.0f * near / (right - left);
m[1][0] = 0.0f;
m[2][0] = (right + left) / (right - left);
m[3][0] = 0.0f;
m[0][1] = 0.0f;
m[1][1] = 2.0f * near / (top - bottom);
m[2][1] = (top + bottom) / (top - bottom);
m[3][1] = 0.0f;
m[0][2] = 0.0f;
m[1][2] = 0.0f;
m[2][2] = -(far + near) / (far - near);
m[3][2] = -2.0f * far * near / (far - near);
m[0][3] = 0.0f;
m[1][3] = 0.0f;
m[2][3] = -1.0f;
m[3][3] = 0.0f;
state.dirty = true;
}
static void mat4_look_from_origin(float m[4][4], float lookdir[3], float camup[3])
{
/* This function is loosely based on Mesa implementation.
*
* SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
* Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice including the dates of first publication and
* either this permission notice or a reference to
* http://oss.sgi.com/projects/FreeB/
* shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of Silicon Graphics, Inc.
* shall not be used in advertising or otherwise to promote the sale, use or
* other dealings in this Software without prior written authorization from
* Silicon Graphics, Inc.
*/
float side[3];
normalize_v3(lookdir);
cross_v3_v3v3(side, lookdir, camup);
normalize_v3(side);
cross_v3_v3v3(camup, side, lookdir);
m[0][0] = side[0];
m[1][0] = side[1];
m[2][0] = side[2];
m[3][0] = 0.0f;
m[0][1] = camup[0];
m[1][1] = camup[1];
m[2][1] = camup[2];
m[3][1] = 0.0f;
m[0][2] = -lookdir[0];
m[1][2] = -lookdir[1];
m[2][2] = -lookdir[2];
m[3][2] = 0.0f;
m[0][3] = 0.0f;
m[1][3] = 0.0f;
m[2][3] = 0.0f;
m[3][3] = 1.0f;
state.dirty = true;
}
void gpuOrtho(float left, float right, float bottom, float top, float near, float far)
{
#if SUPPORT_LEGACY_MATRIX
{
GLenum mode;
glGetIntegerv(GL_MATRIX_MODE, (GLint*)&mode);
if (mode != GL_PROJECTION) {
glMatrixMode(GL_PROJECTION);
}
glLoadIdentity();
glOrtho(left, right, bottom, top, near, far);
if (mode != GL_PROJECTION_MATRIX) {
glMatrixMode(mode); /* restore */
}
state.dirty = true;
return;
}
#endif
mat4_ortho_set(Projection, left, right, bottom, top, near, far);
CHECKMAT(Projection);
state.dirty = true;
}
void gpuOrtho2D(float left, float right, float bottom, float top)
{
#if SUPPORT_LEGACY_MATRIX
{
GLenum mode;
glGetIntegerv(GL_MATRIX_MODE, (GLint*)&mode);
if (mode != GL_PROJECTION) {
glMatrixMode(GL_PROJECTION);
}
glLoadIdentity();
glOrtho(left, right, bottom, top, -1.0f, 1.0f);
if (mode != GL_PROJECTION_MATRIX) {
glMatrixMode(mode); /* restore */
}
state.dirty = true;
return;
}
#endif
Mat4 m;
mat4_ortho_set(m, left, right, bottom, top, -1.0f, 1.0f);
CHECKMAT(Projection2D);
state.dirty = true;
}
void gpuFrustum(float left, float right, float bottom, float top, float near, float far)
{
#if SUPPORT_LEGACY_MATRIX
{
GLenum mode;
glGetIntegerv(GL_MATRIX_MODE, (GLint*)&mode);
if (mode != GL_PROJECTION) {
glMatrixMode(GL_PROJECTION);
}
glLoadIdentity();
glFrustum(left, right, bottom, top, near, far);
if (mode != GL_PROJECTION_MATRIX) {
glMatrixMode(mode); /* restore */
}
state.dirty = true;
return;
}
#endif
mat4_frustum_set(Projection, left, right, bottom, top, near, far);
CHECKMAT(Projection);
state.dirty = true;
}
void gpuPerspective(float fovy, float aspect, float near, float far)
{
float half_height = tanf(fovy * (float)(M_PI / 360.0)) * near;
float half_width = half_height * aspect;
gpuFrustum(-half_width, +half_width, -half_height, +half_height, near, far);
}
void gpuLookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)
{
Mat4 cm;
float lookdir[3];
float camup[3] = {upX, upY, upZ};
lookdir[0] = centerX - eyeX;
lookdir[1] = centerY - eyeY;
lookdir[2] = centerZ - eyeZ;
mat4_look_from_origin(cm, lookdir, camup);
gpuMultMatrix(cm);
gpuTranslate3f(-eyeX, -eyeY, -eyeZ);
}
void gpuProject(const float world[3], const float model[4][4], const float proj[4][4], const int view[4], float win[3])
{
float v[4];
mul_v4_m4v3(v, model, world);
mul_m4_v4(proj, v);
if (v[3] != 0.0f) {
mul_v3_fl(v, 1.0f / v[3]);
}
win[0] = view[0] + (view[2] * (v[0] + 1)) * 0.5f;
win[1] = view[1] + (view[3] * (v[1] + 1)) * 0.5f;
win[2] = (v[2] + 1) * 0.5f;
}
bool gpuUnProject(const float win[3], const float model[4][4], const float proj[4][4], const int view[4], float world[3])
{
float pm[4][4];
float in[4];
float out[4];
mul_m4_m4m4(pm, proj, model);
if (!invert_m4(pm)) {
zero_v3(world);
return false;
}
in[0] = win[0];
in[1] = win[1];
in[2] = win[2];
in[3] = 1;
/* Map x and y from window coordinates */
in[0] = (in[0] - view[0]) / view[2];
in[1] = (in[1] - view[1]) / view[3];
/* Map to range -1 to +1 */
in[0] = 2 * in[0] - 1;
in[1] = 2 * in[1] - 1;
in[2] = 2 * in[2] - 1;
mul_v4_m4v3(out, pm, in);
if (out[3] == 0.0f) {
copy_v3_v3(world, out);
return false;
}
mul_v3_v3fl(world, out, 1.0f / out[3]);
return true;
}
const float (*gpuGetModelViewMatrix(float m[4][4]))[4]
{
#if SUPPORT_LEGACY_MATRIX
{
if (m == NULL) {
static Mat4 temp;
m = temp;
}
glGetFloatv(GL_MODELVIEW_MATRIX, (float*)m);
return m;
}
#endif
if (m) {
copy_m4_m4(m, ModelView);
return m;
}
else {
return ModelView;
}
}
const float (*gpuGetProjectionMatrix(float m[4][4]))[4]
{
#if SUPPORT_LEGACY_MATRIX
{
if (m == NULL) {
static Mat4 temp;
m = temp;
}
glGetFloatv(GL_PROJECTION_MATRIX, (float*)m);
return m;
}
#endif
if (m) {
copy_m4_m4(m, Projection);
return m;
}
else {
return Projection;
}
}
const float (*gpuGetModelViewProjectionMatrix(float m[4][4]))[4]
{
if (m == NULL) {
static Mat4 temp;
m = temp;
}
#if SUPPORT_LEGACY_MATRIX
{
Mat4 proj;
glGetFloatv(GL_MODELVIEW_MATRIX, (float*)m);
glGetFloatv(GL_PROJECTION_MATRIX, (float*)proj);
mul_m4_m4_pre(m, proj);
return m;
}
#endif
mul_m4_m4m4(m, Projection, ModelView);
return m;
}
const float (*gpuGetNormalMatrix(float m[3][3]))[3]
{
if (m == NULL) {
static Mat3 temp3;
m = temp3;
}
copy_m3_m4(m, (const float (*)[4])gpuGetModelViewMatrix(NULL));
invert_m3(m);
transpose_m3(m);
return m;
}
const float (*gpuGetNormalMatrixInverse(float m[3][3]))[3]
{
if (m == NULL) {
static Mat3 temp3;
m = temp3;
}
gpuGetNormalMatrix(m);
invert_m3(m);
return m;
}
void gpuBindMatrices(const ShaderInterface* shaderface)
{
/* set uniform values to matrix stack values
* call this before a draw call if desired matrices are dirty
* call glUseProgram before this, as glUniform expects program to be bound
*/
const ShaderInput *MV = ShaderInterface_builtin_uniform(shaderface, UNIFORM_MODELVIEW);
const ShaderInput *P = ShaderInterface_builtin_uniform(shaderface, UNIFORM_PROJECTION);
const ShaderInput *MVP = ShaderInterface_builtin_uniform(shaderface, UNIFORM_MVP);
const ShaderInput *N = ShaderInterface_builtin_uniform(shaderface, UNIFORM_NORMAL);
const ShaderInput *MV_inv = ShaderInterface_builtin_uniform(shaderface, UNIFORM_MODELVIEW_INV);
const ShaderInput *P_inv = ShaderInterface_builtin_uniform(shaderface, UNIFORM_PROJECTION_INV);
if (MV) {
#if DEBUG_MATRIX_BIND
puts("setting MV matrix");
#endif
glUniformMatrix4fv(MV->location, 1, GL_FALSE, (const float *)gpuGetModelViewMatrix(NULL));
}
if (P) {
#if DEBUG_MATRIX_BIND
puts("setting P matrix");
#endif
glUniformMatrix4fv(P->location, 1, GL_FALSE, (const float *)gpuGetProjectionMatrix(NULL));
}
if (MVP) {
#if DEBUG_MATRIX_BIND
puts("setting MVP matrix");
#endif
glUniformMatrix4fv(MVP->location, 1, GL_FALSE, (const float *)gpuGetModelViewProjectionMatrix(NULL));
}
if (N) {
#if DEBUG_MATRIX_BIND
puts("setting normal matrix");
#endif
glUniformMatrix3fv(N->location, 1, GL_FALSE, (const float *)gpuGetNormalMatrix(NULL));
}
if (MV_inv) {
Mat4 m;
gpuGetModelViewMatrix(m);
invert_m4(m);
glUniformMatrix4fv(MV_inv->location, 1, GL_FALSE, (const float*) m);
}
if (P_inv) {
Mat4 m;
gpuGetProjectionMatrix(m);
invert_m4(m);
glUniformMatrix4fv(P_inv->location, 1, GL_FALSE, (const float*) m);
}
state.dirty = false;
}
bool gpuMatricesDirty(void)
{
return state.dirty;
}