raylib/src/rlgl.c
Ray 84d1b19f61 Added custom Oculus CV1 parameters
Matching the same stereo rendering result given by Oculus PC SDK for
Oculus Rift CV1 is very difficult because hardware has changed a lot and
DK2 distortion shader and parameters don't fit on CV1. Some custom
parameters have been calculated to simulate kind of CV1 stereo
rendering. Further work is required on this point.
2016-07-11 14:43:58 +02:00

4245 lines
170 KiB
C

/**********************************************************************************************
*
* rlgl - raylib OpenGL abstraction layer
*
* raylib now uses OpenGL 1.1 style functions (rlVertex) that are mapped to selected OpenGL version:
* OpenGL 1.1 - Direct map rl* -> gl*
* OpenGL 3.3 - Vertex data is stored in VAOs, call rlglDraw() to render
* OpenGL ES 2 - Vertex data is stored in VBOs or VAOs (when available), call rlglDraw() to render
*
* Copyright (c) 2014 Ramon Santamaria (@raysan5)
*
* This software is provided "as-is", without any express or implied warranty. In no event
* will the authors be held liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose, including commercial
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you
* wrote the original software. If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
* as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
**********************************************************************************************/
#include "rlgl.h"
#include <stdio.h> // Required for: fopen(), fclose(), fread()... [Used only on ReadTextFile()]
#include <stdlib.h> // Required for: malloc(), free(), rand()
#include <string.h> // Required for: strcmp(), strlen(), strtok()
#include <math.h> // Required for: atan2()
#ifndef RLGL_STANDALONE
#include "raymath.h" // Required for Vector3 and Matrix functions
#endif
#if defined(GRAPHICS_API_OPENGL_11)
#ifdef __APPLE__
#include <OpenGL/gl.h> // OpenGL 1.1 library for OSX
#else
#include <GL/gl.h> // OpenGL 1.1 library
#endif
#endif
#if defined(GRAPHICS_API_OPENGL_21)
#define GRAPHICS_API_OPENGL_33
#endif
#if defined(GRAPHICS_API_OPENGL_33)
#ifdef __APPLE__
#include <OpenGL/gl3.h> // OpenGL 3 library for OSX
#else
#define GLAD_IMPLEMENTATION
#if defined(RLGL_STANDALONE)
#include "glad.h" // GLAD extensions loading library, includes OpenGL headers
#else
#include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers
#endif
#endif
#endif
#if defined(GRAPHICS_API_OPENGL_ES2)
#include <EGL/egl.h> // EGL library
#include <GLES2/gl2.h> // OpenGL ES 2.0 library
#include <GLES2/gl2ext.h> // OpenGL ES 2.0 extensions library
#endif
#if defined(RLGL_STANDALONE)
#include <stdarg.h> // Required for: va_list, va_start(), vfprintf(), va_end() [Used only on TraceLog()]
#endif
#if !defined(GRAPHICS_API_OPENGL_11) && !defined(RLGL_NO_STANDARD_SHADER)
#include "shader_standard.h" // Standard shader to be embedded
#endif
#if !defined(GRAPHICS_API_OPENGL_11) && !defined(RLGL_NO_DISTORTION_SHADER)
#include "shader_distortion.h" // Distortion shader to be embedded
#endif
//#define RLGL_OCULUS_SUPPORT // Enable Oculus Rift code
#if defined(RLGL_OCULUS_SUPPORT)
#include "external/OculusSDK/LibOVR/Include/OVR_CAPI_GL.h" // Oculus SDK for OpenGL
#endif
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#define MATRIX_STACK_SIZE 16 // Matrix stack max size
#define MAX_DRAWS_BY_TEXTURE 256 // Draws are organized by texture changes
#define TEMP_VERTEX_BUFFER_SIZE 4096 // Temporal Vertex Buffer (required for vertex-transformations)
// NOTE: Every vertex are 3 floats (12 bytes)
#define MAX_LIGHTS 8 // Max lights supported by standard shader
#ifndef GL_SHADING_LANGUAGE_VERSION
#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
#endif
#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
#endif
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
#endif
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
#endif
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#endif
#ifndef GL_ETC1_RGB8_OES
#define GL_ETC1_RGB8_OES 0x8D64
#endif
#ifndef GL_COMPRESSED_RGB8_ETC2
#define GL_COMPRESSED_RGB8_ETC2 0x9274
#endif
#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC
#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
#endif
#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
#endif
#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
#endif
#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR
#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0
#endif
#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR
#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7
#endif
#if defined(GRAPHICS_API_OPENGL_11)
#define GL_UNSIGNED_SHORT_5_6_5 0x8363
#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
#endif
#if defined(GRAPHICS_API_OPENGL_ES2)
#define glClearDepth glClearDepthf
#define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER
#define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER
#endif
// Default vertex attribute names on shader to set location points
#define DEFAULT_ATTRIB_POSITION_NAME "vertexPosition" // shader-location = 0
#define DEFAULT_ATTRIB_TEXCOORD_NAME "vertexTexCoord" // shader-location = 1
#define DEFAULT_ATTRIB_NORMAL_NAME "vertexNormal" // shader-location = 2
#define DEFAULT_ATTRIB_COLOR_NAME "vertexColor" // shader-location = 3
#define DEFAULT_ATTRIB_TANGENT_NAME "vertexTangent" // shader-location = 4
#define DEFAULT_ATTRIB_TEXCOORD2_NAME "vertexTexCoord2" // shader-location = 5
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Dynamic vertex buffers (position + texcoords + colors + indices arrays)
typedef struct {
int vCounter; // vertex position counter to process (and draw) from full buffer
int tcCounter; // vertex texcoord counter to process (and draw) from full buffer
int cCounter; // vertex color counter to process (and draw) from full buffer
float *vertices; // vertex position (XYZ - 3 components per vertex) (shader-location = 0)
float *texcoords; // vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1)
unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3)
#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
unsigned int *indices; // vertex indices (in case vertex data comes indexed) (6 indices per quad)
#elif defined(GRAPHICS_API_OPENGL_ES2)
unsigned short *indices; // vertex indices (in case vertex data comes indexed) (6 indices per quad)
// NOTE: 6*2 byte = 12 byte, not alignment problem!
#endif
unsigned int vaoId; // OpenGL Vertex Array Object id
unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data)
} DynamicBuffer;
// Draw call type
// NOTE: Used to track required draw-calls, organized by texture
typedef struct {
int vertexCount;
GLuint vaoId;
GLuint textureId;
GLuint shaderId;
Matrix projection;
Matrix modelview;
// TODO: Store additional draw state data
//int blendMode;
//Guint fboId;
} DrawCall;
// Head-Mounted-Display device parameters
typedef struct {
int hResolution; // HMD horizontal resolution in pixels
int vResolution; // HMD vertical resolution in pixels
float hScreenSize; // HMD horizontal size in meters
float vScreenSize; // HMD vertical size in meters
float vScreenCenter; // HMD screen center in meters
float eyeToScreenDistance; // HMD distance between eye and display in meters
float lensSeparationDistance; // HMD lens separation distance in meters
float interpupillaryDistance; // HMD IPD (distance between pupils) in meters
float distortionK[4]; // HMD lens distortion constant parameters
float chromaAbCorrection[4]; // HMD chromatic aberration correction parameters
} VrDeviceInfo;
// VR Stereo rendering configuration for simulator
typedef struct {
RenderTexture2D stereoFbo; // VR stereo rendering framebuffer
Shader distortionShader; // VR stereo rendering distortion shader
//Rectangle eyesViewport[2]; // VR stereo rendering eyes viewports
Matrix eyesProjection[2]; // VR stereo rendering eyes projection matrices
Matrix eyesViewOffset[2]; // VR stereo rendering eyes view offset matrices
} VrStereoConfig;
#if defined(RLGL_OCULUS_SUPPORT)
typedef struct OculusBuffer {
ovrTextureSwapChain textureChain;
GLuint depthId;
GLuint fboId;
int width;
int height;
} OculusBuffer;
typedef struct OculusMirror {
ovrMirrorTexture texture;
GLuint fboId;
int width;
int height;
} OculusMirror;
typedef struct OculusLayer {
ovrViewScaleDesc viewScaleDesc;
ovrLayerEyeFov eyeLayer; // layer 0
//ovrLayerQuad quadLayer; // TODO: layer 1: '2D' quad for GUI
Matrix eyeProjections[2];
int width;
int height;
} OculusLayer;
#endif
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
static Matrix stack[MATRIX_STACK_SIZE];
static int stackCounter = 0;
static Matrix modelview;
static Matrix projection;
static Matrix *currentMatrix;
static int currentMatrixMode;
static DrawMode currentDrawMode;
static float currentDepth = -1.0f;
static DynamicBuffer lines;
static DynamicBuffer triangles;
static DynamicBuffer quads;
// Default buffers draw calls
static DrawCall *draws;
static int drawsCounter;
// Temp vertex buffer to be used with rlTranslate, rlRotate, rlScale
static Vector3 *tempBuffer;
static int tempBufferCount = 0;
static bool useTempBuffer = false;
// Shader Programs
static Shader defaultShader;
static Shader standardShader; // Lazy initialization when GetStandardShader()
static Shader currentShader; // By default, defaultShader
static bool standardShaderLoaded = false; // Flag to track if standard shader has been loaded
// Flags for supported extensions
static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension)
// Compressed textures support flags
static bool texCompETC1Supported = false; // ETC1 texture compression support
static bool texCompETC2Supported = false; // ETC2/EAC texture compression support
static bool texCompPVRTSupported = false; // PVR texture compression support
static bool texCompASTCSupported = false; // ASTC texture compression support
// Lighting data
static Light lights[MAX_LIGHTS]; // Lights pool
static int lightsCount = 0; // Enabled lights counter
#endif
#if defined(RLGL_OCULUS_SUPPORT)
// OVR device variables
static ovrSession session; // Oculus session (pointer to ovrHmdStruct)
static ovrHmdDesc hmdDesc; // Oculus device descriptor parameters
static ovrGraphicsLuid luid; // Oculus locally unique identifier for the program (64 bit)
static OculusLayer layer; // Oculus drawing layer (similar to photoshop)
static OculusBuffer buffer; // Oculus internal buffers (texture chain and fbo)
static OculusMirror mirror; // Oculus mirror texture and fbo
static unsigned int frameIndex = 0; // Oculus frames counter, used to discard frames from chain
#endif
// VR global variables
static VrDeviceInfo hmd; // Current VR device info
static VrStereoConfig vrConfig; // VR stereo configuration for simulator
static bool vrDeviceReady = false; // VR device ready flag
static bool vrSimulator = false; // VR simulator enabled flag
static bool vrEnabled = false; // VR experience enabled (device or simulator)
static bool vrRendering = true; // VR stereo rendering enabled/disabled flag
// NOTE: This flag is useful to render data over stereo image (i.e. FPS)
#if defined(GRAPHICS_API_OPENGL_ES2)
// NOTE: VAO functionality is exposed through extensions (OES)
static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays;
static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray;
static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays;
//static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; // NOTE: Fails in WebGL, omitted
#endif
// Compressed textures support flags
static bool texCompDXTSupported = false; // DDS texture compression support
static bool npotSupported = false; // NPOT textures full support
static int blendMode = 0; // Track current blending mode
// White texture useful for plain color polys (required by shader)
static unsigned int whiteTexture;
// Default framebuffer size (required by Oculus device)
static int screenWidth; // Default framebuffer width
static int screenHeight; // Default framebuffer height
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat);
static unsigned int LoadShaderProgram(const char *vShaderStr, const char *fShaderStr); // Load custom shader strings and return program id
static Shader LoadDefaultShader(void); // Load default shader (just vertex positioning and texture coloring)
static Shader LoadStandardShader(void); // Load standard shader (support materials and lighting)
static void LoadDefaultShaderLocations(Shader *shader); // Bind default shader locations (attributes and uniforms)
static void UnloadDefaultShader(void); // Unload default shader
static void UnloadStandardShader(void); // Unload standard shader
static void LoadDefaultBuffers(void); // Load default internal buffers (lines, triangles, quads)
static void UpdateDefaultBuffers(void); // Update default internal buffers (VAOs/VBOs) with vertex data
static void DrawDefaultBuffers(int eyesCount); // Draw default internal buffers vertex data
static void UnloadDefaultBuffers(void); // Unload default internal buffers vertex data from CPU and GPU
// Configure stereo rendering (including distortion shader) with HMD device parameters
static void SetStereoConfig(VrDeviceInfo info);
// Set internal projection and modelview matrix depending on eyes tracking data
static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView);
static void SetShaderLights(Shader shader); // Sets shader uniform values for lights array
static char *ReadTextFile(const char *fileName);
#endif
#if defined(RLGL_OCULUS_SUPPORT)
static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height); // Load Oculus required buffers
static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer); // Unload texture required buffers
static OculusMirror LoadOculusMirror(ovrSession session, int width, int height); // Load Oculus mirror buffers
static void UnloadOculusMirror(ovrSession session, OculusMirror mirror); // Unload Oculus mirror buffers
static void BlitOculusMirror(ovrSession session, OculusMirror mirror); // Copy Oculus screen buffer to mirror texture
static OculusLayer InitOculusLayer(ovrSession session); // Init Oculus layer (similar to photoshop)
static Matrix FromOvrMatrix(ovrMatrix4f ovrM); // Convert from Oculus ovrMatrix4f struct to raymath Matrix struct
#endif
#if defined(GRAPHICS_API_OPENGL_11)
static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight);
static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight);
#endif
#if defined(RLGL_STANDALONE)
float *MatrixToFloat(Matrix mat); // Converts Matrix to float array
#endif
//----------------------------------------------------------------------------------
// Module Functions Definition - Matrix operations
//----------------------------------------------------------------------------------
#if defined(GRAPHICS_API_OPENGL_11)
// Fallback to OpenGL 1.1 function calls
//---------------------------------------
void rlMatrixMode(int mode)
{
switch (mode)
{
case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break;
case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break;
case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break;
default: break;
}
}
void rlFrustum(double left, double right, double bottom, double top, double near, double far)
{
glFrustum(left, right, bottom, top, near, far);
}
void rlOrtho(double left, double right, double bottom, double top, double near, double far)
{
glOrtho(left, right, bottom, top, near, far);
}
void rlPushMatrix(void) { glPushMatrix(); }
void rlPopMatrix(void) { glPopMatrix(); }
void rlLoadIdentity(void) { glLoadIdentity(); }
void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); }
void rlRotatef(float angleDeg, float x, float y, float z) { glRotatef(angleDeg, x, y, z); }
void rlScalef(float x, float y, float z) { glScalef(x, y, z); }
void rlMultMatrixf(float *mat) { glMultMatrixf(mat); }
#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Choose the current matrix to be transformed
void rlMatrixMode(int mode)
{
if (mode == RL_PROJECTION) currentMatrix = &projection;
else if (mode == RL_MODELVIEW) currentMatrix = &modelview;
//else if (mode == RL_TEXTURE) // Not supported
currentMatrixMode = mode;
}
// Push the current matrix to stack
void rlPushMatrix(void)
{
if (stackCounter == MATRIX_STACK_SIZE - 1)
{
TraceLog(ERROR, "Stack Buffer Overflow (MAX %i Matrix)", MATRIX_STACK_SIZE);
}
stack[stackCounter] = *currentMatrix;
rlLoadIdentity();
stackCounter++;
if (currentMatrixMode == RL_MODELVIEW) useTempBuffer = true;
}
// Pop lattest inserted matrix from stack
void rlPopMatrix(void)
{
if (stackCounter > 0)
{
Matrix mat = stack[stackCounter - 1];
*currentMatrix = mat;
stackCounter--;
}
}
// Reset current matrix to identity matrix
void rlLoadIdentity(void)
{
*currentMatrix = MatrixIdentity();
}
// Multiply the current matrix by a translation matrix
void rlTranslatef(float x, float y, float z)
{
Matrix matTranslation = MatrixTranslate(x, y, z);
MatrixTranspose(&matTranslation);
*currentMatrix = MatrixMultiply(*currentMatrix, matTranslation);
}
// Multiply the current matrix by a rotation matrix
void rlRotatef(float angleDeg, float x, float y, float z)
{
Matrix matRotation = MatrixIdentity();
Vector3 axis = (Vector3){ x, y, z };
VectorNormalize(&axis);
matRotation = MatrixRotate(axis, angleDeg*DEG2RAD);
MatrixTranspose(&matRotation);
*currentMatrix = MatrixMultiply(*currentMatrix, matRotation);
}
// Multiply the current matrix by a scaling matrix
void rlScalef(float x, float y, float z)
{
Matrix matScale = MatrixScale(x, y, z);
MatrixTranspose(&matScale);
*currentMatrix = MatrixMultiply(*currentMatrix, matScale);
}
// Multiply the current matrix by another matrix
void rlMultMatrixf(float *m)
{
// Matrix creation from array
Matrix mat = { m[0], m[1], m[2], m[3],
m[4], m[5], m[6], m[7],
m[8], m[9], m[10], m[11],
m[12], m[13], m[14], m[15] };
*currentMatrix = MatrixMultiply(*currentMatrix, mat);
}
// Multiply the current matrix by a perspective matrix generated by parameters
void rlFrustum(double left, double right, double bottom, double top, double near, double far)
{
Matrix matPerps = MatrixFrustum(left, right, bottom, top, near, far);
MatrixTranspose(&matPerps);
*currentMatrix = MatrixMultiply(*currentMatrix, matPerps);
}
// Multiply the current matrix by an orthographic matrix generated by parameters
void rlOrtho(double left, double right, double bottom, double top, double near, double far)
{
Matrix matOrtho = MatrixOrtho(left, right, bottom, top, near, far);
MatrixTranspose(&matOrtho);
*currentMatrix = MatrixMultiply(*currentMatrix, matOrtho);
}
#endif
// Set the viewport area (transformation from normalized device coordinates to window coordinates)
// NOTE: Updates global variables: screenWidth, screenHeight
void rlViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
//----------------------------------------------------------------------------------
// Module Functions Definition - Vertex level operations
//----------------------------------------------------------------------------------
#if defined(GRAPHICS_API_OPENGL_11)
// Fallback to OpenGL 1.1 function calls
//---------------------------------------
void rlBegin(int mode)
{
switch (mode)
{
case RL_LINES: glBegin(GL_LINES); break;
case RL_TRIANGLES: glBegin(GL_TRIANGLES); break;
case RL_QUADS: glBegin(GL_QUADS); break;
default: break;
}
}
void rlEnd() { glEnd(); }
void rlVertex2i(int x, int y) { glVertex2i(x, y); }
void rlVertex2f(float x, float y) { glVertex2f(x, y); }
void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); }
void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); }
void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); }
void rlColor4ub(byte r, byte g, byte b, byte a) { glColor4ub(r, g, b, a); }
void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); }
void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); }
#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Initialize drawing mode (how to organize vertex)
void rlBegin(int mode)
{
// Draw mode can only be RL_LINES, RL_TRIANGLES and RL_QUADS
currentDrawMode = mode;
}
// Finish vertex providing
void rlEnd(void)
{
if (useTempBuffer)
{
// NOTE: In this case, *currentMatrix is already transposed because transposing has been applied
// independently to translation-scale-rotation matrices -> t(M1 x M2) = t(M2) x t(M1)
// This way, rlTranslatef(), rlRotatef()... behaviour is the same than OpenGL 1.1
// Apply transformation matrix to all temp vertices
for (int i = 0; i < tempBufferCount; i++) VectorTransform(&tempBuffer[i], *currentMatrix);
// Deactivate tempBuffer usage to allow rlVertex3f do its job
useTempBuffer = false;
// Copy all transformed vertices to right VAO
for (int i = 0; i < tempBufferCount; i++) rlVertex3f(tempBuffer[i].x, tempBuffer[i].y, tempBuffer[i].z);
// Reset temp buffer
tempBufferCount = 0;
}
// Make sure vertexCount is the same for vertices-texcoords-normals-colors
// NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls.
switch (currentDrawMode)
{
case RL_LINES:
{
if (lines.vCounter != lines.cCounter)
{
int addColors = lines.vCounter - lines.cCounter;
for (int i = 0; i < addColors; i++)
{
lines.colors[4*lines.cCounter] = lines.colors[4*lines.cCounter - 4];
lines.colors[4*lines.cCounter + 1] = lines.colors[4*lines.cCounter - 3];
lines.colors[4*lines.cCounter + 2] = lines.colors[4*lines.cCounter - 2];
lines.colors[4*lines.cCounter + 3] = lines.colors[4*lines.cCounter - 1];
lines.cCounter++;
}
}
} break;
case RL_TRIANGLES:
{
if (triangles.vCounter != triangles.cCounter)
{
int addColors = triangles.vCounter - triangles.cCounter;
for (int i = 0; i < addColors; i++)
{
triangles.colors[4*triangles.cCounter] = triangles.colors[4*triangles.cCounter - 4];
triangles.colors[4*triangles.cCounter + 1] = triangles.colors[4*triangles.cCounter - 3];
triangles.colors[4*triangles.cCounter + 2] = triangles.colors[4*triangles.cCounter - 2];
triangles.colors[4*triangles.cCounter + 3] = triangles.colors[4*triangles.cCounter - 1];
triangles.cCounter++;
}
}
} break;
case RL_QUADS:
{
// Make sure colors count match vertex count
if (quads.vCounter != quads.cCounter)
{
int addColors = quads.vCounter - quads.cCounter;
for (int i = 0; i < addColors; i++)
{
quads.colors[4*quads.cCounter] = quads.colors[4*quads.cCounter - 4];
quads.colors[4*quads.cCounter + 1] = quads.colors[4*quads.cCounter - 3];
quads.colors[4*quads.cCounter + 2] = quads.colors[4*quads.cCounter - 2];
quads.colors[4*quads.cCounter + 3] = quads.colors[4*quads.cCounter - 1];
quads.cCounter++;
}
}
// Make sure texcoords count match vertex count
if (quads.vCounter != quads.tcCounter)
{
int addTexCoords = quads.vCounter - quads.tcCounter;
for (int i = 0; i < addTexCoords; i++)
{
quads.texcoords[2*quads.tcCounter] = 0.0f;
quads.texcoords[2*quads.tcCounter + 1] = 0.0f;
quads.tcCounter++;
}
}
// TODO: Make sure normals count match vertex count... if normals support is added in a future... :P
} break;
default: break;
}
// NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values,
// as well as depth buffer bit-depth (16bit or 24bit or 32bit)
// Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits)
currentDepth += (1.0f/20000.0f);
}
// Define one vertex (position)
void rlVertex3f(float x, float y, float z)
{
if (useTempBuffer)
{
tempBuffer[tempBufferCount].x = x;
tempBuffer[tempBufferCount].y = y;
tempBuffer[tempBufferCount].z = z;
tempBufferCount++;
}
else
{
switch (currentDrawMode)
{
case RL_LINES:
{
// Verify that MAX_LINES_BATCH limit not reached
if (lines.vCounter / 2 < MAX_LINES_BATCH)
{
lines.vertices[3*lines.vCounter] = x;
lines.vertices[3*lines.vCounter + 1] = y;
lines.vertices[3*lines.vCounter + 2] = z;
lines.vCounter++;
}
else TraceLog(ERROR, "MAX_LINES_BATCH overflow");
} break;
case RL_TRIANGLES:
{
// Verify that MAX_TRIANGLES_BATCH limit not reached
if (triangles.vCounter / 3 < MAX_TRIANGLES_BATCH)
{
triangles.vertices[3*triangles.vCounter] = x;
triangles.vertices[3*triangles.vCounter + 1] = y;
triangles.vertices[3*triangles.vCounter + 2] = z;
triangles.vCounter++;
}
else TraceLog(ERROR, "MAX_TRIANGLES_BATCH overflow");
} break;
case RL_QUADS:
{
// Verify that MAX_QUADS_BATCH limit not reached
if (quads.vCounter / 4 < MAX_QUADS_BATCH)
{
quads.vertices[3*quads.vCounter] = x;
quads.vertices[3*quads.vCounter + 1] = y;
quads.vertices[3*quads.vCounter + 2] = z;
quads.vCounter++;
draws[drawsCounter - 1].vertexCount++;
}
else TraceLog(ERROR, "MAX_QUADS_BATCH overflow");
} break;
default: break;
}
}
}
// Define one vertex (position)
void rlVertex2f(float x, float y)
{
rlVertex3f(x, y, currentDepth);
}
// Define one vertex (position)
void rlVertex2i(int x, int y)
{
rlVertex3f((float)x, (float)y, currentDepth);
}
// Define one vertex (texture coordinate)
// NOTE: Texture coordinates are limited to QUADS only
void rlTexCoord2f(float x, float y)
{
if (currentDrawMode == RL_QUADS)
{
quads.texcoords[2*quads.tcCounter] = x;
quads.texcoords[2*quads.tcCounter + 1] = y;
quads.tcCounter++;
}
}
// Define one vertex (normal)
// NOTE: Normals limited to TRIANGLES only ?
void rlNormal3f(float x, float y, float z)
{
// TODO: Normals usage...
}
// Define one vertex (color)
void rlColor4ub(byte x, byte y, byte z, byte w)
{
switch (currentDrawMode)
{
case RL_LINES:
{
lines.colors[4*lines.cCounter] = x;
lines.colors[4*lines.cCounter + 1] = y;
lines.colors[4*lines.cCounter + 2] = z;
lines.colors[4*lines.cCounter + 3] = w;
lines.cCounter++;
} break;
case RL_TRIANGLES:
{
triangles.colors[4*triangles.cCounter] = x;
triangles.colors[4*triangles.cCounter + 1] = y;
triangles.colors[4*triangles.cCounter + 2] = z;
triangles.colors[4*triangles.cCounter + 3] = w;
triangles.cCounter++;
} break;
case RL_QUADS:
{
quads.colors[4*quads.cCounter] = x;
quads.colors[4*quads.cCounter + 1] = y;
quads.colors[4*quads.cCounter + 2] = z;
quads.colors[4*quads.cCounter + 3] = w;
quads.cCounter++;
} break;
default: break;
}
}
// Define one vertex (color)
void rlColor4f(float r, float g, float b, float a)
{
rlColor4ub((byte)(r*255), (byte)(g*255), (byte)(b*255), (byte)(a*255));
}
// Define one vertex (color)
void rlColor3f(float x, float y, float z)
{
rlColor4ub((byte)(x*255), (byte)(y*255), (byte)(z*255), 255);
}
#endif
//----------------------------------------------------------------------------------
// Module Functions Definition - OpenGL equivalent functions (common to 1.1, 3.3+, ES2)
//----------------------------------------------------------------------------------
// Enable texture usage
void rlEnableTexture(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_11)
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, id);
#endif
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (draws[drawsCounter - 1].textureId != id)
{
if (draws[drawsCounter - 1].vertexCount > 0) drawsCounter++;
draws[drawsCounter - 1].textureId = id;
draws[drawsCounter - 1].vertexCount = 0;
}
#endif
}
// Disable texture usage
void rlDisableTexture(void)
{
#if defined(GRAPHICS_API_OPENGL_11)
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
}
// Enable rendering to texture (fbo)
void rlEnableRenderTexture(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glBindFramebuffer(GL_FRAMEBUFFER, id);
//glDisable(GL_CULL_FACE); // Allow double side drawing for texture flipping
//glCullFace(GL_FRONT);
#endif
}
// Disable rendering to texture
void rlDisableRenderTexture(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
//glEnable(GL_CULL_FACE);
//glCullFace(GL_BACK);
#endif
}
// Enable depth test
void rlEnableDepthTest(void)
{
glEnable(GL_DEPTH_TEST);
}
// Disable depth test
void rlDisableDepthTest(void)
{
glDisable(GL_DEPTH_TEST);
}
// Enable wire mode
void rlEnableWireMode(void)
{
#if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
// NOTE: glPolygonMode() not available on OpenGL ES
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
}
// Disable wire mode
void rlDisableWireMode(void)
{
#if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
// NOTE: glPolygonMode() not available on OpenGL ES
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
// Unload texture from GPU memory
void rlDeleteTextures(unsigned int id)
{
if (id != 0) glDeleteTextures(1, &id);
}
// Unload render texture from GPU memory
void rlDeleteRenderTextures(RenderTexture2D target)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (target.id != 0) glDeleteFramebuffers(1, &target.id);
if (target.texture.id != 0) glDeleteTextures(1, &target.texture.id);
if (target.depth.id != 0) glDeleteTextures(1, &target.depth.id);
TraceLog(INFO, "[FBO ID %i] Unloaded render texture data from VRAM (GPU)", target.id);
#endif
}
// Unload shader from GPU memory
void rlDeleteShader(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (id != 0) glDeleteProgram(id);
#endif
}
// Unload vertex data (VAO) from GPU memory
void rlDeleteVertexArrays(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (vaoSupported)
{
if (id != 0) glDeleteVertexArrays(1, &id);
TraceLog(INFO, "[VAO ID %i] Unloaded model data from VRAM (GPU)", id);
}
#endif
}
// Unload vertex data (VBO) from GPU memory
void rlDeleteBuffers(unsigned int id)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (id != 0)
{
glDeleteBuffers(1, &id);
if (!vaoSupported) TraceLog(INFO, "[VBO ID %i] Unloaded model vertex data from VRAM (GPU)", id);
}
#endif
}
// Clear color buffer with color
void rlClearColor(byte r, byte g, byte b, byte a)
{
// Color values clamp to 0.0f(0) and 1.0f(255)
float cr = (float)r/255;
float cg = (float)g/255;
float cb = (float)b/255;
float ca = (float)a/255;
glClearColor(cr, cg, cb, ca);
}
// Clear used screen buffers (color and depth)
void rlClearScreenBuffers(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D)
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used...
}
// Returns current OpenGL version
int rlGetVersion(void)
{
#if defined(GRAPHICS_API_OPENGL_11)
return OPENGL_11;
#elif defined(GRAPHICS_API_OPENGL_21)
return OPENGL_21;
#elif defined(GRAPHICS_API_OPENGL_33)
return OPENGL_33;
#elif defined(GRAPHICS_API_OPENGL_ES2)
return OPENGL_ES_20;
#endif
}
//----------------------------------------------------------------------------------
// Module Functions Definition - rlgl Functions
//----------------------------------------------------------------------------------
// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states
void rlglInit(int width, int height)
{
// Check OpenGL information and capabilities
//------------------------------------------------------------------------------
// Print current OpenGL and GLSL version
TraceLog(INFO, "GPU: Vendor: %s", glGetString(GL_VENDOR));
TraceLog(INFO, "GPU: Renderer: %s", glGetString(GL_RENDERER));
TraceLog(INFO, "GPU: Version: %s", glGetString(GL_VERSION));
TraceLog(INFO, "GPU: GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
// NOTE: We can get a bunch of extra information about GPU capabilities (glGet*)
//int maxTexSize;
//glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
//TraceLog(INFO, "GL_MAX_TEXTURE_SIZE: %i", maxTexSize);
//GL_MAX_TEXTURE_IMAGE_UNITS
//GL_MAX_VIEWPORT_DIMS
//int numAuxBuffers;
//glGetIntegerv(GL_AUX_BUFFERS, &numAuxBuffers);
//TraceLog(INFO, "GL_AUX_BUFFERS: %i", numAuxBuffers);
//GLint numComp = 0;
//GLint format[32] = { 0 };
//glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numComp);
//glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format);
//for (int i = 0; i < numComp; i++) TraceLog(INFO, "Supported compressed format: 0x%x", format[i]);
// NOTE: We don't need that much data on screen... right now...
#if defined(GRAPHICS_API_OPENGL_11)
//TraceLog(INFO, "OpenGL 1.1 (or driver default) profile initialized");
#endif
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Get supported extensions list
GLint numExt = 0;
#if defined(GRAPHICS_API_OPENGL_33)
// NOTE: On OpenGL 3.3 VAO and NPOT are supported by default
vaoSupported = true;
npotSupported = true;
// We get a list of available extensions and we check for some of them (compressed textures)
// NOTE: We don't need to check again supported extensions but we do (GLAD already dealt with that)
glGetIntegerv(GL_NUM_EXTENSIONS, &numExt);
const char *extList[numExt];
for (int i = 0; i < numExt; i++) extList[i] = (char *)glGetStringi(GL_EXTENSIONS, i);
#elif defined(GRAPHICS_API_OPENGL_ES2)
char *extensions = (char *)glGetString(GL_EXTENSIONS); // One big const string
// NOTE: We have to duplicate string because glGetString() returns a const value
// If not duplicated, it fails in some systems (Raspberry Pi)
// Equivalent to function: char *strdup(const char *str)
char *extensionsDup;
size_t len = strlen(extensions) + 1;
void *newstr = malloc(len);
if (newstr == NULL) extensionsDup = NULL;
extensionsDup = (char *)memcpy(newstr, extensions, len);
// NOTE: String could be splitted using strtok() function (string.h)
// NOTE: strtok() modifies the received string, it can not be const
char *extList[512]; // Allocate 512 strings pointers (2 KB)
extList[numExt] = strtok(extensionsDup, " ");
while (extList[numExt] != NULL)
{
numExt++;
extList[numExt] = strtok(NULL, " ");
}
free(extensionsDup); // Duplicated string must be deallocated
numExt -= 1;
#endif
TraceLog(INFO, "Number of supported extensions: %i", numExt);
// Show supported extensions
//for (int i = 0; i < numExt; i++) TraceLog(INFO, "Supported extension: %s", extList[i]);
// Check required extensions
for (int i = 0; i < numExt; i++)
{
#if defined(GRAPHICS_API_OPENGL_ES2)
// Check VAO support
// NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature
if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0)
{
vaoSupported = true;
// The extension is supported by our hardware and driver, try to get related functions pointers
// NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance...
glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES");
glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES");
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES");
//glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)eglGetProcAddress("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted
}
// Check NPOT textures support
// NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature
if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) npotSupported = true;
#endif
// DDS texture compression support
if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) ||
(strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) ||
(strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) texCompDXTSupported = true;
// ETC1 texture compression support
if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) ||
(strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) texCompETC1Supported = true;
// ETC2/EAC texture compression support
if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) texCompETC2Supported = true;
// PVR texture compression support
if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) texCompPVRTSupported = true;
// ASTC texture compression support
if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) texCompASTCSupported = true;
}
#if defined(GRAPHICS_API_OPENGL_ES2)
if (vaoSupported) TraceLog(INFO, "[EXTENSION] VAO extension detected, VAO functions initialized successfully");
else TraceLog(WARNING, "[EXTENSION] VAO extension not found, VAO usage not supported");
if (npotSupported) TraceLog(INFO, "[EXTENSION] NPOT textures extension detected, full NPOT textures supported");
else TraceLog(WARNING, "[EXTENSION] NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)");
#endif
if (texCompDXTSupported) TraceLog(INFO, "[EXTENSION] DXT compressed textures supported");
if (texCompETC1Supported) TraceLog(INFO, "[EXTENSION] ETC1 compressed textures supported");
if (texCompETC2Supported) TraceLog(INFO, "[EXTENSION] ETC2/EAC compressed textures supported");
if (texCompPVRTSupported) TraceLog(INFO, "[EXTENSION] PVRT compressed textures supported");
if (texCompASTCSupported) TraceLog(INFO, "[EXTENSION] ASTC compressed textures supported");
// Initialize buffers, default shaders and default textures
//----------------------------------------------------------
// Init default white texture
unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes)
whiteTexture = rlglLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1);
if (whiteTexture != 0) TraceLog(INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture);
else TraceLog(WARNING, "Base white texture could not be loaded");
// Init default Shader (customized for GL 3.3 and ES2)
defaultShader = LoadDefaultShader();
currentShader = defaultShader;
// Init default vertex arrays buffers (lines, triangles, quads)
LoadDefaultBuffers();
// Init temp vertex buffer, used when transformation required (translate, rotate, scale)
tempBuffer = (Vector3 *)malloc(sizeof(Vector3)*TEMP_VERTEX_BUFFER_SIZE);
for (int i = 0; i < TEMP_VERTEX_BUFFER_SIZE; i++) tempBuffer[i] = VectorZero();
// Init draw calls tracking system
draws = (DrawCall *)malloc(sizeof(DrawCall)*MAX_DRAWS_BY_TEXTURE);
for (int i = 0; i < MAX_DRAWS_BY_TEXTURE; i++)
{
draws[i].textureId = 0;
draws[i].vertexCount = 0;
}
drawsCounter = 1;
draws[drawsCounter - 1].textureId = whiteTexture;
currentDrawMode = RL_TRIANGLES; // Set default draw mode
// Init internal matrix stack (emulating OpenGL 1.1)
for (int i = 0; i < MATRIX_STACK_SIZE; i++) stack[i] = MatrixIdentity();
// Init internal projection and modelview matrices
projection = MatrixIdentity();
modelview = MatrixIdentity();
currentMatrix = &modelview;
#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Initialize OpenGL default states
//----------------------------------------------------------
// Init state: Depth test
glDepthFunc(GL_LEQUAL); // Type of depth testing to apply
glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D)
// Init state: Blending mode
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed)
glEnable(GL_BLEND); // Enable color blending (required to work with transparencies)
// Init state: Culling
// NOTE: All shapes/models triangles are drawn CCW
glCullFace(GL_BACK); // Cull the back face (default)
glFrontFace(GL_CCW); // Front face are defined counter clockwise (default)
glEnable(GL_CULL_FACE); // Enable backface culling
#if defined(GRAPHICS_API_OPENGL_11)
// Init state: Color hints (deprecated in OpenGL 3.0+)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation
glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation)
#endif
// Init state: Color/Depth buffers clear
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black)
glClearDepth(1.0f); // Set clear depth value (default)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D)
// Store screen size into global variables
screenWidth = width;
screenHeight = height;
TraceLog(INFO, "OpenGL default states initialized successfully");
}
// Vertex Buffer Object deinitialization (memory free)
void rlglClose(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
UnloadDefaultShader();
UnloadStandardShader();
UnloadDefaultBuffers();
// Delete default white texture
glDeleteTextures(1, &whiteTexture);
TraceLog(INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", whiteTexture);
// Unload lights
if (lightsCount > 0)
{
for (int i = 0; i < lightsCount; i++) free(lights[i]);
lightsCount = 0;
}
free(draws);
#endif
}
// Drawing batches: triangles, quads, lines
void rlglDraw(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// NOTE: In a future version, models could be stored in a stack...
//for (int i = 0; i < modelsCount; i++) rlglDrawMesh(models[i]->mesh, models[i]->material, models[i]->transform);
// NOTE: Default buffers upload and draw
UpdateDefaultBuffers();
if (vrEnabled && vrRendering) DrawDefaultBuffers(2);
else DrawDefaultBuffers(1);
#endif
}
// Load OpenGL extensions
// NOTE: External loader function could be passed as a pointer
void rlglLoadExtensions(void *loader)
{
#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_33)
// NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions)
if (!gladLoadGLLoader((GLADloadproc)loader)) TraceLog(WARNING, "GLAD: Cannot load OpenGL extensions");
else TraceLog(INFO, "GLAD: OpenGL extensions loaded successfully");
#if defined(GRAPHICS_API_OPENGL_21)
if (GLAD_GL_VERSION_2_1) TraceLog(INFO, "OpenGL 2.1 profile supported");
#elif defined(GRAPHICS_API_OPENGL_33)
if(GLAD_GL_VERSION_3_3) TraceLog(INFO, "OpenGL 3.3 Core profile supported");
else TraceLog(ERROR, "OpenGL 3.3 Core profile not supported");
#endif
// With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans
//if (GLAD_GL_ARB_vertex_array_object) // Use GL_ARB_vertex_array_object
#endif
}
// Get world coordinates from screen coordinates
Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view)
{
Vector3 result = { 0.0f, 0.0f, 0.0f };
// Calculate unproject matrix (multiply projection matrix and view matrix) and invert it
Matrix matProjView = MatrixMultiply(proj, view);
MatrixInvert(&matProjView);
// Create quaternion from source point
Quaternion quat = { source.x, source.y, source.z, 1.0f };
// Multiply quat point by unproject matrix
QuaternionTransform(&quat, matProjView);
// Normalized world points in vectors
result.x = quat.x/quat.w;
result.y = quat.y/quat.w;
result.z = quat.z/quat.w;
return result;
}
// Convert image data to OpenGL texture (returns OpenGL valid Id)
unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount)
{
glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding
GLuint id = 0;
// Check texture format support by OpenGL 1.1 (compressed textures not supported)
#if defined(GRAPHICS_API_OPENGL_11)
if (textureFormat >= 8)
{
TraceLog(WARNING, "OpenGL 1.1 does not support GPU compressed texture formats");
return id;
}
#endif
if ((!texCompDXTSupported) && ((textureFormat == COMPRESSED_DXT1_RGB) || (textureFormat == COMPRESSED_DXT1_RGBA) ||
(textureFormat == COMPRESSED_DXT3_RGBA) || (textureFormat == COMPRESSED_DXT5_RGBA)))
{
TraceLog(WARNING, "DXT compressed texture format not supported");
return id;
}
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if ((!texCompETC1Supported) && (textureFormat == COMPRESSED_ETC1_RGB))
{
TraceLog(WARNING, "ETC1 compressed texture format not supported");
return id;
}
if ((!texCompETC2Supported) && ((textureFormat == COMPRESSED_ETC2_RGB) || (textureFormat == COMPRESSED_ETC2_EAC_RGBA)))
{
TraceLog(WARNING, "ETC2 compressed texture format not supported");
return id;
}
if ((!texCompPVRTSupported) && ((textureFormat == COMPRESSED_PVRT_RGB) || (textureFormat == COMPRESSED_PVRT_RGBA)))
{
TraceLog(WARNING, "PVRT compressed texture format not supported");
return id;
}
if ((!texCompASTCSupported) && ((textureFormat == COMPRESSED_ASTC_4x4_RGBA) || (textureFormat == COMPRESSED_ASTC_8x8_RGBA)))
{
TraceLog(WARNING, "ASTC compressed texture format not supported");
return id;
}
#endif
glGenTextures(1, &id); // Generate Pointer to the texture
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
//glActiveTexture(GL_TEXTURE0); // If not defined, using GL_TEXTURE0 by default (shader texture)
#endif
glBindTexture(GL_TEXTURE_2D, id);
#if defined(GRAPHICS_API_OPENGL_33)
// NOTE: We define internal (GPU) format as GL_RGBA8 (probably BGRA8 in practice, driver takes care)
// NOTE: On embedded systems, we let the driver choose the best internal format
// Support for multiple color modes (16bit color modes and grayscale)
// (sized)internalFormat format type
// GL_R GL_RED GL_UNSIGNED_BYTE
// GL_RGB565 GL_RGB GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5
// GL_RGB5_A1 GL_RGBA GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_5_5_1
// GL_RGBA4 GL_RGBA GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_4_4_4_4
// GL_RGBA8 GL_RGBA GL_UNSIGNED_BYTE
// GL_RGB8 GL_RGB GL_UNSIGNED_BYTE
switch (textureFormat)
{
case UNCOMPRESSED_GRAYSCALE:
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, (unsigned char *)data);
// With swizzleMask we define how a one channel texture will be mapped to RGBA
// Required GL >= 3.3 or EXT_texture_swizzle/ARB_texture_swizzle
GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE };
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
TraceLog(INFO, "[TEX ID %i] Grayscale texture loaded and swizzled", id);
} break;
case UNCOMPRESSED_GRAY_ALPHA:
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width, height, 0, GL_RG, GL_UNSIGNED_BYTE, (unsigned char *)data);
GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN };
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
} break;
case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break;
case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_S3TC_DXT1_EXT); break;
case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); break;
case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); break;
case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); break;
case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_ETC1_RGB8_OES); break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3
case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB8_ETC2); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3
case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA8_ETC2_EAC); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3
case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU
case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU
case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3
case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_8x8_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3
default: TraceLog(WARNING, "Texture format not recognized"); break;
}
#elif defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_ES2)
// NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA
switch (textureFormat)
{
case UNCOMPRESSED_GRAYSCALE: glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_GRAY_ALPHA: glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break;
case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
#if defined(GRAPHICS_API_OPENGL_ES2)
case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_S3TC_DXT1_EXT); break;
case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); break;
case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); break; // NOTE: Not supported by WebGL
case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); break; // NOTE: Not supported by WebGL
case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_ETC1_RGB8_OES); break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3
case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB8_ETC2); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3
case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA8_ETC2_EAC); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3
case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU
case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU
case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3
case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_8x8_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3
#endif
default: TraceLog(WARNING, "Texture format not supported"); break;
}
#endif
// Texture parameters configuration
// NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used
#if defined(GRAPHICS_API_OPENGL_ES2)
// NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used
if (npotSupported)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis
}
else
{
// NOTE: If using negative texture coordinates (LoadOBJ()), it does not work!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis
}
#else
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis
#endif
// Magnification and minification filters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR
#if defined(GRAPHICS_API_OPENGL_33)
if (mipmapCount > 1)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps (must be available)
}
#endif
// At this point we have the texture loaded in GPU and texture parameters configured
// NOTE: If mipmaps were not in data, they are not generated automatically
// Unbind current texture
glBindTexture(GL_TEXTURE_2D, 0);
if (id > 0) TraceLog(INFO, "[TEX ID %i] Texture created successfully (%ix%i)", id, width, height);
else TraceLog(WARNING, "Texture could not be created");
return id;
}
// Load a texture to be used for rendering (fbo with color and depth attachments)
RenderTexture2D rlglLoadRenderTexture(int width, int height)
{
RenderTexture2D target;
target.id = 0;
target.texture.id = 0;
target.texture.width = width;
target.texture.height = height;
target.texture.format = UNCOMPRESSED_R8G8B8;
target.texture.mipmaps = 1;
target.depth.id = 0;
target.depth.width = width;
target.depth.height = height;
target.depth.format = 19; //DEPTH_COMPONENT_24BIT
target.depth.mipmaps = 1;
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Create the texture that will serve as the color attachment for the framebuffer
glGenTextures(1, &target.texture.id);
glBindTexture(GL_TEXTURE_2D, target.texture.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
#if defined(GRAPHICS_API_OPENGL_33)
#define USE_DEPTH_TEXTURE
#else
#define USE_DEPTH_RENDERBUFFER
#endif
#if defined(USE_DEPTH_RENDERBUFFER)
// Create the renderbuffer that will serve as the depth attachment for the framebuffer.
glGenRenderbuffers(1, &target.depth.id);
glBindRenderbuffer(GL_RENDERBUFFER, target.depth.id);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); // GL_DEPTH_COMPONENT24 not supported on Android
#elif defined(USE_DEPTH_TEXTURE)
// NOTE: We can also use a texture for depth buffer (GL_ARB_depth_texture/GL_OES_depth_texture extension required)
// A renderbuffer is simpler than a texture and could offer better performance on embedded devices
glGenTextures(1, &target.depth.id);
glBindTexture(GL_TEXTURE_2D, target.depth.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
// Create the framebuffer object
glGenFramebuffers(1, &target.id);
glBindFramebuffer(GL_FRAMEBUFFER, target.id);
// Attach color texture and depth renderbuffer to FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture.id, 0);
#if defined(USE_DEPTH_RENDERBUFFER)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, target.depth.id);
#elif defined(USE_DEPTH_TEXTURE)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target.depth.id, 0);
#endif
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
TraceLog(WARNING, "Framebuffer object could not be created...");
switch (status)
{
case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(WARNING, "Framebuffer is unsupported"); break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(WARNING, "Framebuffer incomplete attachment"); break;
#if defined(GRAPHICS_API_OPENGL_ES2)
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(WARNING, "Framebuffer incomplete dimensions"); break;
#endif
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(WARNING, "Framebuffer incomplete missing attachment"); break;
default: break;
}
glDeleteTextures(1, &target.texture.id);
glDeleteTextures(1, &target.depth.id);
glDeleteFramebuffers(1, &target.id);
}
else TraceLog(INFO, "[FBO ID %i] Framebuffer object created successfully", target.id);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#endif
return target;
}
// Update already loaded texture in GPU with new data
void rlglUpdateTexture(unsigned int id, int width, int height, int format, void *data)
{
glBindTexture(GL_TEXTURE_2D, id);
#if defined(GRAPHICS_API_OPENGL_33)
switch (format)
{
case UNCOMPRESSED_GRAYSCALE: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_GRAY_ALPHA: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RG, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G6B5: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G5B5A1: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break;
case UNCOMPRESSED_R4G4B4A4: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8A8: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
default: TraceLog(WARNING, "Texture format updating not supported"); break;
}
#elif defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_ES2)
// NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA
switch (format)
{
case UNCOMPRESSED_GRAYSCALE: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_GRAY_ALPHA: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G6B5: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
case UNCOMPRESSED_R5G5B5A1: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break;
case UNCOMPRESSED_R4G4B4A4: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break;
case UNCOMPRESSED_R8G8B8A8: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break;
default: TraceLog(WARNING, "Texture format updating not supported"); break;
}
#endif
}
// Generate mipmap data for selected texture
void rlglGenerateMipmaps(Texture2D texture)
{
glBindTexture(GL_TEXTURE_2D, texture.id);
// Check if texture is power-of-two (POT)
bool texIsPOT = false;
if (((texture.width > 0) && ((texture.width & (texture.width - 1)) == 0)) &&
((texture.height > 0) && ((texture.height & (texture.height - 1)) == 0))) texIsPOT = true;
if ((texIsPOT) || (npotSupported))
{
#if defined(GRAPHICS_API_OPENGL_11)
// Compute required mipmaps
void *data = rlglReadTexturePixels(texture);
// NOTE: data size is reallocated to fit mipmaps data
// NOTE: CPU mipmap generation only supports RGBA 32bit data
int mipmapCount = GenerateMipmaps(data, texture.width, texture.height);
int size = texture.width*texture.height*4; // RGBA 32bit only
int offset = size;
int mipWidth = texture.width/2;
int mipHeight = texture.height/2;
// Load the mipmaps
for (int level = 1; level < mipmapCount; level++)
{
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + offset);
size = mipWidth*mipHeight*4;
offset += size;
mipWidth /= 2;
mipHeight /= 2;
}
TraceLog(WARNING, "[TEX ID %i] Mipmaps generated manually on CPU side", texture.id);
// NOTE: Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data
free(data);
#endif
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically
TraceLog(INFO, "[TEX ID %i] Mipmaps generated automatically", texture.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps (must be available)
#endif
}
else TraceLog(WARNING, "[TEX ID %i] Mipmaps can not be generated", texture.id);
glBindTexture(GL_TEXTURE_2D, 0);
}
// Upload vertex data into a VAO (if supported) and VBO
void rlglLoadMesh(Mesh *mesh, bool dynamic)
{
mesh->vaoId = 0; // Vertex Array Object
mesh->vboId[0] = 0; // Vertex positions VBO
mesh->vboId[1] = 0; // Vertex texcoords VBO
mesh->vboId[2] = 0; // Vertex normals VBO
mesh->vboId[3] = 0; // Vertex colors VBO
mesh->vboId[4] = 0; // Vertex tangents VBO
mesh->vboId[5] = 0; // Vertex texcoords2 VBO
mesh->vboId[6] = 0; // Vertex indices VBO
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
int drawHint = GL_STATIC_DRAW;
if (dynamic) drawHint = GL_DYNAMIC_DRAW;
GLuint vaoId = 0; // Vertex Array Objects (VAO)
GLuint vboId[7] = { 0 }; // Vertex Buffer Objects (VBOs)
if (vaoSupported)
{
// Initialize Quads VAO (Buffer A)
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
}
// NOTE: Attributes must be uploaded considering default locations points
// Enable vertex attributes: position (shader-location = 0)
glGenBuffers(1, &vboId[0]);
glBindBuffer(GL_ARRAY_BUFFER, vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->vertices, drawHint);
glVertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(0);
// Enable vertex attributes: texcoords (shader-location = 1)
glGenBuffers(1, &vboId[1]);
glBindBuffer(GL_ARRAY_BUFFER, vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords, drawHint);
glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(1);
// Enable vertex attributes: normals (shader-location = 2)
if (mesh->normals != NULL)
{
glGenBuffers(1, &vboId[2]);
glBindBuffer(GL_ARRAY_BUFFER, vboId[2]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->normals, drawHint);
glVertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(2);
}
else
{
// Default color vertex attribute set to WHITE
glVertexAttrib3f(2, 1.0f, 1.0f, 1.0f);
glDisableVertexAttribArray(2);
}
// Default color vertex attribute (shader-location = 3)
if (mesh->colors != NULL)
{
glGenBuffers(1, &vboId[3]);
glBindBuffer(GL_ARRAY_BUFFER, vboId[3]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*mesh->vertexCount, mesh->colors, drawHint);
glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
glEnableVertexAttribArray(3);
}
else
{
// Default color vertex attribute set to WHITE
glVertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f);
glDisableVertexAttribArray(3);
}
// Default tangent vertex attribute (shader-location = 4)
if (mesh->tangents != NULL)
{
glGenBuffers(1, &vboId[4]);
glBindBuffer(GL_ARRAY_BUFFER, vboId[4]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh->vertexCount, mesh->tangents, drawHint);
glVertexAttribPointer(4, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(4);
}
else
{
// Default tangents vertex attribute
glVertexAttrib3f(4, 0.0f, 0.0f, 0.0f);
glDisableVertexAttribArray(4);
}
// Default texcoord2 vertex attribute (shader-location = 5)
if (mesh->texcoords2 != NULL)
{
glGenBuffers(1, &vboId[5]);
glBindBuffer(GL_ARRAY_BUFFER, vboId[5]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh->vertexCount, mesh->texcoords2, drawHint);
glVertexAttribPointer(5, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(5);
}
else
{
// Default tangents vertex attribute
glVertexAttrib2f(5, 0.0f, 0.0f);
glDisableVertexAttribArray(5);
}
if (mesh->indices != NULL)
{
glGenBuffers(1, &vboId[6]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId[6]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangleCount*3, mesh->indices, GL_STATIC_DRAW);
}
mesh->vboId[0] = vboId[0]; // Vertex position VBO
mesh->vboId[1] = vboId[1]; // Texcoords VBO
mesh->vboId[2] = vboId[2]; // Normals VBO
mesh->vboId[3] = vboId[3]; // Colors VBO
mesh->vboId[4] = vboId[4]; // Tangents VBO
mesh->vboId[5] = vboId[5]; // Texcoords2 VBO
mesh->vboId[6] = vboId[6]; // Indices VBO
if (vaoSupported)
{
if (vaoId > 0)
{
mesh->vaoId = vaoId;
TraceLog(INFO, "[VAO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId);
}
else TraceLog(WARNING, "Mesh could not be uploaded to VRAM (GPU)");
}
else
{
TraceLog(INFO, "[VBOs] Mesh uploaded successfully to VRAM (GPU)");
}
#endif
}
// Update vertex data on GPU (upload new data to one buffer)
void rlglUpdateMesh(Mesh mesh, int buffer, int numVertex)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Activate mesh VAO
if (vaoSupported) glBindVertexArray(mesh.vaoId);
switch (buffer)
{
case 0: // Update vertices (vertex position)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.vertices, GL_DYNAMIC_DRAW);
else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.vertices);
} break;
case 1: // Update texcoords (vertex texture coordinates)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]);
if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*numVertex, mesh.texcoords, GL_DYNAMIC_DRAW);
else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*numVertex, mesh.texcoords);
} break;
case 2: // Update normals (vertex normals)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.normals, GL_DYNAMIC_DRAW);
else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.normals);
} break;
case 3: // Update colors (vertex colors)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]);
if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*numVertex, mesh.colors, GL_DYNAMIC_DRAW);
else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*numVertex, mesh.colors);
} break;
case 4: // Update tangents (vertex tangents)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*numVertex, mesh.tangents, GL_DYNAMIC_DRAW);
else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*numVertex, mesh.tangents);
} break;
case 5: // Update texcoords2 (vertex second texture coordinates)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]);
if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*numVertex, mesh.texcoords2, GL_DYNAMIC_DRAW);
else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*numVertex, mesh.texcoords2);
} break;
default: break;
}
// Unbind the current VAO
if (vaoSupported) glBindVertexArray(0);
// Another option would be using buffer mapping...
//mesh.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
// Now we can modify vertices
//glUnmapBuffer(GL_ARRAY_BUFFER);
#endif
}
// Draw a 3d mesh with material and transform
void rlglDrawMesh(Mesh mesh, Material material, Matrix transform)
{
#if defined(GRAPHICS_API_OPENGL_11)
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id);
// NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model
glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array
glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array
if (mesh.normals != NULL) glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array
if (mesh.colors != NULL) glEnableClientState(GL_COLOR_ARRAY); // Enable colors array
glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array
glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array
if (mesh.normals != NULL) glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array
if (mesh.colors != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array
rlPushMatrix();
rlMultMatrixf(MatrixToFloat(transform));
rlColor4ub(material.colDiffuse.r, material.colDiffuse.g, material.colDiffuse.b, material.colDiffuse.a);
if (mesh.indices != NULL) glDrawElements(GL_TRIANGLES, mesh.triangleCount*3, GL_UNSIGNED_SHORT, mesh.indices);
else glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount);
rlPopMatrix();
glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array
glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array
if (mesh.normals != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array
if (mesh.colors != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable colors array
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
int eyesCount = 1;
if (vrEnabled) eyesCount = 2;
glUseProgram(material.shader.id);
// Upload to shader material.colDiffuse
float vColorDiffuse[4] = { (float)material.colDiffuse.r/255, (float)material.colDiffuse.g/255, (float)material.colDiffuse.b/255, (float)material.colDiffuse.a/255 };
glUniform4fv(material.shader.tintColorLoc, 1, vColorDiffuse);
// At this point the modelview matrix just contains the view matrix (camera)
// That's because Begin3dMode() sets it an no model-drawing function modifies it, all use rlPushMatrix() and rlPopMatrix()
Matrix matView = modelview; // View matrix (camera)
Matrix matProjection = projection; // Projection matrix (perspective)
// Calculate model-view matrix combining matModel and matView
Matrix matModelView = MatrixMultiply(transform, matView); // Transform to camera-space coordinates
// Check if using standard shader to get location points
// NOTE: standard shader specific locations are got at render time to keep Shader struct as simple as possible (with just default shader locations)
if (material.shader.id == standardShader.id)
{
// Transpose and inverse model transformations matrix for fragment normal calculations
Matrix transInvTransform = transform;
MatrixTranspose(&transInvTransform);
MatrixInvert(&transInvTransform);
// Send model transformations matrix to shader
glUniformMatrix4fv(glGetUniformLocation(material.shader.id, "modelMatrix"), 1, false, MatrixToFloat(transInvTransform));
// Send view transformation matrix to shader. View matrix 8, 9 and 10 are view direction vector axis values (target - position)
glUniform3f(glGetUniformLocation(material.shader.id, "viewDir"), matView.m8, matView.m9, matView.m10);
// Setup shader uniforms for lights
SetShaderLights(material.shader);
// Upload to shader material.colAmbient
glUniform4f(glGetUniformLocation(material.shader.id, "colAmbient"), (float)material.colAmbient.r/255, (float)material.colAmbient.g/255, (float)material.colAmbient.b/255, (float)material.colAmbient.a/255);
// Upload to shader material.colSpecular
glUniform4f(glGetUniformLocation(material.shader.id, "colSpecular"), (float)material.colSpecular.r/255, (float)material.colSpecular.g/255, (float)material.colSpecular.b/255, (float)material.colSpecular.a/255);
// Upload to shader glossiness
glUniform1f(glGetUniformLocation(material.shader.id, "glossiness"), material.glossiness);
}
// Set shader textures (diffuse, normal, specular)
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, material.texDiffuse.id);
glUniform1i(material.shader.mapTexture0Loc, 0); // Diffuse texture fits in active texture unit 0
if ((material.texNormal.id != 0) && (material.shader.mapTexture1Loc != -1))
{
// Upload to shader specular map flag
glUniform1i(glGetUniformLocation(material.shader.id, "useNormal"), 1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, material.texNormal.id);
glUniform1i(material.shader.mapTexture1Loc, 1); // Normal texture fits in active texture unit 1
}
if ((material.texSpecular.id != 0) && (material.shader.mapTexture2Loc != -1))
{
// Upload to shader specular map flag
glUniform1i(glGetUniformLocation(material.shader.id, "useSpecular"), 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, material.texSpecular.id);
glUniform1i(material.shader.mapTexture2Loc, 2); // Specular texture fits in active texture unit 2
}
if (vaoSupported)
{
glBindVertexArray(mesh.vaoId);
}
else
{
// Bind mesh VBO data: vertex position (shader-location = 0)
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]);
glVertexAttribPointer(material.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(material.shader.vertexLoc);
// Bind mesh VBO data: vertex texcoords (shader-location = 1)
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]);
glVertexAttribPointer(material.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(material.shader.texcoordLoc);
// Bind mesh VBO data: vertex normals (shader-location = 2, if available)
if (material.shader.normalLoc != -1)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]);
glVertexAttribPointer(material.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(material.shader.normalLoc);
}
// Bind mesh VBO data: vertex colors (shader-location = 3, if available)
if (material.shader.colorLoc != -1)
{
if (mesh.vboId[3] != 0)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]);
glVertexAttribPointer(material.shader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
glEnableVertexAttribArray(material.shader.colorLoc);
}
else
{
// Set default value for unused attribute
// NOTE: Required when using default shader and no VAO support
glVertexAttrib4f(material.shader.colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
glDisableVertexAttribArray(material.shader.colorLoc);
}
}
// Bind mesh VBO data: vertex tangents (shader-location = 4, if available)
if (material.shader.tangentLoc != -1)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]);
glVertexAttribPointer(material.shader.tangentLoc, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(material.shader.tangentLoc);
}
// Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available)
if (material.shader.texcoord2Loc != -1)
{
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]);
glVertexAttribPointer(material.shader.texcoord2Loc, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(material.shader.texcoord2Loc);
}
if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
}
for (int eye = 0; eye < eyesCount; eye++)
{
if (eyesCount == 2) SetStereoView(eye, matProjection, matModelView);
else modelview = matModelView;
// Calculate model-view-projection matrix (MVP)
Matrix matMVP = MatrixMultiply(modelview, projection); // Transform to screen-space coordinates
// Send combined model-view-projection matrix to shader
glUniformMatrix4fv(material.shader.mvpLoc, 1, false, MatrixToFloat(matMVP));
// Draw call!
if (mesh.indices != NULL) glDrawElements(GL_TRIANGLES, mesh.triangleCount*3, GL_UNSIGNED_SHORT, 0); // Indexed vertices draw
else glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount);
}
if (material.texNormal.id != 0)
{
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
}
if (material.texSpecular.id != 0)
{
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, 0);
}
glActiveTexture(GL_TEXTURE0); // Set shader active texture to default 0
glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures
if (vaoSupported) glBindVertexArray(0); // Unbind VAO
else
{
glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind VBOs
if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
glUseProgram(0); // Unbind shader program
// Restore projection/modelview matrices
projection = matProjection;
modelview = matView;
#endif
}
// Unload mesh data from CPU and GPU
void rlglUnloadMesh(Mesh *mesh)
{
if (mesh->vertices != NULL) free(mesh->vertices);
if (mesh->texcoords != NULL) free(mesh->texcoords);
if (mesh->normals != NULL) free(mesh->normals);
if (mesh->colors != NULL) free(mesh->colors);
if (mesh->tangents != NULL) free(mesh->tangents);
if (mesh->texcoords2 != NULL) free(mesh->texcoords2);
if (mesh->indices != NULL) free(mesh->indices);
rlDeleteBuffers(mesh->vboId[0]); // vertex
rlDeleteBuffers(mesh->vboId[1]); // texcoords
rlDeleteBuffers(mesh->vboId[2]); // normals
rlDeleteBuffers(mesh->vboId[3]); // colors
rlDeleteBuffers(mesh->vboId[4]); // tangents
rlDeleteBuffers(mesh->vboId[5]); // texcoords2
rlDeleteBuffers(mesh->vboId[6]); // indices
rlDeleteVertexArrays(mesh->vaoId);
}
// Read screen pixel data (color buffer)
unsigned char *rlglReadScreenPixels(int width, int height)
{
unsigned char *screenData = (unsigned char *)malloc(width*height*sizeof(unsigned char)*4);
// NOTE: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData);
// Flip image vertically!
unsigned char *imgData = (unsigned char *)malloc(width*height*sizeof(unsigned char)*4);
for (int y = height - 1; y >= 0; y--)
{
for (int x = 0; x < (width*4); x++)
{
// Flip line
imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x];
// Set alpha component value to 255 (no trasparent image retrieval)
// NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it!
if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255;
}
}
free(screenData);
return imgData; // NOTE: image data should be freed
}
// Read texture pixel data
// NOTE: glGetTexImage() is not available on OpenGL ES 2.0
// Texture2D width and height are required on OpenGL ES 2.0. There is no way to get it from texture id.
void *rlglReadTexturePixels(Texture2D texture)
{
void *pixels = NULL;
#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33)
glBindTexture(GL_TEXTURE_2D, texture.id);
// NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0)
/*
int width, height, format;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
// Other texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE
*/
int glFormat = 0, glType = 0;
unsigned int size = texture.width*texture.height;
// NOTE: GL_LUMINANCE and GL_LUMINANCE_ALPHA are removed since OpenGL 3.1
// Must be replaced by GL_RED and GL_RG on Core OpenGL 3.3
switch (texture.format)
{
#if defined(GRAPHICS_API_OPENGL_11)
case UNCOMPRESSED_GRAYSCALE: pixels = (unsigned char *)malloc(size); glFormat = GL_LUMINANCE; glType = GL_UNSIGNED_BYTE; break; // 8 bit per pixel (no alpha)
case UNCOMPRESSED_GRAY_ALPHA: pixels = (unsigned char *)malloc(size*2); glFormat = GL_LUMINANCE_ALPHA; glType = GL_UNSIGNED_BYTE; break; // 16 bpp (2 channels)
#elif defined(GRAPHICS_API_OPENGL_33)
case UNCOMPRESSED_GRAYSCALE: pixels = (unsigned char *)malloc(size); glFormat = GL_RED; glType = GL_UNSIGNED_BYTE; break;
case UNCOMPRESSED_GRAY_ALPHA: pixels = (unsigned char *)malloc(size*2); glFormat = GL_RG; glType = GL_UNSIGNED_BYTE; break;
#endif
case UNCOMPRESSED_R5G6B5: pixels = (unsigned short *)malloc(size); glFormat = GL_RGB; glType = GL_UNSIGNED_SHORT_5_6_5; break; // 16 bpp
case UNCOMPRESSED_R8G8B8: pixels = (unsigned char *)malloc(size*3); glFormat = GL_RGB; glType = GL_UNSIGNED_BYTE; break; // 24 bpp
case UNCOMPRESSED_R5G5B5A1: pixels = (unsigned short *)malloc(size); glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_5_5_5_1; break; // 16 bpp (1 bit alpha)
case UNCOMPRESSED_R4G4B4A4: pixels = (unsigned short *)malloc(size); glFormat = GL_RGBA; glType = GL_UNSIGNED_SHORT_4_4_4_4; break; // 16 bpp (4 bit alpha)
case UNCOMPRESSED_R8G8B8A8: pixels = (unsigned char *)malloc(size*4); glFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE; break; // 32 bpp
default: TraceLog(WARNING, "Texture data retrieval, format not suported"); break;
}
// NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding.
// Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting.
// GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.)
// GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.)
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
#if defined(GRAPHICS_API_OPENGL_ES2)
RenderTexture2D fbo = rlglLoadRenderTexture(texture.width, texture.height);
// NOTE: Two possible Options:
// 1 - Bind texture to color fbo attachment and glReadPixels()
// 2 - Create an fbo, activate it, render quad with texture, glReadPixels()
#define GET_TEXTURE_FBO_OPTION_1 // It works
#if defined(GET_TEXTURE_FBO_OPTION_1)
glBindFramebuffer(GL_FRAMEBUFFER, fbo.id);
glBindTexture(GL_TEXTURE_2D, 0);
// Attach our texture to FBO -> Texture must be RGB
// NOTE: Previoust attached texture is automatically detached
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0);
pixels = (unsigned char *)malloc(texture.width*texture.height*4*sizeof(unsigned char));
// NOTE: Despite FBO color texture is RGB, we read data as RGBA... reading as RGB doesn't work... o__O
glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
// Re-attach internal FBO color texture before deleting it
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
#elif defined(GET_TEXTURE_FBO_OPTION_2)
// Render texture to fbo
glBindFramebuffer(GL_FRAMEBUFFER, fbo.id);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepthf(1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, width, height);
//glMatrixMode(GL_PROJECTION);
//glLoadIdentity();
rlOrtho(0.0, width, height, 0.0, 0.0, 1.0);
//glMatrixMode(GL_MODELVIEW);
//glLoadIdentity();
//glDisable(GL_TEXTURE_2D);
//glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
Model quad;
//quad.mesh = GenMeshQuad(width, height);
quad.transform = MatrixIdentity();
quad.shader = defaultShader;
DrawModel(quad, (Vector3){ 0.0f, 0.0f, 0.0f }, 1.0f, WHITE);
pixels = (unsigned char *)malloc(texture.width*texture.height*3*sizeof(unsigned char));
glReadPixels(0, 0, texture.width, texture.height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
// Bind framebuffer 0, which means render to back buffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
UnloadModel(quad);
#endif // GET_TEXTURE_FBO_OPTION
// Clean up temporal fbo
rlDeleteRenderTextures(fbo);
#endif
return pixels;
}
/*
// TODO: Record draw calls to be processed in batch
// NOTE: Global state must be kept
void rlglRecordDraw(void)
{
// TODO: Before adding a new draw, check if anything changed from last stored draw
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
draws[drawsCounter].vaoId = currentState.vaoId; // lines.id, trangles.id, quads.id?
draws[drawsCounter].textureId = currentState.textureId; // whiteTexture?
draws[drawsCounter].shaderId = currentState.shaderId; // defaultShader.id
draws[drawsCounter].projection = projection;
draws[drawsCounter].modelview = modelview;
draws[drawsCounter].vertexCount = currentState.vertexCount;
drawsCounter++;
#endif
}
*/
//----------------------------------------------------------------------------------
// Module Functions Definition - Shaders Functions
// NOTE: Those functions are exposed directly to the user in raylib.h
//----------------------------------------------------------------------------------
// Get default internal texture (white texture)
Texture2D GetDefaultTexture(void)
{
Texture2D texture;
texture.id = whiteTexture;
texture.width = 1;
texture.height = 1;
texture.mipmaps = 1;
texture.format = UNCOMPRESSED_R8G8B8A8;
return texture;
}
// Load a custom shader and bind default locations
Shader LoadShader(char *vsFileName, char *fsFileName)
{
Shader shader = { 0 };
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Shaders loading from external text file
char *vShaderStr = ReadTextFile(vsFileName);
char *fShaderStr = ReadTextFile(fsFileName);
if ((vShaderStr != NULL) && (fShaderStr != NULL))
{
shader.id = LoadShaderProgram(vShaderStr, fShaderStr);
// After shader loading, we try to load default location names
if (shader.id != 0) LoadDefaultShaderLocations(&shader);
// Shader strings must be freed
free(vShaderStr);
free(fShaderStr);
}
if (shader.id == 0)
{
TraceLog(WARNING, "Custom shader could not be loaded");
shader = defaultShader;
}
#endif
return shader;
}
// Unload a custom shader from memory
void UnloadShader(Shader shader)
{
if (shader.id != 0)
{
rlDeleteShader(shader.id);
TraceLog(INFO, "[SHDR ID %i] Unloaded shader program data", shader.id);
}
}
// Begin custom shader mode
void BeginShaderMode(Shader shader)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (currentShader.id != shader.id)
{
rlglDraw();
currentShader = shader;
}
#endif
}
// End custom shader mode (returns to default shader)
void EndShaderMode(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
BeginShaderMode(defaultShader);
#endif
}
// Get default shader
Shader GetDefaultShader(void)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
return defaultShader;
#else
Shader shader = { 0 };
return shader;
#endif
}
// Get default shader
// NOTE: Inits global variable standardShader
Shader GetStandardShader(void)
{
Shader shader = { 0 };
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (standardShaderLoaded) shader = standardShader;
else
{
// Lazy initialization of standard shader
standardShader = LoadStandardShader();
shader = standardShader;
}
#endif
return shader;
}
// Get shader uniform location
int GetShaderLocation(Shader shader, const char *uniformName)
{
int location = -1;
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
location = glGetUniformLocation(shader.id, uniformName);
if (location == -1) TraceLog(DEBUG, "[SHDR ID %i] Shader location for %s could not be found", shader.id, uniformName);
#endif
return location;
}
// Set shader uniform value (float)
void SetShaderValue(Shader shader, int uniformLoc, float *value, int size)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glUseProgram(shader.id);
if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float
else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2
else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3
else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4
else TraceLog(WARNING, "Shader value float array size not supported");
glUseProgram(0);
#endif
}
// Set shader uniform value (int)
void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glUseProgram(shader.id);
if (size == 1) glUniform1iv(uniformLoc, 1, value); // Shader uniform type: int
else if (size == 2) glUniform2iv(uniformLoc, 1, value); // Shader uniform type: ivec2
else if (size == 3) glUniform3iv(uniformLoc, 1, value); // Shader uniform type: ivec3
else if (size == 4) glUniform4iv(uniformLoc, 1, value); // Shader uniform type: ivec4
else TraceLog(WARNING, "Shader value int array size not supported");
glUseProgram(0);
#endif
}
// Set shader uniform value (matrix 4x4)
void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
glUseProgram(shader.id);
glUniformMatrix4fv(uniformLoc, 1, false, MatrixToFloat(mat));
glUseProgram(0);
#endif
}
// Set a custom projection matrix (replaces internal projection matrix)
void SetMatrixProjection(Matrix proj)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
projection = proj;
#endif
}
// Set a custom modelview matrix (replaces internal modelview matrix)
void SetMatrixModelview(Matrix view)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
modelview = view;
#endif
}
// Begin blending mode (alpha, additive, multiplied)
// NOTE: Only 3 blending modes supported, default blend mode is alpha
void BeginBlendMode(int mode)
{
if ((blendMode != mode) && (mode < 3))
{
rlglDraw();
switch (mode)
{
case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break;
case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; // Alternative: glBlendFunc(GL_ONE, GL_ONE);
case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break;
default: break;
}
blendMode = mode;
}
}
// End blending mode (reset to default: alpha blending)
void EndBlendMode(void)
{
BeginBlendMode(BLEND_ALPHA);
}
// Create a new light, initialize it and add to pool
Light CreateLight(int type, Vector3 position, Color diffuse)
{
Light light = NULL;
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (lightsCount < MAX_LIGHTS)
{
// Allocate dynamic memory
light = (Light)malloc(sizeof(LightData));
// Initialize light values with generic values
light->id = lightsCount;
light->type = type;
light->enabled = true;
light->position = position;
light->target = (Vector3){ 0.0f, 0.0f, 0.0f };
light->intensity = 1.0f;
light->diffuse = diffuse;
// Add new light to the array
lights[lightsCount] = light;
// Increase enabled lights count
lightsCount++;
}
else TraceLog(WARNING, "Too many lights, only supported up to %i lights", MAX_LIGHTS);
#else
// TODO: Support OpenGL 1.1 lighting system
TraceLog(WARNING, "Lighting currently not supported on OpenGL 1.1");
#endif
return light;
}
// Destroy a light and take it out of the list
void DestroyLight(Light light)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
if (light != NULL)
{
// Free dynamic memory allocation
free(lights[light->id]);
// Remove *obj from the pointers array
for (int i = light->id; i < lightsCount; i++)
{
// Resort all the following pointers of the array
if ((i + 1) < lightsCount)
{
lights[i] = lights[i + 1];
lights[i]->id = lights[i + 1]->id;
}
else free(lights[i]);
}
// Decrease enabled physic objects count
lightsCount--;
}
#endif
}
// Init VR device (or simulator)
// NOTE: If device is not available, it fallbacks to default device (simulator)
// NOTE: It modifies the global variable: VrDeviceInfo hmd
void InitVrDevice(int hmdDevice)
{
switch (hmdDevice)
{
case HMD_DEFAULT_DEVICE: TraceLog(INFO, "Initializing default VR Device (Oculus Rift CV1)");
case HMD_OCULUS_RIFT_DK2:
case HMD_OCULUS_RIFT_CV1:
{
#if defined(RLGL_OCULUS_SUPPORT)
vrDeviceReady = InitOculusDevice();
#else
TraceLog(WARNING, "Oculus Rift not supported by default, recompile raylib with Oculus support");
#endif
} break;
case HMD_VALVE_HTC_VIVE:
case HMD_SAMSUNG_GEAR_VR:
case HMD_GOOGLE_CARDBOARD:
case HMD_SONY_PLAYSTATION_VR:
case HMD_RAZER_OSVR:
case HMD_FOVE_VR: TraceLog(WARNING, "VR Device not supported");
default: break;
}
if (!vrDeviceReady)
{
TraceLog(WARNING, "VR Device not found: Initializing VR Simulator (Oculus Rift CV1)");
if (hmdDevice == HMD_OCULUS_RIFT_DK2)
{
// Oculus Rift DK2 parameters
hmd.hResolution = 1280; // HMD horizontal resolution in pixels
hmd.vResolution = 800; // HMD vertical resolution in pixels
hmd.hScreenSize = 0.14976f; // HMD horizontal size in meters
hmd.vScreenSize = 0.09356f; // HMD vertical size in meters
hmd.vScreenCenter = 0.04678f; // HMD screen center in meters
hmd.eyeToScreenDistance = 0.041f; // HMD distance between eye and display in meters
hmd.lensSeparationDistance = 0.0635f; // HMD lens separation distance in meters
hmd.interpupillaryDistance = 0.064f; // HMD IPD (distance between pupils) in meters
hmd.distortionK[0] = 1.0f; // HMD lens distortion constant parameter 0
hmd.distortionK[1] = 0.22f; // HMD lens distortion constant parameter 1
hmd.distortionK[2] = 0.24f; // HMD lens distortion constant parameter 2
hmd.distortionK[3] = 0.0f; // HMD lens distortion constant parameter 3
hmd.chromaAbCorrection[0] = 0.996f; // HMD chromatic aberration correction parameter 0
hmd.chromaAbCorrection[1] = -0.004f; // HMD chromatic aberration correction parameter 1
hmd.chromaAbCorrection[2] = 1.014f; // HMD chromatic aberration correction parameter 2
hmd.chromaAbCorrection[3] = 0.0f; // HMD chromatic aberration correction parameter 3
}
else if ((hmdDevice == HMD_DEFAULT_DEVICE) || (hmdDevice == HMD_OCULUS_RIFT_CV1))
{
// Oculus Rift CV1 parameters
// NOTE: CV1 represents a complete HMD redesign compared to previous versions,
// new Fresnel-hybrid-asymmetric lenses have been added and, consequently,
// previous parameters (DK2) and distortion shader (DK2) doesn't work any more.
// I just defined a set of parameters for simulator that approximate to CV1 stereo rendering
// but result is not the same obtained with Oculus PC SDK.
hmd.hResolution = 2160; // HMD horizontal resolution in pixels
hmd.vResolution = 1200; // HMD vertical resolution in pixels
hmd.hScreenSize = 0.133793f; // HMD horizontal size in meters
hmd.vScreenSize = 0.0669; // HMD vertical size in meters
hmd.vScreenCenter = 0.04678f; // HMD screen center in meters
hmd.eyeToScreenDistance = 0.041f; // HMD distance between eye and display in meters
hmd.lensSeparationDistance = 0.07f; // HMD lens separation distance in meters
hmd.interpupillaryDistance = 0.07f; // HMD IPD (distance between pupils) in meters
hmd.distortionK[0] = 1.0f; // HMD lens distortion constant parameter 0
hmd.distortionK[1] = 0.22f; // HMD lens distortion constant parameter 1
hmd.distortionK[2] = 0.24f; // HMD lens distortion constant parameter 2
hmd.distortionK[3] = 0.0f; // HMD lens distortion constant parameter 3
hmd.chromaAbCorrection[0] = 0.996f; // HMD chromatic aberration correction parameter 0
hmd.chromaAbCorrection[1] = -0.004f; // HMD chromatic aberration correction parameter 1
hmd.chromaAbCorrection[2] = 1.014f; // HMD chromatic aberration correction parameter 2
hmd.chromaAbCorrection[3] = 0.0f; // HMD chromatic aberration correction parameter 3
}
// Initialize framebuffer and textures for stereo rendering
// NOTE: screen size should match HMD aspect ratio
vrConfig.stereoFbo = rlglLoadRenderTexture(screenWidth, screenHeight);
// Load distortion shader (initialized by default with Oculus Rift CV1 parameters)
vrConfig.distortionShader.id = LoadShaderProgram(vDistortionShaderStr, fDistortionShaderStr);
if (vrConfig.distortionShader.id != 0) LoadDefaultShaderLocations(&vrConfig.distortionShader);
SetStereoConfig(hmd);
vrSimulator = true;
vrEnabled = true;
}
}
// Close VR device (or simulator)
void CloseVrDevice(void)
{
#if defined(RLGL_OCULUS_SUPPORT)
if (vrDeviceReady) CloseOculusDevice();
else
#endif
{
rlDeleteRenderTextures(vrConfig.stereoFbo); // Unload stereo framebuffer and texture
UnloadShader(vrConfig.distortionShader); // Unload distortion shader
}
vrDeviceReady = false;
}
// Detect if VR device is available
bool IsVrDeviceReady(void)
{
return (vrDeviceReady || vrSimulator) && vrEnabled;
}
// Enable/Disable VR experience (device or simulator)
void ToggleVrMode(void)
{
vrEnabled = !vrEnabled;
}
// Update VR tracking (position and orientation)
void UpdateVrTracking(void)
{
#if defined(RLGL_OCULUS_SUPPORT)
if (vrDeviceReady) UpdateOculusTracking();
else
#endif
{
// TODO: Use alternative inputs (mouse, keyboard) to simulate tracking data (eyes position/orientation)
}
}
// Begin Oculus drawing configuration
void BeginVrDrawing(void)
{
#if defined(RLGL_OCULUS_SUPPORT)
if (vrDeviceReady)
{
BeginOculusDrawing();
}
else
#endif
{
// Setup framebuffer for stereo rendering
rlEnableRenderTexture(vrConfig.stereoFbo.id);
}
// NOTE: If your application is configured to treat the texture as a linear format (e.g. GL_RGBA)
// and performs linear-to-gamma conversion in GLSL or does not care about gamma-correction, then:
// - Require OculusBuffer format to be OVR_FORMAT_R8G8B8A8_UNORM_SRGB
// - Do NOT enable GL_FRAMEBUFFER_SRGB
//glEnable(GL_FRAMEBUFFER_SRGB);
//glViewport(0, 0, buffer.width, buffer.height); // Useful if rendering to separate framebuffers (every eye)
rlClearScreenBuffers(); // Clear current framebuffer(s)
vrRendering = true;
}
// End Oculus drawing process (and desktop mirror)
void EndVrDrawing(void)
{
#if defined(RLGL_OCULUS_SUPPORT)
if (vrDeviceReady)
{
EndOculusDrawing();
}
else
#endif
{
// Unbind current framebuffer
rlDisableRenderTexture();
rlClearScreenBuffers(); // Clear current framebuffer
// Set viewport to default framebuffer size (screen size)
rlViewport(0, 0, screenWidth, screenHeight);
// Let rlgl reconfigure internal matrices
rlMatrixMode(RL_PROJECTION); // Enable internal projection matrix
rlLoadIdentity(); // Reset internal projection matrix
rlOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); // Recalculate internal projection matrix
rlMatrixMode(RL_MODELVIEW); // Enable internal modelview matrix
rlLoadIdentity(); // Reset internal modelview matrix
// Draw RenderTexture (stereoFbo) using distortion shader
currentShader = vrConfig.distortionShader;
rlEnableTexture(vrConfig.stereoFbo.texture.id);
rlPushMatrix();
rlBegin(RL_QUADS);
rlColor4ub(255, 255, 255, 255);
rlNormal3f(0.0f, 0.0f, 1.0f);
// Bottom-left corner for texture and quad
rlTexCoord2f(0.0f, 1.0f);
rlVertex2f(0.0f, 0.0f);
// Bottom-right corner for texture and quad
rlTexCoord2f(0.0f, 0.0f);
rlVertex2f(0.0f, vrConfig.stereoFbo.texture.height);
// Top-right corner for texture and quad
rlTexCoord2f(1.0f, 0.0f);
rlVertex2f(vrConfig.stereoFbo.texture.width, vrConfig.stereoFbo.texture.height);
// Top-left corner for texture and quad
rlTexCoord2f(1.0f, 1.0f);
rlVertex2f(vrConfig.stereoFbo.texture.width, 0.0f);
rlEnd();
rlPopMatrix();
rlDisableTexture();
UpdateDefaultBuffers();
DrawDefaultBuffers(1);
currentShader = defaultShader;
}
rlDisableDepthTest();
vrRendering = false;
}
//----------------------------------------------------------------------------------
// Module specific Functions Definition
//----------------------------------------------------------------------------------
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Convert image data to OpenGL texture (returns OpenGL valid Id)
// NOTE: Expected compressed image data and POT image
static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
int blockSize = 0; // Bytes every block
int offset = 0;
if ((compressedFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) ||
(compressedFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ||
#if defined(GRAPHICS_API_OPENGL_ES2)
(compressedFormat == GL_ETC1_RGB8_OES) ||
#endif
(compressedFormat == GL_COMPRESSED_RGB8_ETC2)) blockSize = 8;
else blockSize = 16;
// Load the mipmap levels
for (int level = 0; level < mipmapCount && (width || height); level++)
{
unsigned int size = 0;
size = ((width + 3)/4)*((height + 3)/4)*blockSize;
glCompressedTexImage2D(GL_TEXTURE_2D, level, compressedFormat, width, height, 0, size, data + offset);
offset += size;
width /= 2;
height /= 2;
// Security check for NPOT textures
if (width < 1) width = 1;
if (height < 1) height = 1;
}
}
// Load custom shader strings and return program id
static unsigned int LoadShaderProgram(const char *vShaderStr, const char *fShaderStr)
{
unsigned int program = 0;
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
GLuint vertexShader;
GLuint fragmentShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
const char *pvs = vShaderStr;
const char *pfs = fShaderStr;
glShaderSource(vertexShader, 1, &pvs, NULL);
glShaderSource(fragmentShader, 1, &pfs, NULL);
GLint success = 0;
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (success != GL_TRUE)
{
TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader);
int maxLength = 0;
int length;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength);
char log[maxLength];
glGetShaderInfoLog(vertexShader, maxLength, &length, log);
TraceLog(INFO, "%s", log);
}
else TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (success != GL_TRUE)
{
TraceLog(WARNING, "[FSHDR ID %i] Failed to compile fragment shader...", fragmentShader);
int maxLength = 0;
int length;
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength);
char log[maxLength];
glGetShaderInfoLog(fragmentShader, maxLength, &length, log);
TraceLog(INFO, "%s", log);
}
else TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader);
program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
// NOTE: Default attribute shader locations must be binded before linking
glBindAttribLocation(program, 0, DEFAULT_ATTRIB_POSITION_NAME);
glBindAttribLocation(program, 1, DEFAULT_ATTRIB_TEXCOORD_NAME);
glBindAttribLocation(program, 2, DEFAULT_ATTRIB_NORMAL_NAME);
glBindAttribLocation(program, 3, DEFAULT_ATTRIB_COLOR_NAME);
glBindAttribLocation(program, 4, DEFAULT_ATTRIB_TANGENT_NAME);
glBindAttribLocation(program, 5, DEFAULT_ATTRIB_TEXCOORD2_NAME);
// NOTE: If some attrib name is no found on the shader, it locations becomes -1
glLinkProgram(program);
// NOTE: All uniform variables are intitialised to 0 when a program links
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (success == GL_FALSE)
{
TraceLog(WARNING, "[SHDR ID %i] Failed to link shader program...", program);
int maxLength = 0;
int length;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
char log[maxLength];
glGetProgramInfoLog(program, maxLength, &length, log);
TraceLog(INFO, "%s", log);
glDeleteProgram(program);
program = 0;
}
else TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
#endif
return program;
}
// Load default shader (just vertex positioning and texture coloring)
// NOTE: This shader program is used for batch buffers (lines, triangles, quads)
static Shader LoadDefaultShader(void)
{
Shader shader;
// Vertex shader directly defined, no external file required
char vDefaultShaderStr[] =
#if defined(GRAPHICS_API_OPENGL_21)
"#version 120 \n"
#elif defined(GRAPHICS_API_OPENGL_ES2)
"#version 100 \n"
#endif
#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21)
"attribute vec3 vertexPosition; \n"
"attribute vec2 vertexTexCoord; \n"
"attribute vec4 vertexColor; \n"
"varying vec2 fragTexCoord; \n"
"varying vec4 fragColor; \n"
#elif defined(GRAPHICS_API_OPENGL_33)
"#version 330 \n"
"in vec3 vertexPosition; \n"
"in vec2 vertexTexCoord; \n"
"in vec4 vertexColor; \n"
"out vec2 fragTexCoord; \n"
"out vec4 fragColor; \n"
#endif
"uniform mat4 mvpMatrix; \n"
"void main() \n"
"{ \n"
" fragTexCoord = vertexTexCoord; \n"
" fragColor = vertexColor; \n"
" gl_Position = mvpMatrix*vec4(vertexPosition, 1.0); \n"
"} \n";
// Fragment shader directly defined, no external file required
char fDefaultShaderStr[] =
#if defined(GRAPHICS_API_OPENGL_21)
"#version 120 \n"
#elif defined(GRAPHICS_API_OPENGL_ES2)
"#version 100 \n"
"precision mediump float; \n" // precision required for OpenGL ES2 (WebGL)
#endif
#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21)
"varying vec2 fragTexCoord; \n"
"varying vec4 fragColor; \n"
#elif defined(GRAPHICS_API_OPENGL_33)
"#version 330 \n"
"in vec2 fragTexCoord; \n"
"in vec4 fragColor; \n"
"out vec4 finalColor; \n"
#endif
"uniform sampler2D texture0; \n"
"uniform vec4 colDiffuse; \n"
"void main() \n"
"{ \n"
#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21)
" vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0
" gl_FragColor = texelColor*colDiffuse*fragColor; \n"
#elif defined(GRAPHICS_API_OPENGL_33)
" vec4 texelColor = texture(texture0, fragTexCoord); \n"
" finalColor = texelColor*colDiffuse*fragColor; \n"
#endif
"} \n";
shader.id = LoadShaderProgram(vDefaultShaderStr, fDefaultShaderStr);
if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id);
else TraceLog(WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id);
if (shader.id != 0) LoadDefaultShaderLocations(&shader);
return shader;
}
// Load standard shader
// NOTE: This shader supports:
// - Up to 3 different maps: diffuse, normal, specular
// - Material properties: colAmbient, colDiffuse, colSpecular, glossiness
// - Up to 8 lights: Point, Directional or Spot
static Shader LoadStandardShader(void)
{
Shader shader;
#if !defined(RLGL_NO_STANDARD_SHADER)
// Load standard shader (embeded in standard_shader.h)
shader.id = LoadShaderProgram(vStandardShaderStr, fStandardShaderStr);
if (shader.id != 0)
{
LoadDefaultShaderLocations(&shader);
TraceLog(INFO, "[SHDR ID %i] Standard shader loaded successfully", shader.id);
standardShaderLoaded = true;
}
else
{
TraceLog(WARNING, "[SHDR ID %i] Standard shader could not be loaded, using default shader", shader.id);
shader = GetDefaultShader();
}
#else
shader = defaultShader;
TraceLog(WARNING, "[SHDR ID %i] Standard shader not available, using default shader", shader.id);
#endif
return shader;
}
// Get location handlers to for shader attributes and uniforms
// NOTE: If any location is not found, loc point becomes -1
static void LoadDefaultShaderLocations(Shader *shader)
{
// NOTE: Default shader attrib locations have been fixed before linking:
// vertex position location = 0
// vertex texcoord location = 1
// vertex normal location = 2
// vertex color location = 3
// vertex tangent location = 4
// vertex texcoord2 location = 5
// Get handles to GLSL input attibute locations
shader->vertexLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_POSITION_NAME);
shader->texcoordLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD_NAME);
shader->texcoord2Loc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD2_NAME);
shader->normalLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_NORMAL_NAME);
shader->tangentLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TANGENT_NAME);
shader->colorLoc = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_COLOR_NAME);
// Get handles to GLSL uniform locations (vertex shader)
shader->mvpLoc = glGetUniformLocation(shader->id, "mvpMatrix");
// Get handles to GLSL uniform locations (fragment shader)
shader->tintColorLoc = glGetUniformLocation(shader->id, "colDiffuse");
shader->mapTexture0Loc = glGetUniformLocation(shader->id, "texture0");
shader->mapTexture1Loc = glGetUniformLocation(shader->id, "texture1");
shader->mapTexture2Loc = glGetUniformLocation(shader->id, "texture2");
}
// Unload default shader
static void UnloadDefaultShader(void)
{
glUseProgram(0);
//glDetachShader(defaultShader, vertexShader);
//glDetachShader(defaultShader, fragmentShader);
//glDeleteShader(vertexShader); // Already deleted on shader compilation
//glDeleteShader(fragmentShader); // Already deleted on shader compilation
glDeleteProgram(defaultShader.id);
}
// Unload standard shader
static void UnloadStandardShader(void)
{
glUseProgram(0);
#if !defined(RLGL_NO_STANDARD_SHADER)
//glDetachShader(defaultShader, vertexShader);
//glDetachShader(defaultShader, fragmentShader);
//glDeleteShader(vertexShader); // Already deleted on shader compilation
//glDeleteShader(fragmentShader); // Already deleted on shader compilation
glDeleteProgram(standardShader.id);
#endif
}
// Load default internal buffers (lines, triangles, quads)
static void LoadDefaultBuffers(void)
{
// [CPU] Allocate and initialize float array buffers to store vertex data (lines, triangles, quads)
//--------------------------------------------------------------------------------------------
// Lines - Initialize arrays (vertex position and color data)
lines.vertices = (float *)malloc(sizeof(float)*3*2*MAX_LINES_BATCH); // 3 float by vertex, 2 vertex by line
lines.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*2*MAX_LINES_BATCH); // 4 float by color, 2 colors by line
lines.texcoords = NULL;
lines.indices = NULL;
for (int i = 0; i < (3*2*MAX_LINES_BATCH); i++) lines.vertices[i] = 0.0f;
for (int i = 0; i < (4*2*MAX_LINES_BATCH); i++) lines.colors[i] = 0;
lines.vCounter = 0;
lines.cCounter = 0;
lines.tcCounter = 0;
// Triangles - Initialize arrays (vertex position and color data)
triangles.vertices = (float *)malloc(sizeof(float)*3*3*MAX_TRIANGLES_BATCH); // 3 float by vertex, 3 vertex by triangle
triangles.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH); // 4 float by color, 3 colors by triangle
triangles.texcoords = NULL;
triangles.indices = NULL;
for (int i = 0; i < (3*3*MAX_TRIANGLES_BATCH); i++) triangles.vertices[i] = 0.0f;
for (int i = 0; i < (4*3*MAX_TRIANGLES_BATCH); i++) triangles.colors[i] = 0;
triangles.vCounter = 0;
triangles.cCounter = 0;
triangles.tcCounter = 0;
// Quads - Initialize arrays (vertex position, texcoord, color data and indexes)
quads.vertices = (float *)malloc(sizeof(float)*3*4*MAX_QUADS_BATCH); // 3 float by vertex, 4 vertex by quad
quads.texcoords = (float *)malloc(sizeof(float)*2*4*MAX_QUADS_BATCH); // 2 float by texcoord, 4 texcoord by quad
quads.colors = (unsigned char *)malloc(sizeof(unsigned char)*4*4*MAX_QUADS_BATCH); // 4 float by color, 4 colors by quad
#if defined(GRAPHICS_API_OPENGL_33)
quads.indices = (unsigned int *)malloc(sizeof(int)*6*MAX_QUADS_BATCH); // 6 int by quad (indices)
#elif defined(GRAPHICS_API_OPENGL_ES2)
quads.indices = (unsigned short *)malloc(sizeof(short)*6*MAX_QUADS_BATCH); // 6 int by quad (indices)
#endif
for (int i = 0; i < (3*4*MAX_QUADS_BATCH); i++) quads.vertices[i] = 0.0f;
for (int i = 0; i < (2*4*MAX_QUADS_BATCH); i++) quads.texcoords[i] = 0.0f;
for (int i = 0; i < (4*4*MAX_QUADS_BATCH); i++) quads.colors[i] = 0;
int k = 0;
// Indices can be initialized right now
for (int i = 0; i < (6*MAX_QUADS_BATCH); i+=6)
{
quads.indices[i] = 4*k;
quads.indices[i+1] = 4*k+1;
quads.indices[i+2] = 4*k+2;
quads.indices[i+3] = 4*k;
quads.indices[i+4] = 4*k+2;
quads.indices[i+5] = 4*k+3;
k++;
}
quads.vCounter = 0;
quads.tcCounter = 0;
quads.cCounter = 0;
TraceLog(INFO, "[CPU] Default buffers initialized successfully (lines, triangles, quads)");
//--------------------------------------------------------------------------------------------
// [GPU] Upload vertex data and initialize VAOs/VBOs (lines, triangles, quads)
// NOTE: Default buffers are linked to use currentShader (defaultShader)
//--------------------------------------------------------------------------------------------
// Upload and link lines vertex buffers
if (vaoSupported)
{
// Initialize Lines VAO
glGenVertexArrays(1, &lines.vaoId);
glBindVertexArray(lines.vaoId);
}
// Lines - Vertex buffers binding and attributes enable
// Vertex position buffer (shader-location = 0)
glGenBuffers(2, &lines.vboId[0]);
glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.vertexLoc);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
// Vertex color buffer (shader-location = 3)
glGenBuffers(2, &lines.vboId[1]);
glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.colorLoc);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (lines)", lines.vaoId);
else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (lines)", lines.vboId[0], lines.vboId[1]);
// Upload and link triangles vertex buffers
if (vaoSupported)
{
// Initialize Triangles VAO
glGenVertexArrays(1, &triangles.vaoId);
glBindVertexArray(triangles.vaoId);
}
// Triangles - Vertex buffers binding and attributes enable
// Vertex position buffer (shader-location = 0)
glGenBuffers(1, &triangles.vboId[0]);
glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.vertexLoc);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
// Vertex color buffer (shader-location = 3)
glGenBuffers(1, &triangles.vboId[1]);
glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.colorLoc);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (triangles)", triangles.vaoId);
else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (triangles)", triangles.vboId[0], triangles.vboId[1]);
// Upload and link quads vertex buffers
if (vaoSupported)
{
// Initialize Quads VAO
glGenVertexArrays(1, &quads.vaoId);
glBindVertexArray(quads.vaoId);
}
// Quads - Vertex buffers binding and attributes enable
// Vertex position buffer (shader-location = 0)
glGenBuffers(1, &quads.vboId[0]);
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.vertexLoc);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
// Vertex texcoord buffer (shader-location = 1)
glGenBuffers(1, &quads.vboId[1]);
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.texcoordLoc);
glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
// Vertex color buffer (shader-location = 3)
glGenBuffers(1, &quads.vboId[2]);
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]);
glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(currentShader.colorLoc);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
// Fill index buffer
glGenBuffers(1, &quads.vboId[3]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
#if defined(GRAPHICS_API_OPENGL_33)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW);
#elif defined(GRAPHICS_API_OPENGL_ES2)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_QUADS_BATCH, quads.indices, GL_STATIC_DRAW);
#endif
if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Default buffers VAO initialized successfully (quads)", quads.vaoId);
else TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i][VBO ID %i] Default buffers VBOs initialized successfully (quads)", quads.vboId[0], quads.vboId[1], quads.vboId[2], quads.vboId[3]);
// Unbind the current VAO
if (vaoSupported) glBindVertexArray(0);
//--------------------------------------------------------------------------------------------
}
// Update default internal buffers (VAOs/VBOs) with vertex array data
// NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0)
// TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required)
static void UpdateDefaultBuffers(void)
{
// Update lines vertex buffers
if (lines.vCounter > 0)
{
// Activate Lines VAO
if (vaoSupported) glBindVertexArray(lines.vaoId);
// Lines - vertex positions buffer
glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*lines.vCounter, lines.vertices); // target - offset (in bytes) - size (in bytes) - data pointer
// Lines - colors buffer
glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*lines.cCounter, lines.colors);
}
// Update triangles vertex buffers
if (triangles.vCounter > 0)
{
// Activate Triangles VAO
if (vaoSupported) glBindVertexArray(triangles.vaoId);
// Triangles - vertex positions buffer
glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*triangles.vCounter, triangles.vertices);
// Triangles - colors buffer
glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*triangles.cCounter, triangles.colors);
}
// Update quads vertex buffers
if (quads.vCounter > 0)
{
// Activate Quads VAO
if (vaoSupported) glBindVertexArray(quads.vaoId);
// Quads - vertex positions buffer
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*quads.vCounter, quads.vertices);
// Quads - texture coordinates buffer
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*quads.vCounter, quads.texcoords);
// Quads - colors buffer
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]);
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*quads.vCounter, quads.colors);
// Another option would be using buffer mapping...
//quads.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
// Now we can modify vertices
//glUnmapBuffer(GL_ARRAY_BUFFER);
}
//--------------------------------------------------------------
// Unbind the current VAO
if (vaoSupported) glBindVertexArray(0);
}
// Draw default internal buffers vertex data
// NOTE: We draw in this order: lines, triangles, quads
static void DrawDefaultBuffers(int eyesCount)
{
Matrix matProjection = projection;
Matrix matModelView = modelview;
for (int eye = 0; eye < eyesCount; eye++)
{
if (eyesCount == 2) SetStereoView(eye, matProjection, matModelView);
// Set current shader and upload current MVP matrix
if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0))
{
glUseProgram(currentShader.id);
// Create modelview-projection matrix
Matrix matMVP = MatrixMultiply(modelview, projection);
glUniformMatrix4fv(currentShader.mvpLoc, 1, false, MatrixToFloat(matMVP));
glUniform4f(currentShader.tintColorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
glUniform1i(currentShader.mapTexture0Loc, 0);
// NOTE: Additional map textures not considered for default buffers drawing
}
// Draw lines buffers
if (lines.vCounter > 0)
{
glBindTexture(GL_TEXTURE_2D, whiteTexture);
if (vaoSupported)
{
glBindVertexArray(lines.vaoId);
}
else
{
// Bind vertex attrib: position (shader-location = 0)
glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[0]);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(currentShader.vertexLoc);
// Bind vertex attrib: color (shader-location = 3)
glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
glEnableVertexAttribArray(currentShader.colorLoc);
}
glDrawArrays(GL_LINES, 0, lines.vCounter);
if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
// Draw triangles buffers
if (triangles.vCounter > 0)
{
glBindTexture(GL_TEXTURE_2D, whiteTexture);
if (vaoSupported)
{
glBindVertexArray(triangles.vaoId);
}
else
{
// Bind vertex attrib: position (shader-location = 0)
glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[0]);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(currentShader.vertexLoc);
// Bind vertex attrib: color (shader-location = 3)
glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
glEnableVertexAttribArray(currentShader.colorLoc);
}
glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter);
if (!vaoSupported) glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
// Draw quads buffers
if (quads.vCounter > 0)
{
int quadsCount = 0;
int numIndicesToProcess = 0;
int indicesOffset = 0;
if (vaoSupported)
{
glBindVertexArray(quads.vaoId);
}
else
{
// Bind vertex attrib: position (shader-location = 0)
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[0]);
glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(currentShader.vertexLoc);
// Bind vertex attrib: texcoord (shader-location = 1)
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]);
glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(currentShader.texcoordLoc);
// Bind vertex attrib: color (shader-location = 3)
glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]);
glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
glEnableVertexAttribArray(currentShader.colorLoc);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]);
}
//TraceLog(DEBUG, "Draws required per frame: %i", drawsCounter);
for (int i = 0; i < drawsCounter; i++)
{
quadsCount = draws[i].vertexCount/4;
numIndicesToProcess = quadsCount*6; // Get number of Quads * 6 index by Quad
//TraceLog(DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount);
glBindTexture(GL_TEXTURE_2D, draws[i].textureId);
// NOTE: The final parameter tells the GPU the offset in bytes from the start of the index buffer to the location of the first index to process
#if defined(GRAPHICS_API_OPENGL_33)
glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_INT, (GLvoid *)(sizeof(GLuint)*indicesOffset));
#elif defined(GRAPHICS_API_OPENGL_ES2)
glDrawElements(GL_TRIANGLES, numIndicesToProcess, GL_UNSIGNED_SHORT, (GLvoid *)(sizeof(GLushort)*indicesOffset));
#endif
//GLenum err;
//if ((err = glGetError()) != GL_NO_ERROR) TraceLog(INFO, "OpenGL error: %i", (int)err); //GL_INVALID_ENUM!
indicesOffset += draws[i].vertexCount/4*6;
}
if (!vaoSupported)
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures
}
if (vaoSupported) glBindVertexArray(0); // Unbind VAO
glUseProgram(0); // Unbind shader program
}
// Reset draws counter
drawsCounter = 1;
draws[0].textureId = whiteTexture;
draws[0].vertexCount = 0;
// Reset vertex counters for next frame
lines.vCounter = 0;
lines.cCounter = 0;
triangles.vCounter = 0;
triangles.cCounter = 0;
quads.vCounter = 0;
quads.tcCounter = 0;
quads.cCounter = 0;
// Reset depth for next draw
currentDepth = -1.0f;
// Restore projection/modelview matrices
projection = matProjection;
modelview = matModelView;
}
// Unload default internal buffers vertex data from CPU and GPU
static void UnloadDefaultBuffers(void)
{
// Unbind everything
if (vaoSupported) glBindVertexArray(0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// Delete VBOs from GPU (VRAM)
glDeleteBuffers(1, &lines.vboId[0]);
glDeleteBuffers(1, &lines.vboId[1]);
glDeleteBuffers(1, &triangles.vboId[0]);
glDeleteBuffers(1, &triangles.vboId[1]);
glDeleteBuffers(1, &quads.vboId[0]);
glDeleteBuffers(1, &quads.vboId[1]);
glDeleteBuffers(1, &quads.vboId[2]);
glDeleteBuffers(1, &quads.vboId[3]);
if (vaoSupported)
{
// Delete VAOs from GPU (VRAM)
glDeleteVertexArrays(1, &lines.vaoId);
glDeleteVertexArrays(1, &triangles.vaoId);
glDeleteVertexArrays(1, &quads.vaoId);
}
// Free vertex arrays memory from CPU (RAM)
free(lines.vertices);
free(lines.colors);
free(triangles.vertices);
free(triangles.colors);
free(quads.vertices);
free(quads.texcoords);
free(quads.colors);
free(quads.indices);
}
// Setup shader uniform values for lights array
// NOTE: It would be far easier with shader UBOs but are not supported on OpenGL ES 2.0f
static void SetShaderLights(Shader shader)
{
int locPoint = -1;
char locName[32] = "lights[x].position\0";
for (int i = 0; i < MAX_LIGHTS; i++)
{
locName[7] = '0' + i;
if (lights[i] != NULL) // Only upload registered lights data
{
memcpy(&locName[10], "enabled\0", strlen("enabled\0") + 1);
locPoint = GetShaderLocation(shader, locName);
glUniform1i(locPoint, lights[i]->enabled);
memcpy(&locName[10], "type\0", strlen("type\0") + 1);
locPoint = GetShaderLocation(shader, locName);
glUniform1i(locPoint, lights[i]->type);
memcpy(&locName[10], "diffuse\0", strlen("diffuse\0") + 2);
locPoint = glGetUniformLocation(shader.id, locName);
glUniform4f(locPoint, (float)lights[i]->diffuse.r/255, (float)lights[i]->diffuse.g/255, (float)lights[i]->diffuse.b/255, (float)lights[i]->diffuse.a/255);
memcpy(&locName[10], "intensity\0", strlen("intensity\0"));
locPoint = glGetUniformLocation(shader.id, locName);
glUniform1f(locPoint, lights[i]->intensity);
switch (lights[i]->type)
{
case LIGHT_POINT:
{
memcpy(&locName[10], "position\0", strlen("position\0") + 1);
locPoint = GetShaderLocation(shader, locName);
glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z);
memcpy(&locName[10], "radius\0", strlen("radius\0") + 2);
locPoint = GetShaderLocation(shader, locName);
glUniform1f(locPoint, lights[i]->radius);
} break;
case LIGHT_DIRECTIONAL:
{
memcpy(&locName[10], "direction\0", strlen("direction\0") + 2);
locPoint = GetShaderLocation(shader, locName);
Vector3 direction = { lights[i]->target.x - lights[i]->position.x, lights[i]->target.y - lights[i]->position.y, lights[i]->target.z - lights[i]->position.z };
VectorNormalize(&direction);
glUniform3f(locPoint, direction.x, direction.y, direction.z);
} break;
case LIGHT_SPOT:
{
memcpy(&locName[10], "position\0", strlen("position\0") + 1);
locPoint = GetShaderLocation(shader, locName);
glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z);
memcpy(&locName[10], "direction\0", strlen("direction\0") + 2);
locPoint = GetShaderLocation(shader, locName);
Vector3 direction = { lights[i]->target.x - lights[i]->position.x, lights[i]->target.y - lights[i]->position.y, lights[i]->target.z - lights[i]->position.z };
VectorNormalize(&direction);
glUniform3f(locPoint, direction.x, direction.y, direction.z);
memcpy(&locName[10], "coneAngle\0", strlen("coneAngle\0"));
locPoint = GetShaderLocation(shader, locName);
glUniform1f(locPoint, lights[i]->coneAngle);
} break;
default: break;
}
// TODO: Pass to the shader any other required data from LightData struct
}
else // Not enabled lights
{
memcpy(&locName[10], "enabled\0", strlen("enabled\0") + 1);
locPoint = GetShaderLocation(shader, locName);
glUniform1i(locPoint, 0);
}
}
}
// Read text data from file
// NOTE: text chars array should be freed manually
static char *ReadTextFile(const char *fileName)
{
FILE *textFile;
char *text = NULL;
int count = 0;
if (fileName != NULL)
{
textFile = fopen(fileName,"rt");
if (textFile != NULL)
{
fseek(textFile, 0, SEEK_END);
count = ftell(textFile);
rewind(textFile);
if (count > 0)
{
text = (char *)malloc(sizeof(char)*(count + 1));
count = fread(text, sizeof(char), count, textFile);
text[count] = '\0';
}
fclose(textFile);
}
else TraceLog(WARNING, "[%s] Text file could not be opened", fileName);
}
return text;
}
#endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if defined(GRAPHICS_API_OPENGL_11)
// Mipmaps data is generated after image data
static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight)
{
int mipmapCount = 1; // Required mipmap levels count (including base level)
int width = baseWidth;
int height = baseHeight;
int size = baseWidth*baseHeight*4; // Size in bytes (will include mipmaps...), RGBA only
// Count mipmap levels required
while ((width != 1) && (height != 1))
{
if (width != 1) width /= 2;
if (height != 1) height /= 2;
TraceLog(DEBUG, "Next mipmap size: %i x %i", width, height);
mipmapCount++;
size += (width*height*4); // Add mipmap size (in bytes)
}
TraceLog(DEBUG, "Total mipmaps required: %i", mipmapCount);
TraceLog(DEBUG, "Total size of data required: %i", size);
unsigned char *temp = realloc(data, size);
if (temp != NULL) data = temp;
else TraceLog(WARNING, "Mipmaps required memory could not be allocated");
width = baseWidth;
height = baseHeight;
size = (width*height*4);
// Generate mipmaps
// NOTE: Every mipmap data is stored after data
Color *image = (Color *)malloc(width*height*sizeof(Color));
Color *mipmap = NULL;
int offset = 0;
int j = 0;
for (int i = 0; i < size; i += 4)
{
image[j].r = data[i];
image[j].g = data[i + 1];
image[j].b = data[i + 2];
image[j].a = data[i + 3];
j++;
}
TraceLog(DEBUG, "Mipmap base (%ix%i)", width, height);
for (int mip = 1; mip < mipmapCount; mip++)
{
mipmap = GenNextMipmap(image, width, height);
offset += (width*height*4); // Size of last mipmap
j = 0;
width /= 2;
height /= 2;
size = (width*height*4); // Mipmap size to store after offset
// Add mipmap to data
for (int i = 0; i < size; i += 4)
{
data[offset + i] = mipmap[j].r;
data[offset + i + 1] = mipmap[j].g;
data[offset + i + 2] = mipmap[j].b;
data[offset + i + 3] = mipmap[j].a;
j++;
}
free(image);
image = mipmap;
mipmap = NULL;
}
free(mipmap); // free mipmap data
return mipmapCount;
}
// Manual mipmap generation (basic scaling algorithm)
static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight)
{
int x2, y2;
Color prow, pcol;
int width = srcWidth/2;
int height = srcHeight/2;
Color *mipmap = (Color *)malloc(width*height*sizeof(Color));
// Scaling algorithm works perfectly (box-filter)
for (int y = 0; y < height; y++)
{
y2 = 2*y;
for (int x = 0; x < width; x++)
{
x2 = 2*x;
prow.r = (srcData[y2*srcWidth + x2].r + srcData[y2*srcWidth + x2 + 1].r)/2;
prow.g = (srcData[y2*srcWidth + x2].g + srcData[y2*srcWidth + x2 + 1].g)/2;
prow.b = (srcData[y2*srcWidth + x2].b + srcData[y2*srcWidth + x2 + 1].b)/2;
prow.a = (srcData[y2*srcWidth + x2].a + srcData[y2*srcWidth + x2 + 1].a)/2;
pcol.r = (srcData[(y2+1)*srcWidth + x2].r + srcData[(y2+1)*srcWidth + x2 + 1].r)/2;
pcol.g = (srcData[(y2+1)*srcWidth + x2].g + srcData[(y2+1)*srcWidth + x2 + 1].g)/2;
pcol.b = (srcData[(y2+1)*srcWidth + x2].b + srcData[(y2+1)*srcWidth + x2 + 1].b)/2;
pcol.a = (srcData[(y2+1)*srcWidth + x2].a + srcData[(y2+1)*srcWidth + x2 + 1].a)/2;
mipmap[y*width + x].r = (prow.r + pcol.r)/2;
mipmap[y*width + x].g = (prow.g + pcol.g)/2;
mipmap[y*width + x].b = (prow.b + pcol.b)/2;
mipmap[y*width + x].a = (prow.a + pcol.a)/2;
}
}
TraceLog(DEBUG, "Mipmap generated successfully (%ix%i)", width, height);
return mipmap;
}
#endif
// Configure stereo rendering (including distortion shader) with HMD device parameters
static void SetStereoConfig(VrDeviceInfo hmd)
{
// Compute aspect ratio
float aspect = ((float)hmd.hResolution*0.5f)/(float)hmd.vResolution;
// Compute lens parameters
float lensShift = (hmd.hScreenSize*0.25f - hmd.lensSeparationDistance*0.5f)/hmd.hScreenSize;
float leftLensCenter[2] = { 0.25 + lensShift, 0.5f };
float rightLensCenter[2] = { 0.75 - lensShift, 0.5f };
float leftScreenCenter[2] = { 0.25f, 0.5f };
float rightScreenCenter[2] = { 0.75f, 0.5f };
// Compute distortion scale parameters
// NOTE: To get lens max radius, lensShift must be normalized to [-1..1]
float lensRadius = fabsf(-1.0f - 4.0f*lensShift);
float lensRadiusSq = lensRadius*lensRadius;
float distortionScale = hmd.distortionK[0] +
hmd.distortionK[1]*lensRadiusSq +
hmd.distortionK[2]*lensRadiusSq*lensRadiusSq +
hmd.distortionK[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq;
TraceLog(DEBUG, "VR: Distortion Scale: %f", distortionScale);
float normScreenWidth = 0.5f;
float normScreenHeight = 1.0f;
float scaleIn[2] = { 2/normScreenWidth, 2/normScreenHeight/aspect };
float scale[2] = { normScreenWidth*0.5/distortionScale, normScreenHeight*0.5*aspect/distortionScale };
TraceLog(DEBUG, "VR: Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]);
TraceLog(DEBUG, "VR: Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]);
TraceLog(DEBUG, "VR: Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]);
TraceLog(DEBUG, "VR: Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]);
// Update distortion shader with lens and distortion-scale parameters
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftLensCenter"), leftLensCenter, 2);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightLensCenter"), rightLensCenter, 2);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftScreenCenter"), leftScreenCenter, 2);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightScreenCenter"), rightScreenCenter, 2);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scale"), scale, 2);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scaleIn"), scaleIn, 2);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "hmdWarpParam"), hmd.distortionK, 4);
SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, 4);
// Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance)*RAD2DEG
// ...but with lens distortion it is increased (see Oculus SDK Documentation)
//float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance)*RAD2DEG; // Really need distortionScale?
float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f, hmd.eyeToScreenDistance)*RAD2DEG;
// Compute camera projection matrices
float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1]
Matrix proj = MatrixPerspective(fovy, aspect, 0.01, 1000.0);
vrConfig.eyesProjection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f));
vrConfig.eyesProjection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f));
// NOTE: Projection matrices must be transposed due to raymath convention
MatrixTranspose(&vrConfig.eyesProjection[0]);
MatrixTranspose(&vrConfig.eyesProjection[1]);
// Compute camera transformation matrices
// NOTE: Camera movement might seem more natural if we model the head.
// Our axis of rotation is the base of our head, so we might want to add
// some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions.
vrConfig.eyesViewOffset[0] = MatrixTranslate(-hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f);
vrConfig.eyesViewOffset[1] = MatrixTranslate(hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f);
// Compute eyes Viewports
//vrConfig.eyesViewport[0] = (Rectangle){ 0, 0, hmd.hResolution/2, hmd.vResolution };
//vrConfig.eyesViewport[1] = (Rectangle){ hmd.hResolution/2, 0, hmd.hResolution/2, hmd.vResolution };
}
// Set internal projection and modelview matrix depending on eyes tracking data
static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView)
{
if (vrEnabled)
{
Matrix eyeProjection = matProjection;
Matrix eyeModelView = matModelView;
#if defined(RLGL_OCULUS_SUPPORT)
if (vrDeviceReady)
{
rlViewport(layer.eyeLayer.Viewport[eye].Pos.x, layer.eyeLayer.Viewport[eye].Pos.y,
layer.eyeLayer.Viewport[eye].Size.w, layer.eyeLayer.Viewport[eye].Size.h);
Quaternion eyeRenderPose = (Quaternion){ layer.eyeLayer.RenderPose[eye].Orientation.x,
layer.eyeLayer.RenderPose[eye].Orientation.y,
layer.eyeLayer.RenderPose[eye].Orientation.z,
layer.eyeLayer.RenderPose[eye].Orientation.w };
QuaternionInvert(&eyeRenderPose);
Matrix eyeOrientation = QuaternionToMatrix(eyeRenderPose);
Matrix eyeTranslation = MatrixTranslate(-layer.eyeLayer.RenderPose[eye].Position.x,
-layer.eyeLayer.RenderPose[eye].Position.y,
-layer.eyeLayer.RenderPose[eye].Position.z);
Matrix eyeView = MatrixMultiply(eyeTranslation, eyeOrientation); // Matrix containing eye-head movement
eyeModelView = MatrixMultiply(matModelView, eyeView); // Combine internal camera matrix (modelview) wih eye-head movement
eyeProjection = layer.eyeProjections[eye];
}
else
#endif
{
// Setup viewport and projection/modelview matrices using tracking data
rlViewport(eye*screenWidth/2, 0, screenWidth/2, screenHeight);
// Apply view offset to modelview matrix
eyeModelView = MatrixMultiply(matModelView, vrConfig.eyesViewOffset[eye]);
eyeProjection = vrConfig.eyesProjection[eye];
}
SetMatrixModelview(eyeModelView);
SetMatrixProjection(eyeProjection);
}
}
#if defined(RLGL_OCULUS_SUPPORT)
// Initialize Oculus device
static bool InitOculusDevice(void)
{
bool oculusReady = false;
ovrResult result = ovr_Initialize(NULL);
if (OVR_FAILURE(result)) TraceLog(WARNING, "OVR: Could not initialize Oculus device");
else
{
result = ovr_Create(&session, &luid);
if (OVR_FAILURE(result))
{
TraceLog(WARNING, "OVR: Could not create Oculus session");
ovr_Shutdown();
}
else
{
hmdDesc = ovr_GetHmdDesc(session);
TraceLog(INFO, "OVR: Product Name: %s", hmdDesc.ProductName);
TraceLog(INFO, "OVR: Manufacturer: %s", hmdDesc.Manufacturer);
TraceLog(INFO, "OVR: Product ID: %i", hmdDesc.ProductId);
TraceLog(INFO, "OVR: Product Type: %i", hmdDesc.Type);
//TraceLog(INFO, "OVR: Serial Number: %s", hmdDesc.SerialNumber);
TraceLog(INFO, "OVR: Resolution: %ix%i", hmdDesc.Resolution.w, hmdDesc.Resolution.h);
// NOTE: Oculus mirror is set to defined screenWidth and screenHeight...
// ...ideally, it should be (hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2)
// Initialize Oculus Buffers
layer = InitOculusLayer(session);
buffer = LoadOculusBuffer(session, layer.width, layer.height);
mirror = LoadOculusMirror(session, hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2); // NOTE: hardcoded...
layer.eyeLayer.ColorTexture[0] = buffer.textureChain; //SetOculusLayerTexture(eyeLayer, buffer.textureChain);
// Recenter OVR tracking origin
ovr_RecenterTrackingOrigin(session);
oculusReady = true;
vrEnabled = true;
}
}
return oculusReady;
}
static void CloseOculusDevice(void)
{
UnloadOculusMirror(session, mirror); // Unload Oculus mirror buffer
UnloadOculusBuffer(session, buffer); // Unload Oculus texture buffers
ovr_Destroy(session); // Free Oculus session data
ovr_Shutdown(); // Close Oculus device connection
}
static void UpdateOculusTracking(void)
{
frameIndex++;
ovrPosef eyePoses[2];
ovr_GetEyePoses(session, frameIndex, ovrTrue, layer.viewScaleDesc.HmdToEyeOffset, eyePoses, &layer.eyeLayer.SensorSampleTime);
layer.eyeLayer.RenderPose[0] = eyePoses[0];
layer.eyeLayer.RenderPose[1] = eyePoses[1];
// Get session status information
ovrSessionStatus sessionStatus;
ovr_GetSessionStatus(session, &sessionStatus);
if (sessionStatus.ShouldQuit) TraceLog(WARNING, "OVR: Session should quit...");
if (sessionStatus.ShouldRecenter) ovr_RecenterTrackingOrigin(session);
//if (sessionStatus.HmdPresent) // HMD is present.
//if (sessionStatus.DisplayLost) // HMD was unplugged or the display driver was manually disabled or encountered a TDR.
//if (sessionStatus.HmdMounted) // HMD is on the user's head.
//if (sessionStatus.IsVisible) // the game or experience has VR focus and is visible in the HMD.
}
static void BeginOculusDrawing(void)
{
GLuint currentTexId;
int currentIndex;
ovr_GetTextureSwapChainCurrentIndex(session, buffer.textureChain, &currentIndex);
ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, currentIndex, &currentTexId);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer.fboId);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, currentTexId, 0);
//glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, buffer.depthId, 0); // Already binded
}
static void EndOculusDrawing(void)
{
// Unbind current framebuffer (Oculus buffer)
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
ovr_CommitTextureSwapChain(session, buffer.textureChain);
ovrLayerHeader *layers = &layer.eyeLayer.Header;
ovr_SubmitFrame(session, frameIndex, &layer.viewScaleDesc, &layers, 1);
// Blit mirror texture to back buffer
BlitOculusMirror(session, mirror);
}
// Load Oculus required buffers: texture-swap-chain, fbo, texture-depth
static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height)
{
OculusBuffer buffer;
buffer.width = width;
buffer.height = height;
// Create OVR texture chain
ovrTextureSwapChainDesc desc = {};
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; // Requires glEnable(GL_FRAMEBUFFER_SRGB);
desc.SampleCount = 1;
desc.StaticImage = ovrFalse;
ovrResult result = ovr_CreateTextureSwapChainGL(session, &desc, &buffer.textureChain);
if (!OVR_SUCCESS(result)) TraceLog(WARNING, "OVR: Failed to create swap textures buffer");
int textureCount = 0;
ovr_GetTextureSwapChainLength(session, buffer.textureChain, &textureCount);
if (!OVR_SUCCESS(result) || !textureCount) TraceLog(WARNING, "OVR: Unable to count swap chain textures");
for (int i = 0; i < textureCount; ++i)
{
GLuint chainTexId;
ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, i, &chainTexId);
glBindTexture(GL_TEXTURE_2D, chainTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
glBindTexture(GL_TEXTURE_2D, 0);
/*
// Setup framebuffer object (using depth texture)
glGenFramebuffers(1, &buffer.fboId);
glGenTextures(1, &buffer.depthId);
glBindTexture(GL_TEXTURE_2D, buffer.depthId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, buffer.width, buffer.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
*/
// Setup framebuffer object (using depth renderbuffer)
glGenFramebuffers(1, &buffer.fboId);
glGenRenderbuffers(1, &buffer.depthId);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer.fboId);
glBindRenderbuffer(GL_RENDERBUFFER, buffer.depthId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, buffer.width, buffer.height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer.depthId);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
return buffer;
}
// Unload texture required buffers
static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer)
{
if (buffer.textureChain)
{
ovr_DestroyTextureSwapChain(session, buffer.textureChain);
buffer.textureChain = NULL;
}
if (buffer.depthId != 0) glDeleteTextures(1, &buffer.depthId);
if (buffer.fboId != 0) glDeleteFramebuffers(1, &buffer.fboId);
}
// Load Oculus mirror buffers
static OculusMirror LoadOculusMirror(ovrSession session, int width, int height)
{
OculusMirror mirror;
mirror.width = width;
mirror.height = height;
ovrMirrorTextureDesc mirrorDesc;
memset(&mirrorDesc, 0, sizeof(mirrorDesc));
mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
mirrorDesc.Width = mirror.width;
mirrorDesc.Height = mirror.height;
if (!OVR_SUCCESS(ovr_CreateMirrorTextureGL(session, &mirrorDesc, &mirror.texture))) TraceLog(WARNING, "Could not create mirror texture");
glGenFramebuffers(1, &mirror.fboId);
return mirror;
}
// Unload Oculus mirror buffers
static void UnloadOculusMirror(ovrSession session, OculusMirror mirror)
{
if (mirror.fboId != 0) glDeleteFramebuffers(1, &mirror.fboId);
if (mirror.texture) ovr_DestroyMirrorTexture(session, mirror.texture);
}
// Copy Oculus screen buffer to mirror texture
static void BlitOculusMirror(ovrSession session, OculusMirror mirror)
{
GLuint mirrorTextureId;
ovr_GetMirrorTextureBufferGL(session, mirror.texture, &mirrorTextureId);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mirror.fboId);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mirrorTextureId, 0);
#if defined(GRAPHICS_API_OPENGL_33)
// NOTE: glBlitFramebuffer() requires extension: GL_EXT_framebuffer_blit (not available in OpenGL ES 2.0)
glBlitFramebuffer(0, 0, mirror.width, mirror.height, 0, mirror.height, mirror.width, 0, GL_COLOR_BUFFER_BIT, GL_NEAREST);
#endif
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
// Init Oculus layer (similar to photoshop)
static OculusLayer InitOculusLayer(ovrSession session)
{
OculusLayer layer = { 0 };
layer.viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
memset(&layer.eyeLayer, 0, sizeof(ovrLayerEyeFov));
layer.eyeLayer.Header.Type = ovrLayerType_EyeFov;
layer.eyeLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft;
ovrEyeRenderDesc eyeRenderDescs[2];
for (int eye = 0; eye < 2; eye++)
{
eyeRenderDescs[eye] = ovr_GetRenderDesc(session, eye, hmdDesc.DefaultEyeFov[eye]);
ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(eyeRenderDescs[eye].Fov, 0.01f, 10000.0f, ovrProjection_None); //ovrProjection_ClipRangeOpenGL);
layer.eyeProjections[eye] = FromOvrMatrix(ovrPerspectiveProjection); // NOTE: struct ovrMatrix4f { float M[4][4] } --> struct Matrix
layer.viewScaleDesc.HmdToEyeOffset[eye] = eyeRenderDescs[eye].HmdToEyeOffset;
layer.eyeLayer.Fov[eye] = eyeRenderDescs[eye].Fov;
ovrSizei eyeSize = ovr_GetFovTextureSize(session, eye, layer.eyeLayer.Fov[eye], 1.0f);
layer.eyeLayer.Viewport[eye].Size = eyeSize;
layer.eyeLayer.Viewport[eye].Pos.x = layer.width;
layer.eyeLayer.Viewport[eye].Pos.y = 0;
layer.height = eyeSize.h; //std::max(renderTargetSize.y, (uint32_t)eyeSize.h);
layer.width += eyeSize.w;
}
return layer;
}
// Convert from Oculus ovrMatrix4f struct to raymath Matrix struct
static Matrix FromOvrMatrix(ovrMatrix4f ovrmat)
{
Matrix rmat;
rmat.m0 = ovrmat.M[0][0];
rmat.m1 = ovrmat.M[1][0];
rmat.m2 = ovrmat.M[2][0];
rmat.m3 = ovrmat.M[3][0];
rmat.m4 = ovrmat.M[0][1];
rmat.m5 = ovrmat.M[1][1];
rmat.m6 = ovrmat.M[2][1];
rmat.m7 = ovrmat.M[3][1];
rmat.m8 = ovrmat.M[0][2];
rmat.m9 = ovrmat.M[1][2];
rmat.m10 = ovrmat.M[2][2];
rmat.m11 = ovrmat.M[3][2];
rmat.m12 = ovrmat.M[0][3];
rmat.m13 = ovrmat.M[1][3];
rmat.m14 = ovrmat.M[2][3];
rmat.m15 = ovrmat.M[3][3];
MatrixTranspose(&rmat);
return rmat;
}
#endif
#if defined(RLGL_STANDALONE)
// Output a trace log message
// NOTE: Expected msgType: (0)Info, (1)Error, (2)Warning
void TraceLog(int msgType, const char *text, ...)
{
va_list args;
va_start(args, text);
switch (msgType)
{
case INFO: fprintf(stdout, "INFO: "); break;
case ERROR: fprintf(stdout, "ERROR: "); break;
case WARNING: fprintf(stdout, "WARNING: "); break;
case DEBUG: fprintf(stdout, "DEBUG: "); break;
default: break;
}
vfprintf(stdout, text, args);
fprintf(stdout, "\n");
va_end(args);
if (msgType == ERROR) exit(1);
}
// Converts Matrix to float array
// NOTE: Returned vector is a transposed version of the Matrix struct,
// it should be this way because, despite raymath use OpenGL column-major convention,
// Matrix struct memory alignment and variables naming are not coherent
float *MatrixToFloat(Matrix mat)
{
static float buffer[16];
buffer[0] = mat.m0;
buffer[1] = mat.m4;
buffer[2] = mat.m8;
buffer[3] = mat.m12;
buffer[4] = mat.m1;
buffer[5] = mat.m5;
buffer[6] = mat.m9;
buffer[7] = mat.m13;
buffer[8] = mat.m2;
buffer[9] = mat.m6;
buffer[10] = mat.m10;
buffer[11] = mat.m14;
buffer[12] = mat.m3;
buffer[13] = mat.m7;
buffer[14] = mat.m11;
buffer[15] = mat.m15;
return buffer;
}
#endif