From 096fd6f2c3d56214fecf17c4f60df79801dfbffe Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 16 Jul 2018 17:53:47 +0200 Subject: [PATCH] Converted rlgl module in header-only This change allows rlgl.h usage as independent single-file header-only module... still some tweaks required, like removing GLAD dependency... required extensions could be manually loaded! Also removed shader_distortion.h, embedded in rlgl.h --- examples/core/core_vr_simulator.c | 1 + release/include/raylib.h | 2 +- release/libs/win32/mingw32/libraylib.a | Bin 1243590 -> 1237446 bytes src/core.c | 2 + src/rlgl.c | 4231 ----------------------- src/rlgl.h | 4272 +++++++++++++++++++++++- src/shader_distortion.h | 106 - 7 files changed, 4272 insertions(+), 4342 deletions(-) delete mode 100644 src/rlgl.c delete mode 100644 src/shader_distortion.h diff --git a/examples/core/core_vr_simulator.c b/examples/core/core_vr_simulator.c index 35136114..3f59e839 100644 --- a/examples/core/core_vr_simulator.c +++ b/examples/core/core_vr_simulator.c @@ -31,6 +31,7 @@ int main() camera.target = (Vector3){ 0.0f, 2.0f, 0.0f }; // Camera looking at point camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) camera.fovy = 60.0f; // Camera field-of-view Y + camera.type = CAMERA_PERSPECTIVE; // Camera type Vector3 cubePosition = { 0.0f, 0.0f, 0.0f }; diff --git a/release/include/raylib.h b/release/include/raylib.h index da8713a9..bc8745c6 100644 --- a/release/include/raylib.h +++ b/release/include/raylib.h @@ -1008,7 +1008,7 @@ RLAPI int MeasureText(const char *text, int fontSize); RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font RLAPI const char *FormatText(const char *text, ...); // Formatting of text with variables to 'embed' RLAPI const char *SubText(const char *text, int position, int length); // Get a piece of a text string -RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on sprite font +RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on font //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) diff --git a/release/libs/win32/mingw32/libraylib.a b/release/libs/win32/mingw32/libraylib.a index 251f2685ab70956fba80cc0db8271ac658f0c587..c27c819e30eda1abd3ba8a2beaff6bb0cb6757d9 100644 GIT binary patch delta 150665 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

UD;w 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

!ljHKf{5J*dId7%GNZK&^!Q^WR3sUDPq^ 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

IaU=g%a&A=9k*zUvZ>(&!Bfu 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 delta 159168 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 diff --git a/src/core.c b/src/core.c index 45615f83..c578a807 100644 --- a/src/core.c +++ b/src/core.c @@ -95,7 +95,9 @@ #define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation of raymath here #include "raymath.h" // Required for: Vector3 and Matrix functions +#define RLGL_IMPLEMENTATION #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 + #include "utils.h" // Required for: fopen() Android mapping #if defined(SUPPORT_GESTURES_SYSTEM) diff --git a/src/rlgl.c b/src/rlgl.c deleted file mode 100644 index c806d35d..00000000 --- a/src/rlgl.c +++ /dev/null @@ -1,4231 +0,0 @@ -/********************************************************************************************** -* -* rlgl - raylib OpenGL abstraction layer -* -* rlgl is a wrapper for multiple OpenGL versions (1.1, 2.1, 3.3 Core, ES 2.0) to -* pseudo-OpenGL 1.1 style functions (rlVertex, rlTranslate, rlRotate...). -* -* When chosing an OpenGL version greater than OpenGL 1.1, rlgl stores vertex data on internal -* VBO buffers (and VAOs if available). It requires calling 3 functions: -* rlglInit() - Initialize internal buffers and auxiliar resources -* rlglDraw() - Process internal buffers and send required draw calls -* rlglClose() - De-initialize internal buffers data and other auxiliar resources -* -* CONFIGURATION: -* -* #define GRAPHICS_API_OPENGL_11 -* #define GRAPHICS_API_OPENGL_21 -* #define GRAPHICS_API_OPENGL_33 -* #define GRAPHICS_API_OPENGL_ES2 -* Use selected OpenGL backend -* -* #define RLGL_STANDALONE -* Use rlgl as standalone library (no raylib dependency) -* -* #define SUPPORT_VR_SIMULATOR -* Support VR simulation functionality (stereo rendering) -* -* #define SUPPORT_DISTORTION_SHADER -* Include stereo rendering distortion shader (shader_distortion.h) -* -* DEPENDENCIES: -* raymath - 3D math functionality (Vector3, Matrix, Quaternion) -* GLAD - OpenGL extensions loading (OpenGL 3.3 Core only) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2018 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 "config.h" - -#include "rlgl.h" - -#include // Required for: fopen(), fclose(), fread()... [Used only on LoadText()] -#include // Required for: malloc(), free(), rand() -#include // Required for: strcmp(), strlen(), strtok() [Used only in extensions loading] -#include // Required for: atan2() - -#if !defined(RLGL_STANDALONE) - #include "raymath.h" // Required for: Vector3 and Matrix functions -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(__APPLE__) - #include // OpenGL 1.1 library for OSX - #include - #else - // APIENTRY for OpenGL function pointer declarations is required - #ifndef APIENTRY - #ifdef _WIN32 - #define APIENTRY __stdcall - #else - #define APIENTRY - #endif - #endif - // WINGDIAPI definition. Some Windows OpenGL headers need it - #if !defined(WINGDIAPI) && defined(_WIN32) - #define WINGDIAPI __declspec(dllimport) - #endif - - #include // OpenGL 1.1 library - #endif -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - #define GRAPHICS_API_OPENGL_33 // OpenGL 2.1 uses mostly OpenGL 3.3 Core functionality -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - #if defined(__APPLE__) - #include // OpenGL 3 library for OSX - #include // OpenGL 3 extensions 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 library - #include // OpenGL ES 2.0 library - #include // OpenGL ES 2.0 extensions library -#endif - -#if defined(RLGL_STANDALONE) - #include // Required for: va_list, va_start(), vfprintf(), va_end() [Used only on TraceLog()] -#endif - -#if !defined(GRAPHICS_API_OPENGL_11) && defined(SUPPORT_DISTORTION_SHADER) - #include "shader_distortion.h" // Distortion shader to be embedded -#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) - -#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 - -#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF -#endif - -#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE -#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_21) - #define GL_LUMINANCE 0x1909 - #define GL_LUMINANCE_ALPHA 0x190A -#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 DynamicBuffer { - 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 DrawCall { - int vertexCount; - GLuint vaoId; - GLuint textureId; - GLuint shaderId; - - Matrix projection; - Matrix modelview; - - // TODO: Store additional draw state data - //int blendMode; - //Guint fboId; -} DrawCall; - -#if defined(SUPPORT_VR_SIMULATOR) -// VR Stereo rendering configuration for simulator -typedef struct VrStereoConfig { - 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; -#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 int currentDrawMode; - -static float currentDepth = -1.0f; - -static DynamicBuffer lines; // Default dynamic buffer for lines data -static DynamicBuffer triangles; // Default dynamic buffer for triangles data -static DynamicBuffer quads; // Default dynamic buffer for quads data (used to draw textures) - -// 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; - -// Shaders -static unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) -static unsigned int defaultFShaderId; // Default fragment shader Id (used by default shader program) - -static Shader defaultShader; // Basic shader, support vertex color and diffuse texture -static Shader currentShader; // Shader to be used on rendering (by default, defaultShader) - -// Extension supported flag: VAO -static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension) - -// Extension supported flag: Compressed textures -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 - -#if defined(SUPPORT_VR_SIMULATOR) -// VR global variables -static VrStereoConfig vrConfig; // VR stereo configuration for simulator -static bool vrSimulatorReady = false; // VR simulator ready flag -static bool vrStereoRender = false; // VR stereo rendering enabled/disabled flag - // NOTE: This flag is useful to render data over stereo image (i.e. FPS) -#endif // defined(SUPPORT_VR_SIMULATOR) - -#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - -// Extension supported flag: Anisotropic filtering -static bool texAnisotropicFilterSupported = false; // Anisotropic texture filtering support -static float maxAnisotropicLevel = 0.0f; // Maximum anisotropy level supported (minimum is 2.0f) - -// Extension supported flag: Clamp mirror wrap mode -static bool texClampMirrorSupported = false; // Clamp mirror wrap mode supported - -#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 - -static bool debugMarkerSupported = false; - -// Compressed textures support flags -static bool texCompDXTSupported = false; // DDS texture compression support -static bool texNPOTSupported = false; // NPOT textures full support -static bool texFloatSupported = false; // float textures support (32 bit per channel) - -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 -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 unsigned int CompileShader(const char *shaderStr, int type); // Compile custom shader and return shader id -static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program - -static Shader LoadShaderDefault(void); // Load default shader (just vertex positioning and texture coloring) -static void SetShaderDefaultLocations(Shader *shader); // Bind default shader locations (attributes and uniforms) -static void UnloadShaderDefault(void); // Unload default shader - -static void LoadBuffersDefault(void); // Load default internal buffers (lines, triangles, quads) -static void UpdateBuffersDefault(void); // Update default internal buffers (VAOs/VBOs) with vertex data -static void DrawBuffersDefault(void); // Draw default internal buffers vertex data -static void UnloadBuffersDefault(void); // Unload default internal buffers vertex data from CPU and GPU - -static void GenDrawCube(void); // Generate and draw cube -static void GenDrawQuad(void); // Generate and draw quad - -#if defined(SUPPORT_VR_SIMULATOR) -static void SetStereoConfig(VrDeviceInfo info); // Configure stereo rendering (including distortion shader) with HMD device parameters -static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView); // Set internal projection and modelview matrix depending on eye -#endif - -#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - -// Get OpenGL internal formats and data type from raylib PixelFormat -static void GetGlFormats(int format, int *glInternalFormat, int *glFormat, int *glType); - -#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 - -//---------------------------------------------------------------------------------- -// 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 zNear, double zFar) -{ - glFrustum(left, right, bottom, top, zNear, zFar); -} - -void rlOrtho(double left, double right, double bottom, double top, double zNear, double zFar) -{ - glOrtho(left, right, bottom, top, zNear, zFar); -} - -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 *matf) { glMultMatrixf(matf); } - -#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(LOG_ERROR, "Stack Buffer Overflow (MAX %i Matrix)", MATRIX_STACK_SIZE); - } - - stack[stackCounter] = *currentMatrix; - rlLoadIdentity(); // TODO: Review matrix stack logic! - 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); - - // NOTE: We transpose matrix with multiplication order - *currentMatrix = MatrixMultiply(matTranslation, *currentMatrix); -} - -// 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 }; - matRotation = MatrixRotate(Vector3Normalize(axis), angleDeg*DEG2RAD); - - // NOTE: We transpose matrix with multiplication order - *currentMatrix = MatrixMultiply(matRotation, *currentMatrix); -} - -// Multiply the current matrix by a scaling matrix -void rlScalef(float x, float y, float z) -{ - Matrix matScale = MatrixScale(x, y, z); - - // NOTE: We transpose matrix with multiplication order - *currentMatrix = MatrixMultiply(matScale, *currentMatrix); -} - -// Multiply the current matrix by another matrix -void rlMultMatrixf(float *matf) -{ - // Matrix creation from array - Matrix mat = { matf[0], matf[4], matf[8], matf[12], - matf[1], matf[5], matf[9], matf[13], - matf[2], matf[6], matf[10], matf[14], - matf[3], matf[7], matf[11], matf[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); - - *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); - - *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++) tempBuffer[i] = Vector3Transform(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); - - // Verify internal buffers limits - // NOTE: This check is combined with usage of rlCheckBufferLimit() - if ((lines.vCounter/2 >= MAX_LINES_BATCH - 2) || - (triangles.vCounter/3 >= MAX_TRIANGLES_BATCH - 3) || - (quads.vCounter/4 >= MAX_QUADS_BATCH - 4)) rlglDraw(); -} - -// 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(LOG_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(LOG_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(LOG_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++; - - if (drawsCounter >= MAX_DRAWS_BY_TEXTURE) - { - rlglDraw(); - drawsCounter = 1; - } - - 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); -#else - // NOTE: If quads batch limit is reached, - // we force a draw call and next batch starts - if (quads.vCounter/4 >= MAX_QUADS_BATCH) rlglDraw(); -#endif -} - -// Set texture parameters (wrap mode/filter mode) -void rlTextureParameters(unsigned int id, int param, int value) -{ - glBindTexture(GL_TEXTURE_2D, id); - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if ((value == RL_WRAP_CLAMP_MIRROR) && !texClampMirrorSupported) TraceLog(LOG_WARNING, "Clamp mirror wrap mode not supported"); - else glTexParameteri(GL_TEXTURE_2D, param, value); - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; - case RL_TEXTURE_ANISOTROPIC_FILTER: - { - if (value <= maxAnisotropicLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (maxAnisotropicLevel > 0.0f) - { - TraceLog(LOG_WARNING, "[TEX ID %i] Maximum anisotropic filter level supported is %iX", id, maxAnisotropicLevel); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TraceLog(LOG_WARNING, "Anisotropic filtering not supported"); - } break; - default: break; - } - - glBindTexture(GL_TEXTURE_2D, 0); -} - -// 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(LOG_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(LOG_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(LOG_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... -} - -//---------------------------------------------------------------------------------- -// 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(LOG_INFO, "GPU: Vendor: %s", glGetString(GL_VENDOR)); - TraceLog(LOG_INFO, "GPU: Renderer: %s", glGetString(GL_RENDERER)); - TraceLog(LOG_INFO, "GPU: Version: %s", glGetString(GL_VERSION)); - TraceLog(LOG_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(LOG_INFO, "GL_MAX_TEXTURE_SIZE: %i", maxTexSize); - - //GL_MAX_TEXTURE_IMAGE_UNITS - //GL_MAX_VIEWPORT_DIMS - - //int numAuxBuffers; - //glGetIntegerv(GL_AUX_BUFFERS, &numAuxBuffers); - //TraceLog(LOG_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(LOG_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(LOG_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; - texNPOTSupported = true; - texFloatSupported = 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); - -#ifdef _MSC_VER - const char **extList = malloc(sizeof(const char *)*numExt); -#else - const char *extList[numExt]; -#endif - - 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(LOG_INFO, "Number of supported extensions: %i", numExt); - - // Show supported extensions - //for (int i = 0; i < numExt; i++) TraceLog(LOG_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) texNPOTSupported = true; - - // Check texture float support - if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) texFloatSupported = 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; - - // Anisotropic texture filter support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) - { - texAnisotropicFilterSupported = true; - glGetFloatv(0x84FF, &maxAnisotropicLevel); // GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - } - - // Clamp mirror wrap mode supported - if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) texClampMirrorSupported = true; - - // Debug marker support - if(strcmp(extList[i], (const char *)"GL_EXT_debug_marker") == 0) debugMarkerSupported = true; - } - -#if defined(_MSC_VER) - //free(extList); -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - if (vaoSupported) TraceLog(LOG_INFO, "[EXTENSION] VAO extension detected, VAO functions initialized successfully"); - else TraceLog(LOG_WARNING, "[EXTENSION] VAO extension not found, VAO usage not supported"); - - if (texNPOTSupported) TraceLog(LOG_INFO, "[EXTENSION] NPOT textures extension detected, full NPOT textures supported"); - else TraceLog(LOG_WARNING, "[EXTENSION] NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); -#endif - - if (texCompDXTSupported) TraceLog(LOG_INFO, "[EXTENSION] DXT compressed textures supported"); - if (texCompETC1Supported) TraceLog(LOG_INFO, "[EXTENSION] ETC1 compressed textures supported"); - if (texCompETC2Supported) TraceLog(LOG_INFO, "[EXTENSION] ETC2/EAC compressed textures supported"); - if (texCompPVRTSupported) TraceLog(LOG_INFO, "[EXTENSION] PVRT compressed textures supported"); - if (texCompASTCSupported) TraceLog(LOG_INFO, "[EXTENSION] ASTC compressed textures supported"); - - if (texAnisotropicFilterSupported) TraceLog(LOG_INFO, "[EXTENSION] Anisotropic textures filtering supported (max: %.0fX)", maxAnisotropicLevel); - if (texClampMirrorSupported) TraceLog(LOG_INFO, "[EXTENSION] Clamp mirror wrap texture mode supported"); - - if (debugMarkerSupported) TraceLog(LOG_INFO, "[EXTENSION] Debug Marker 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 = rlLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1); - - if (whiteTexture != 0) TraceLog(LOG_INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture); - else TraceLog(LOG_WARNING, "Base white texture could not be loaded"); - - // Init default Shader (customized for GL 3.3 and ES2) - defaultShader = LoadShaderDefault(); - currentShader = defaultShader; - - // Init default vertex arrays buffers (lines, triangles, quads) - LoadBuffersDefault(); - - // 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] = Vector3Zero(); - - // 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(LOG_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) - UnloadShaderDefault(); // Unload default shader - UnloadBuffersDefault(); // Unload default buffers (lines, triangles, quads) - glDeleteTextures(1, &whiteTexture); // Unload default texture - - TraceLog(LOG_INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", whiteTexture); - - 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++) rlDrawMesh(models[i]->mesh, models[i]->material, models[i]->transform); - - // NOTE: Default buffers upload and draw - UpdateBuffersDefault(); - DrawBuffersDefault(); // NOTE: Stereo rendering is checked inside -#endif -} - -// Returns current OpenGL version -int rlGetVersion(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - return OPENGL_11; -#elif defined(GRAPHICS_API_OPENGL_21) - #if defined(__APPLE__) - return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX - #else - return OPENGL_21; - #endif -#elif defined(GRAPHICS_API_OPENGL_33) - return OPENGL_33; -#elif defined(GRAPHICS_API_OPENGL_ES2) - return OPENGL_ES_20; -#endif -} - -// Check internal buffer overflow for a given number of vertex -bool rlCheckBufferLimit(int type, int vCount) -{ - bool overflow = false; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (type) - { - case RL_LINES: overflow = ((lines.vCounter + vCount)/2 >= MAX_LINES_BATCH); break; - case RL_TRIANGLES: overflow = ((triangles.vCounter + vCount)/3 >= MAX_TRIANGLES_BATCH); break; - case RL_QUADS: overflow = ((quads.vCounter + vCount)/4 >= MAX_QUADS_BATCH); break; - default: break; - } -#endif - return overflow; -} - -// Set debug marker -void rlSetDebugMarker(const char *text) -{ -#if defined(GRAPHICS_API_OPENGL_33) - if (debugMarkerSupported) glInsertEventMarkerEXT(0, text); -#endif -} - -// Load OpenGL extensions -// NOTE: External loader function could be passed as a pointer -void rlLoadExtensions(void *loader) -{ -#if defined(GRAPHICS_API_OPENGL_33) - // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) - #if !defined(__APPLE__) - if (!gladLoadGLLoader((GLADloadproc)loader)) TraceLog(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); - else TraceLog(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); - - #if defined(GRAPHICS_API_OPENGL_21) - if (GLAD_GL_VERSION_2_1) TraceLog(LOG_INFO, "OpenGL 2.1 profile supported"); - #elif defined(GRAPHICS_API_OPENGL_33) - if(GLAD_GL_VERSION_3_3) TraceLog(LOG_INFO, "OpenGL 3.3 Core profile supported"); - else TraceLog(LOG_ERROR, "OpenGL 3.3 Core profile not supported"); - #endif - #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 rlUnproject(Vector3 source, Matrix proj, Matrix view) -{ - Vector3 result = { 0.0f, 0.0f, 0.0f }; - - // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it - Matrix matViewProj = MatrixMultiply(view, proj); - matViewProj = MatrixInvert(matViewProj); - - // Create quaternion from source point - Quaternion quat = { source.x, source.y, source.z, 1.0f }; - - // Multiply quat point by unproject matrix - quat = QuaternionTransform(quat, matViewProj); - - // 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 rlLoadTexture(void *data, int width, int height, int format, 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 (format >= COMPRESSED_DXT1_RGB) - { - TraceLog(LOG_WARNING, "OpenGL 1.1 does not support GPU compressed texture formats"); - return id; - } -#endif - - if ((!texCompDXTSupported) && ((format == COMPRESSED_DXT1_RGB) || (format == COMPRESSED_DXT1_RGBA) || - (format == COMPRESSED_DXT3_RGBA) || (format == COMPRESSED_DXT5_RGBA))) - { - TraceLog(LOG_WARNING, "DXT compressed texture format not supported"); - return id; - } -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((!texCompETC1Supported) && (format == COMPRESSED_ETC1_RGB)) - { - TraceLog(LOG_WARNING, "ETC1 compressed texture format not supported"); - return id; - } - - if ((!texCompETC2Supported) && ((format == COMPRESSED_ETC2_RGB) || (format == COMPRESSED_ETC2_EAC_RGBA))) - { - TraceLog(LOG_WARNING, "ETC2 compressed texture format not supported"); - return id; - } - - if ((!texCompPVRTSupported) && ((format == COMPRESSED_PVRT_RGB) || (format == COMPRESSED_PVRT_RGBA))) - { - TraceLog(LOG_WARNING, "PVRT compressed texture format not supported"); - return id; - } - - if ((!texCompASTCSupported) && ((format == COMPRESSED_ASTC_4x4_RGBA) || (format == COMPRESSED_ASTC_8x8_RGBA))) - { - TraceLog(LOG_WARNING, "ASTC compressed texture format not supported"); - return id; - } -#endif - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - 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); - - int mipWidth = width; - int mipHeight = height; - int mipOffset = 0; // Mipmap data offset - - TraceLog(LOG_DEBUG, "Load texture from data memory address: 0x%x", data); - - // Load the different mipmap levels - for (int i = 0; i < mipmapCount; i++) - { - unsigned int mipSize = GetPixelDataSize(mipWidth, mipHeight, format); - - int glInternalFormat, glFormat, glType; - GetGlFormats(format, &glInternalFormat, &glFormat, &glType); - - TraceLog(LOG_DEBUG, "Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); - - if (glInternalFormat != -1) - { - if (format < COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); - #if !defined(GRAPHICS_API_OPENGL_11) - else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, (unsigned char *)data + mipOffset); - #endif - - #if defined(GRAPHICS_API_OPENGL_33) - if (format == UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == UNCOMPRESSED_GRAY_ALPHA) - { - #if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; - #elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; - #endif - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - #endif - } - - mipWidth /= 2; - mipHeight /= 2; - mipOffset += mipSize; - - // Security check for NPOT textures - if (mipWidth < 1) mipWidth = 1; - if (mipHeight < 1) mipHeight = 1; - } - - // 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 (texNPOTSupported) - { - 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) - { - // Activate Trilinear filtering if mipmaps are available - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } -#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(LOG_INFO, "[TEX ID %i] Texture created successfully (%ix%i - %i mipmaps)", id, width, height, mipmapCount); - else TraceLog(LOG_WARNING, "Texture could not be created"); - - return id; -} - -// Update already loaded texture in GPU with new data -void rlUpdateTexture(unsigned int id, int width, int height, int format, const void *data) -{ - glBindTexture(GL_TEXTURE_2D, id); - - int glInternalFormat, glFormat, glType; - GetGlFormats(format, &glInternalFormat, &glFormat, &glType); - - if ((glInternalFormat != -1) && (format < COMPRESSED_DXT1_RGB)) - { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, glFormat, glType, (unsigned char *)data); - } - else TraceLog(LOG_WARNING, "Texture format updating not supported"); -} - -// Unload texture from GPU memory -void rlUnloadTexture(unsigned int id) -{ - if (id > 0) glDeleteTextures(1, &id); -} - - -// Load a texture to be used for rendering (fbo with color and depth attachments) -RenderTexture2D rlLoadRenderTexture(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_R8G8B8A8; - 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_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - -#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) - #define USE_DEPTH_RENDERBUFFER -#else - #define USE_DEPTH_TEXTURE -#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(LOG_WARNING, "Framebuffer object could not be created..."); - - switch (status) - { - case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(LOG_WARNING, "Framebuffer is unsupported"); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer incomplete attachment"); break; -#if defined(GRAPHICS_API_OPENGL_ES2) - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(LOG_WARNING, "Framebuffer incomplete dimensions"); break; -#endif - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer incomplete missing attachment"); break; - default: break; - } - - glDeleteTextures(1, &target.texture.id); - glDeleteTextures(1, &target.depth.id); - glDeleteFramebuffers(1, &target.id); - } - else TraceLog(LOG_INFO, "[FBO ID %i] Framebuffer object created successfully", target.id); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif - - return target; -} - -// Generate mipmap data for selected texture -void rlGenerateMipmaps(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) || (texNPOTSupported)) - { -#if defined(GRAPHICS_API_OPENGL_11) - // WARNING: Manual mipmap generation only works for RGBA 32bit textures! - if (texture->format == UNCOMPRESSED_R8G8B8A8) - { - // Retrieve texture data from VRAM - void *data = rlReadTexturePixels(*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; - 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, (unsigned char *)data + offset); - - size = mipWidth*mipHeight*4; - offset += size; - - mipWidth /= 2; - mipHeight /= 2; - } - - texture->mipmaps = mipmapCount + 1; - free(data); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data - - TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps [%i] generated manually on CPU side", texture->id, texture->mipmaps); - } - else TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps could not be generated for texture format", texture->id); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE - glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically - TraceLog(LOG_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 - - #define MIN(a,b) (((a)<(b))?(a):(b)) - #define MAX(a,b) (((a)>(b))?(a):(b)) - - texture->mipmaps = 1 + (int)floor(log(MAX(texture->width, texture->height))/log(2)); -#endif - } - else TraceLog(LOG_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 -// TODO: Check if mesh has already been loaded in GPU -void rlLoadMesh(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; - - if (vaoSupported) - { - // Initialize Quads VAO (Buffer A) - glGenVertexArrays(1, &mesh->vaoId); - glBindVertexArray(mesh->vaoId); - } - - // NOTE: Attributes must be uploaded considering default locations points - - // Enable vertex attributes: position (shader-location = 0) - glGenBuffers(1, &mesh->vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[3]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[4]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[4]); - glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*mesh->vertexCount, mesh->tangents, drawHint); - glVertexAttribPointer(4, 4, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(4); - } - else - { - // Default tangents vertex attribute - glVertexAttrib4f(4, 0.0f, 0.0f, 0.0f, 0.0f); - glDisableVertexAttribArray(4); - } - - // Default texcoord2 vertex attribute (shader-location = 5) - if (mesh->texcoords2 != NULL) - { - glGenBuffers(1, &mesh->vboId[5]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->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 texcoord2 vertex attribute - glVertexAttrib2f(5, 0.0f, 0.0f); - glDisableVertexAttribArray(5); - } - - if (mesh->indices != NULL) - { - glGenBuffers(1, &mesh->vboId[6]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->vboId[6]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangleCount*3, mesh->indices, GL_STATIC_DRAW); - } - - if (vaoSupported) - { - if (mesh->vaoId > 0) TraceLog(LOG_INFO, "[VAO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); - else TraceLog(LOG_WARNING, "Mesh could not be uploaded to VRAM (GPU)"); - } - else - { - TraceLog(LOG_INFO, "[VBOs] Mesh uploaded successfully to VRAM (GPU)"); - } -#endif -} - -// Update vertex data on GPU (upload new data to one buffer) -void rlUpdateMesh(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[2]); - 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[3]); - 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[4]); - if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*numVertex, mesh.tangents, GL_DYNAMIC_DRAW); - else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*4*numVertex, mesh.tangents); - } break; - case 5: // Update texcoords2 (vertex second texture coordinates) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); - 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 rlDrawMesh(Mesh mesh, Material material, Matrix transform) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, material.maps[MAP_DIFFUSE].texture.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.maps[MAP_DIFFUSE].color.r, material.maps[MAP_DIFFUSE].color.g, material.maps[MAP_DIFFUSE].color.b, material.maps[MAP_DIFFUSE].color.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) - // Bind shader program - glUseProgram(material.shader.id); - - // Matrices and other values required by shader - //----------------------------------------------------- - // Calculate and send to shader model matrix (used by PBR shader) - if (material.shader.locs[LOC_MATRIX_MODEL] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_MODEL], transform); - - // Upload to shader material.colDiffuse - if (material.shader.locs[LOC_COLOR_DIFFUSE] != -1) - glUniform4f(material.shader.locs[LOC_COLOR_DIFFUSE], (float)material.maps[MAP_DIFFUSE].color.r/255.0f, - (float)material.maps[MAP_DIFFUSE].color.g/255.0f, - (float)material.maps[MAP_DIFFUSE].color.b/255.0f, - (float)material.maps[MAP_DIFFUSE].color.a/255.0f); - - // Upload to shader material.colSpecular (if available) - if (material.shader.locs[LOC_COLOR_SPECULAR] != -1) - glUniform4f(material.shader.locs[LOC_COLOR_SPECULAR], (float)material.maps[MAP_SPECULAR].color.r/255.0f, - (float)material.maps[MAP_SPECULAR].color.g/255.0f, - (float)material.maps[MAP_SPECULAR].color.b/255.0f, - (float)material.maps[MAP_SPECULAR].color.a/255.0f); - - if (material.shader.locs[LOC_MATRIX_VIEW] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_VIEW], modelview); - if (material.shader.locs[LOC_MATRIX_PROJECTION] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_PROJECTION], projection); - - // At this point the modelview matrix just contains the view matrix (camera) - // That's because BeginMode3D() 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 - //----------------------------------------------------- - - // Bind active texture maps (if available) - for (int i = 0; i < MAX_MATERIAL_MAPS; i++) - { - if (material.maps[i].texture.id > 0) - { - glActiveTexture(GL_TEXTURE0 + i); - if ((i == MAP_IRRADIANCE) || (i == MAP_PREFILTER) || (i == MAP_CUBEMAP)) glBindTexture(GL_TEXTURE_CUBE_MAP, material.maps[i].texture.id); - else glBindTexture(GL_TEXTURE_2D, material.maps[i].texture.id); - - glUniform1i(material.shader.locs[LOC_MAP_DIFFUSE + i], i); - } - } - - // Bind vertex array objects (or VBOs) - if (vaoSupported) glBindVertexArray(mesh.vaoId); - else - { - // TODO: Simplify VBO binding into a for loop - - // Bind mesh VBO data: vertex position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_POSITION]); - - // Bind mesh VBO data: vertex texcoords (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TEXCOORD01]); - - // Bind mesh VBO data: vertex normals (shader-location = 2, if available) - if (material.shader.locs[LOC_VERTEX_NORMAL] != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_NORMAL]); - } - - // Bind mesh VBO data: vertex colors (shader-location = 3, if available) - if (material.shader.locs[LOC_VERTEX_COLOR] != -1) - { - if (mesh.vboId[3] != 0) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_COLOR]); - } - else - { - // Set default value for unused attribute - // NOTE: Required when using default shader and no VAO support - glVertexAttrib4f(material.shader.locs[LOC_VERTEX_COLOR], 1.0f, 1.0f, 1.0f, 1.0f); - glDisableVertexAttribArray(material.shader.locs[LOC_VERTEX_COLOR]); - } - } - - // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) - if (material.shader.locs[LOC_VERTEX_TANGENT] != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TANGENT], 4, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TANGENT]); - } - - // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) - if (material.shader.locs[LOC_VERTEX_TEXCOORD02] != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TEXCOORD02], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TEXCOORD02]); - } - - if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.vboId[6]); - } - - int eyesCount = 1; -#if defined(SUPPORT_VR_SIMULATOR) - if (vrStereoRender) eyesCount = 2; -#endif - - for (int eye = 0; eye < eyesCount; eye++) - { - if (eyesCount == 1) modelview = matModelView; - #if defined(SUPPORT_VR_SIMULATOR) - else SetStereoView(eye, matProjection, matModelView); - #endif - - // 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.locs[LOC_MATRIX_MVP], 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); - } - - // Unbind all binded texture maps - for (int i = 0; i < MAX_MATERIAL_MAPS; i++) - { - glActiveTexture(GL_TEXTURE0 + i); // Set shader active texture - if ((i == MAP_IRRADIANCE) || (i == MAP_PREFILTER) || (i == MAP_CUBEMAP)) glBindTexture(GL_TEXTURE_CUBE_MAP, 0); - else glBindTexture(GL_TEXTURE_2D, 0); // Unbind current active texture - } - - // Unind vertex array objects (or VBOs) - if (vaoSupported) glBindVertexArray(0); - else - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - // Unbind shader program - glUseProgram(0); - - // Restore projection/modelview matrices - // NOTE: In stereo rendering matrices are being modified to fit every eye - projection = matProjection; - modelview = matView; -#endif -} - -// Unload mesh data from CPU and GPU -void rlUnloadMesh(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 *rlReadScreenPixels(int width, int height) -{ - unsigned char *screenData = (unsigned char *)calloc(width*height*4, sizeof(unsigned char)); - - // 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 *rlReadTexturePixels(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 - */ - - // 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); - - int glInternalFormat, glFormat, glType; - GetGlFormats(texture.format, &glInternalFormat, &glFormat, &glType); - unsigned int size = GetPixelDataSize(texture.width, texture.height, texture.format); - - if ((glInternalFormat != -1) && (texture.format < COMPRESSED_DXT1_RGB)) - { - pixels = (unsigned char *)malloc(size); - glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); - } - else TraceLog(LOG_WARNING, "Texture data retrieval not suported for pixel format"); - - glBindTexture(GL_TEXTURE_2D, 0); -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - RenderTexture2D fbo = rlLoadRenderTexture(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 RGBA - // 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: We read data as RGBA because FBO texture is configured as RGBA, despite binding a RGB texture... - 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); - //glDisable(GL_TEXTURE_2D); - glEnable(GL_DEPTH_TEST); - //glDisable(GL_BLEND); - - glViewport(0, 0, texture.width, texture.height); - rlOrtho(0.0, texture.width, texture.height, 0.0, 0.0, 1.0); - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glUseProgram(GetShaderDefault().id); - glBindTexture(GL_TEXTURE_2D, texture.id); - GenDrawQuad(); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); - - pixels = (unsigned char *)malloc(texture.width*texture.height*4*sizeof(unsigned char)); - - glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Bind framebuffer 0, which means render to back buffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); - -#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 rlRecordDraw(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 GetTextureDefault(void) -{ - Texture2D texture; - - texture.id = whiteTexture; - texture.width = 1; - texture.height = 1; - texture.mipmaps = 1; - texture.format = UNCOMPRESSED_R8G8B8A8; - - return texture; -} - -// Get default shader -Shader GetShaderDefault(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - return defaultShader; -#else - Shader shader = { 0 }; - return shader; -#endif -} - -// Load text data from file -// NOTE: text chars array should be freed manually -char *LoadText(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(LOG_WARNING, "[%s] Text file could not be opened", fileName); - } - - return text; -} - -// Load shader from files and bind default locations -// NOTE: If shader string is NULL, using default vertex/fragment shaders -Shader LoadShader(const char *vsFileName, const char *fsFileName) -{ - Shader shader = { 0 }; - - char *vShaderStr = NULL; - char *fShaderStr = NULL; - - if (vsFileName != NULL) vShaderStr = LoadText(vsFileName); - if (fsFileName != NULL) fShaderStr = LoadText(fsFileName); - - shader = LoadShaderCode(vShaderStr, fShaderStr); - - if (vShaderStr != NULL) free(vShaderStr); - if (fShaderStr != NULL) free(fShaderStr); - - return shader; -} - -// Load shader from code strings -// NOTE: If shader string is NULL, using default vertex/fragment shaders -Shader LoadShaderCode(char *vsCode, char *fsCode) -{ - Shader shader = { 0 }; - - // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int vertexShaderId = defaultVShaderId; - unsigned int fragmentShaderId = defaultFShaderId; - - if (vsCode != NULL) vertexShaderId = CompileShader(vsCode, GL_VERTEX_SHADER); - if (fsCode != NULL) fragmentShaderId = CompileShader(fsCode, GL_FRAGMENT_SHADER); - - if ((vertexShaderId == defaultVShaderId) && (fragmentShaderId == defaultFShaderId)) shader = defaultShader; - else - { - shader.id = LoadShaderProgram(vertexShaderId, fragmentShaderId); - - if (vertexShaderId != defaultVShaderId) glDeleteShader(vertexShaderId); - if (fragmentShaderId != defaultFShaderId) glDeleteShader(fragmentShaderId); - - if (shader.id == 0) - { - TraceLog(LOG_WARNING, "Custom shader could not be loaded"); - shader = defaultShader; - } - - // After shader loading, we TRY to set default location names - if (shader.id > 0) SetShaderDefaultLocations(&shader); - } - - // Get available shader uniforms - // NOTE: This information is useful for debug... - int uniformCount = -1; - - glGetProgramiv(shader.id, GL_ACTIVE_UNIFORMS, &uniformCount); - - for(int i = 0; i < uniformCount; i++) - { - int namelen = -1; - int num = -1; - char name[256]; // Assume no variable names longer than 256 - GLenum type = GL_ZERO; - - // Get the name of the uniforms - glGetActiveUniform(shader.id, i,sizeof(name) - 1, &namelen, &num, &type, name); - - name[namelen] = 0; - - // Get the location of the named uniform - GLuint location = glGetUniformLocation(shader.id, name); - - TraceLog(LOG_DEBUG, "[SHDR ID %i] Active uniform [%s] set at location: %i", shader.id, name, location); - } -#endif - - return shader; -} - -// Unload shader from GPU memory (VRAM) -void UnloadShader(Shader shader) -{ - if (shader.id > 0) - { - rlDeleteShader(shader.id); - TraceLog(LOG_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 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(LOG_WARNING, "[SHDR ID %i] Shader uniform [%s] COULD NOT BE FOUND", shader.id, uniformName); - else TraceLog(LOG_INFO, "[SHDR ID %i] Shader uniform [%s] set at location: %i", shader.id, uniformName, location); -#endif - return location; -} - -// Set shader uniform value (float) -void SetShaderValue(Shader shader, int uniformLoc, const 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(LOG_WARNING, "Shader value float array size not supported"); - - //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set -#endif -} - -// Set shader uniform value (int) -void SetShaderValuei(Shader shader, int uniformLoc, const 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(LOG_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 -} - -// Return internal modelview matrix -Matrix GetMatrixModelview() -{ - Matrix matrix = MatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, mat); -#else - matrix = modelview; -#endif - return matrix; -} - -// Generate cubemap texture from HDR texture -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) -{ - Texture2D cubemap = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader - // Other locations should be setup externally in shader before calling the function - - // Set up depth face culling and cubemap seamless - glDisable(GL_CULL_FACE); -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Flag not supported on OpenGL ES 2.0 -#endif - - - // Setup framebuffer - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); - - // Set up cubemap to render and attach to framebuffer - // NOTE: faces are stored with 16 bit floating point values - glGenTextures(1, &cubemap.id); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - for (unsigned int i = 0; i < 6; i++) - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if defined(GRAPHICS_API_OPENGL_33) - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 -#endif - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Create projection (transposed) and different views for each face - Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); - //MatrixTranspose(&fboProjection); - Matrix fboViews[6] = { - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) - }; - - // Convert HDR equirectangular environment map to cubemap equivalent - glUseProgram(shader.id); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, skyHDR.id); - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); - - // Note: don't forget to configure the viewport to the capture dimensions - glViewport(0, 0, size, size); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - for (unsigned int i = 0; i < 6; i++) - { - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap.id, 0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawCube(); - } - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); - //glEnable(GL_CULL_FACE); - - cubemap.width = size; - cubemap.height = size; -#endif - return cubemap; -} - -// Generate irradiance texture using cubemap data -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) -{ - Texture2D irradiance = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader - // Other locations should be setup externally in shader before calling the function - - // Setup framebuffer - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); - - // Create an irradiance cubemap, and re-scale capture FBO to irradiance scale - glGenTextures(1, &irradiance.id); - glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance.id); - for (unsigned int i = 0; i < 6; i++) - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Create projection (transposed) and different views for each face - Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); - //MatrixTranspose(&fboProjection); - Matrix fboViews[6] = { - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) - }; - - // Solve diffuse integral by convolution to create an irradiance cubemap - glUseProgram(shader.id); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); - - // Note: don't forget to configure the viewport to the capture dimensions - glViewport(0, 0, size, size); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - for (unsigned int i = 0; i < 6; i++) - { - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradiance.id, 0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawCube(); - } - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); - - irradiance.width = size; - irradiance.height = size; -#endif - return irradiance; -} - -// Generate prefilter texture using cubemap data -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) -{ - Texture2D prefilter = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader - // Other locations should be setup externally in shader before calling the function - // TODO: Locations should be taken out of this function... too shader dependant... - int roughnessLoc = GetShaderLocation(shader, "roughness"); - - // Setup framebuffer - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); - - // Create a prefiltered HDR environment map - glGenTextures(1, &prefilter.id); - glBindTexture(GL_TEXTURE_CUBE_MAP, prefilter.id); - for (unsigned int i = 0; i < 6; i++) - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Generate mipmaps for the prefiltered HDR texture - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - - // Create projection (transposed) and different views for each face - Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); - //MatrixTranspose(&fboProjection); - Matrix fboViews[6] = { - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) - }; - - // Prefilter HDR and store data into mipmap levels - glUseProgram(shader.id); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); - - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - #define MAX_MIPMAP_LEVELS 5 // Max number of prefilter texture mipmaps - - for (unsigned int mip = 0; mip < MAX_MIPMAP_LEVELS; mip++) - { - // Resize framebuffer according to mip-level size. - unsigned int mipWidth = size*powf(0.5f, mip); - unsigned int mipHeight = size*powf(0.5f, mip); - - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight); - glViewport(0, 0, mipWidth, mipHeight); - - float roughness = (float)mip/(float)(MAX_MIPMAP_LEVELS - 1); - glUniform1f(roughnessLoc, roughness); - - for (unsigned int i = 0; i < 6; ++i) - { - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilter.id, mip); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawCube(); - } - } - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); - - prefilter.width = size; - prefilter.height = size; -#endif - return prefilter; -} - -// Generate BRDF texture using cubemap data -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) -{ - Texture2D brdf = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) - // Generate BRDF convolution texture - glGenTextures(1, &brdf.id); - glBindTexture(GL_TEXTURE_2D, brdf.id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, size, size, 0, GL_RG, GL_FLOAT, 0); - 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); - - // Render BRDF LUT into a quad using FBO - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdf.id, 0); - - glViewport(0, 0, size, size); - glUseProgram(shader.id); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawQuad(); - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, screenWidth, screenHeight); - - brdf.width = size; - brdf.height = size; -#endif - return brdf; -} - -// 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); -} - -#if defined(SUPPORT_VR_SIMULATOR) -// Get VR device information for some standard devices -VrDeviceInfo GetVrDeviceInfo(int vrDeviceType) -{ - VrDeviceInfo hmd = { 0 }; // Current VR device info - - switch (vrDeviceType) - { - case HMD_DEFAULT_DEVICE: - case 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.0669f; // 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.lensDistortionValues[0] = 1.0f; // HMD lens distortion constant parameter 0 - hmd.lensDistortionValues[1] = 0.22f; // HMD lens distortion constant parameter 1 - hmd.lensDistortionValues[2] = 0.24f; // HMD lens distortion constant parameter 2 - hmd.lensDistortionValues[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 - - TraceLog(LOG_INFO, "Initializing VR Simulator (Oculus Rift CV1)"); - } break; - case 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.lensDistortionValues[0] = 1.0f; // HMD lens distortion constant parameter 0 - hmd.lensDistortionValues[1] = 0.22f; // HMD lens distortion constant parameter 1 - hmd.lensDistortionValues[2] = 0.24f; // HMD lens distortion constant parameter 2 - hmd.lensDistortionValues[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 - - TraceLog(LOG_INFO, "Initializing VR Simulator (Oculus Rift DK2)"); - } break; - case HMD_OCULUS_GO: - { - // TODO: Provide device display and lens parameters - } - case HMD_VALVE_HTC_VIVE: - { - // TODO: Provide device display and lens parameters - } - case HMD_SONY_PSVR: - { - // TODO: Provide device display and lens parameters - } - default: break; - } - - return hmd; -} - -// Init VR simulator for selected device parameters -// NOTE: It modifies the global variable: VrStereoConfig vrConfig -void InitVrSimulator(VrDeviceInfo info) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Initialize framebuffer and textures for stereo rendering - // NOTE: Screen size should match HMD aspect ratio - vrConfig.stereoFbo = rlLoadRenderTexture(screenWidth, screenHeight); - -#if defined(SUPPORT_DISTORTION_SHADER) - // Load distortion shader - unsigned int vertexShaderId = CompileShader(distortionVShaderStr, GL_VERTEX_SHADER); - unsigned int fragmentShaderId = CompileShader(distortionFShaderStr, GL_FRAGMENT_SHADER); - - vrConfig.distortionShader.id = LoadShaderProgram(vertexShaderId, fragmentShaderId); - if (vrConfig.distortionShader.id > 0) SetShaderDefaultLocations(&vrConfig.distortionShader); -#endif - - // Set VR configutarion parameters, including distortion shader - SetStereoConfig(info); - - vrSimulatorReady = true; -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - TraceLog(LOG_WARNING, "VR Simulator not supported on OpenGL 1.1"); -#endif -} - -// Close VR simulator for current device -void CloseVrSimulator(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (vrSimulatorReady) - { - rlDeleteRenderTextures(vrConfig.stereoFbo); // Unload stereo framebuffer and texture - #if defined(SUPPORT_DISTORTION_SHADER) - UnloadShader(vrConfig.distortionShader); // Unload distortion shader - #endif - } -#endif -} - -// Detect if VR simulator is running -bool IsVrSimulatorReady(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - return vrSimulatorReady; -#else - return false; -#endif -} - -// Set VR distortion shader for stereoscopic rendering -// TODO: Review VR system to be more flexible, move distortion shader to user side -void SetVrDistortionShader(Shader shader) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - vrConfig.distortionShader = shader; - - //SetStereoConfig(info); // TODO: Must be reviewed to set new distortion shader uniform values... -#endif -} - -// Enable/Disable VR experience (device or simulator) -void ToggleVrMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - vrSimulatorReady = !vrSimulatorReady; - - if (!vrSimulatorReady) - { - vrStereoRender = false; - - // Reset viewport and default projection-modelview matrices - rlViewport(0, 0, screenWidth, screenHeight); - projection = MatrixOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); - modelview = MatrixIdentity(); - } - else vrStereoRender = true; -#endif -} - -// Update VR tracking (position and orientation) and camera -// NOTE: Camera (position, target, up) gets update with head tracking information -void UpdateVrTracking(Camera *camera) -{ - // TODO: Simulate 1st person camera system -} - -// Begin Oculus drawing configuration -void BeginVrDrawing(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (vrSimulatorReady) - { - // 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) - - vrStereoRender = true; - } -#endif -} - -// End Oculus drawing process (and desktop mirror) -void EndVrDrawing(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (vrSimulatorReady) - { - vrStereoRender = false; // Disable stereo render - - rlDisableRenderTexture(); // Unbind current framebuffer - - 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 - -#if defined(SUPPORT_DISTORTION_SHADER) - // Draw RenderTexture (stereoFbo) using distortion shader - currentShader = vrConfig.distortionShader; -#else - currentShader = GetShaderDefault(); -#endif - - 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(); - - // Update and draw render texture fbo with distortion to backbuffer - UpdateBuffersDefault(); - DrawBuffersDefault(); - - // Restore defaultShader - currentShader = defaultShader; - - // Reset viewport and default projection-modelview matrices - rlViewport(0, 0, screenWidth, screenHeight); - projection = MatrixOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); - modelview = MatrixIdentity(); - - rlDisableDepthTest(); - } -#endif -} -#endif // SUPPORT_VR_SIMULATOR - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Compile custom shader and return shader id -static unsigned int CompileShader(const char *shaderStr, int type) -{ - unsigned int shader = glCreateShader(type); - glShaderSource(shader, 1, &shaderStr, NULL); - - GLint success = 0; - glCompileShader(shader); - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) - { - TraceLog(LOG_WARNING, "[SHDR ID %i] Failed to compile shader...", shader); - int maxLength = 0; - int length; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - -#if defined(_MSC_VER) - char *log = malloc(maxLength); -#else - char log[maxLength]; -#endif - glGetShaderInfoLog(shader, maxLength, &length, log); - - TraceLog(LOG_INFO, "%s", log); - -#if defined(_MSC_VER) - free(log); -#endif - } - else TraceLog(LOG_INFO, "[SHDR ID %i] Shader compiled successfully", shader); - - return shader; -} - -// Load custom shader strings and return program id -static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - - GLint success = 0; - program = glCreateProgram(); - - glAttachShader(program, vShaderId); - glAttachShader(program, fShaderId); - - // 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(LOG_WARNING, "[SHDR ID %i] Failed to link shader program...", program); - - int maxLength = 0; - int length; - - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - -#ifdef _MSC_VER - char *log = malloc(maxLength); -#else - char log[maxLength]; -#endif - glGetProgramInfoLog(program, maxLength, &length, log); - - TraceLog(LOG_INFO, "%s", log); - -#ifdef _MSC_VER - free(log); -#endif - glDeleteProgram(program); - - program = 0; - } - else TraceLog(LOG_INFO, "[SHDR ID %i] Shader program loaded successfully", program); -#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 LoadShaderDefault(void) -{ - Shader shader = { 0 }; - - // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; - - // Vertex shader directly defined, no external file required - char defaultVShaderStr[] = -#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 mvp; \n" - "void main() \n" - "{ \n" - " fragTexCoord = vertexTexCoord; \n" - " fragColor = vertexColor; \n" - " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" - "} \n"; - - // Fragment shader directly defined, no external file required - char defaultFShaderStr[] = -#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"; - - // NOTE: Compiled vertex/fragment shaders are kept for re-use - defaultVShaderId = CompileShader(defaultVShaderStr, GL_VERTEX_SHADER); // Compile default vertex shader - defaultFShaderId = CompileShader(defaultFShaderStr, GL_FRAGMENT_SHADER); // Compile default fragment shader - - shader.id = LoadShaderProgram(defaultVShaderId, defaultFShaderId); - - if (shader.id > 0) - { - TraceLog(LOG_INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id); - - // Set default shader locations: attributes locations - shader.locs[LOC_VERTEX_POSITION] = glGetAttribLocation(shader.id, "vertexPosition"); - shader.locs[LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(shader.id, "vertexTexCoord"); - shader.locs[LOC_VERTEX_COLOR] = glGetAttribLocation(shader.id, "vertexColor"); - - // Set default shader locations: uniform locations - shader.locs[LOC_MATRIX_MVP] = glGetUniformLocation(shader.id, "mvp"); - shader.locs[LOC_COLOR_DIFFUSE] = glGetUniformLocation(shader.id, "colDiffuse"); - shader.locs[LOC_MAP_DIFFUSE] = glGetUniformLocation(shader.id, "texture0"); - - // NOTE: We could also use below function but in case DEFAULT_ATTRIB_* points are - // changed for external custom shaders, we just use direct bindings above - //SetShaderDefaultLocations(&shader); - } - else TraceLog(LOG_WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id); - - return shader; -} - -// Get location handlers to for shader attributes and uniforms -// NOTE: If any location is not found, loc point becomes -1 -static void SetShaderDefaultLocations(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->locs[LOC_VERTEX_POSITION] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_POSITION_NAME); - shader->locs[LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD_NAME); - shader->locs[LOC_VERTEX_TEXCOORD02] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD2_NAME); - shader->locs[LOC_VERTEX_NORMAL] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_NORMAL_NAME); - shader->locs[LOC_VERTEX_TANGENT] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TANGENT_NAME); - shader->locs[LOC_VERTEX_COLOR] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_COLOR_NAME); - - // Get handles to GLSL uniform locations (vertex shader) - shader->locs[LOC_MATRIX_MVP] = glGetUniformLocation(shader->id, "mvp"); - shader->locs[LOC_MATRIX_PROJECTION] = glGetUniformLocation(shader->id, "projection"); - shader->locs[LOC_MATRIX_VIEW] = glGetUniformLocation(shader->id, "view"); - - // Get handles to GLSL uniform locations (fragment shader) - shader->locs[LOC_COLOR_DIFFUSE] = glGetUniformLocation(shader->id, "colDiffuse"); - shader->locs[LOC_MAP_DIFFUSE] = glGetUniformLocation(shader->id, "texture0"); - shader->locs[LOC_MAP_SPECULAR] = glGetUniformLocation(shader->id, "texture1"); - shader->locs[LOC_MAP_NORMAL] = glGetUniformLocation(shader->id, "texture2"); -} - -// Unload default shader -static void UnloadShaderDefault(void) -{ - glUseProgram(0); - - glDetachShader(defaultShader.id, defaultVShaderId); - glDetachShader(defaultShader.id, defaultFShaderId); - glDeleteShader(defaultVShaderId); - glDeleteShader(defaultFShaderId); - - glDeleteProgram(defaultShader.id); -} - -// Load default internal buffers (lines, triangles, quads) -static void LoadBuffersDefault(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(unsigned int)*6*MAX_QUADS_BATCH); // 6 int by quad (indices) -#elif defined(GRAPHICS_API_OPENGL_ES2) - quads.indices = (unsigned short *)malloc(sizeof(unsigned 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(LOG_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.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 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.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (lines)", lines.vaoId); - else TraceLog(LOG_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.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 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.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (triangles)", triangles.vaoId); - else TraceLog(LOG_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.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 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.locs[LOC_VERTEX_TEXCOORD01]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 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.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 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(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (quads)", quads.vaoId); - else TraceLog(LOG_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 UpdateBuffersDefault(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 DrawBuffersDefault(void) -{ - Matrix matProjection = projection; - Matrix matModelView = modelview; - - int eyesCount = 1; -#if defined(SUPPORT_VR_SIMULATOR) - if (vrStereoRender) eyesCount = 2; -#endif - - for (int eye = 0; eye < eyesCount; eye++) - { - #if defined(SUPPORT_VR_SIMULATOR) - if (eyesCount == 2) SetStereoView(eye, matProjection, matModelView); - #endif - - // 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.locs[LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); - glUniform4f(currentShader.locs[LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1i(currentShader.locs[LOC_MAP_DIFFUSE], 0); - - // NOTE: Additional map textures not considered for default buffers drawing - } - - // Draw lines buffers - if (lines.vCounter > 0) - { - glActiveTexture(GL_TEXTURE0); - 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.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - } - - 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) - { - glActiveTexture(GL_TEXTURE0); - 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.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - } - - 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.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); - - // Bind vertex attrib: texcoord (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_TEXCOORD01]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]); - glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]); - } - - //TraceLog(LOG_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(LOG_DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount); - - glActiveTexture(GL_TEXTURE0); - 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(LOG_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 UnloadBuffersDefault(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); -} - -// Renders a 1x1 XY quad in NDC -static void GenDrawQuad(void) -{ - unsigned int quadVAO = 0; - unsigned int quadVBO = 0; - - float vertices[] = { - // Positions // Texture Coords - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - }; - - // Set up plane VAO - glGenVertexArrays(1, &quadVAO); - glGenBuffers(1, &quadVBO); - glBindVertexArray(quadVAO); - - // Fill buffer - glBindBuffer(GL_ARRAY_BUFFER, quadVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); - - // Link vertex attributes - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); - - // Draw quad - glBindVertexArray(quadVAO); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); - - glDeleteBuffers(1, &quadVBO); - glDeleteVertexArrays(1, &quadVAO); -} - -// Renders a 1x1 3D cube in NDC -static void GenDrawCube(void) -{ - unsigned int cubeVAO = 0; - unsigned int cubeVBO = 0; - - float vertices[] = { - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f - }; - - // Set up cube VAO - glGenVertexArrays(1, &cubeVAO); - glGenBuffers(1, &cubeVBO); - - // Fill buffer - glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - // Link vertex attributes - glBindVertexArray(cubeVAO); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - // Draw cube - glBindVertexArray(cubeVAO); - glDrawArrays(GL_TRIANGLES, 0, 36); - glBindVertexArray(0); - - glDeleteBuffers(1, &cubeVBO); - glDeleteVertexArrays(1, &cubeVAO); -} - -#if defined(SUPPORT_VR_SIMULATOR) -// Configure stereo rendering (including distortion shader) with HMD device parameters -// NOTE: It modifies the global variable: VrStereoConfig vrConfig -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.25f + lensShift, 0.5f }; - float rightLensCenter[2] = { 0.75f - 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 = fabs(-1.0f - 4.0f*lensShift); - float lensRadiusSq = lensRadius*lensRadius; - float distortionScale = hmd.lensDistortionValues[0] + - hmd.lensDistortionValues[1]*lensRadiusSq + - hmd.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + - hmd.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; - - TraceLog(LOG_DEBUG, "VR: Distortion Scale: %f", distortionScale); - - float normScreenWidth = 0.5f; - float normScreenHeight = 1.0f; - float scaleIn[2] = { 2.0f/normScreenWidth, 2.0f/normScreenHeight/aspect }; - float scale[2] = { normScreenWidth*0.5f/distortionScale, normScreenHeight*0.5f*aspect/distortionScale }; - - TraceLog(LOG_DEBUG, "VR: Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]); - TraceLog(LOG_DEBUG, "VR: Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]); - TraceLog(LOG_DEBUG, "VR: Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]); - TraceLog(LOG_DEBUG, "VR: Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]); - -#if defined(SUPPORT_DISTORTION_SHADER) - // 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.lensDistortionValues, 4); - SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, 4); -#endif - - // Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance) - // ...but with lens distortion it is increased (see Oculus SDK Documentation) - //float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance); // Really need distortionScale? - float fovy = 2.0f*(float)atan2(hmd.vScreenSize*0.5f, hmd.eyeToScreenDistance); - - // 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)); - - // 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) -{ - Matrix eyeProjection = matProjection; - Matrix eyeModelView = matModelView; - - // 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]); - - // Set current eye projection matrix - eyeProjection = vrConfig.eyesProjection[eye]; - - SetMatrixModelview(eyeModelView); - SetMatrixProjection(eyeProjection); -} -#endif // defined(SUPPORT_VR_SIMULATOR) - -#endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - -// Get OpenGL internal formats and data type from raylib PixelFormat -static void GetGlFormats(int format, int *glInternalFormat, int *glFormat, int *glType) -{ - *glInternalFormat = -1; - *glFormat = -1; - *glType = -1; - - switch (format) - { - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || 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 - case UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - #if !defined(GRAPHICS_API_OPENGL_11) - case UNCOMPRESSED_R32: if (texFloatSupported) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case UNCOMPRESSED_R32G32B32: if (texFloatSupported) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - #endif - #elif defined(GRAPHICS_API_OPENGL_33) - case UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R32: if (texFloatSupported) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; - case UNCOMPRESSED_R32G32B32: if (texFloatSupported) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - #endif - #if !defined(GRAPHICS_API_OPENGL_11) - case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; - case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; - case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 - case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - #endif - default: TraceLog(LOG_WARNING, "Texture format not supported"); break; - } -} - -#if defined(GRAPHICS_API_OPENGL_11) -// Mipmaps data is generated after image data -// NOTE: Only works with RGBA (4 bytes) 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(LOG_DEBUG, "Next mipmap size: %i x %i", width, height); - - mipmapCount++; - - size += (width*height*4); // Add mipmap size (in bytes) - } - - TraceLog(LOG_DEBUG, "Total mipmaps required: %i", mipmapCount); - TraceLog(LOG_DEBUG, "Total size of data required: %i", size); - - unsigned char *temp = realloc(data, size); - - if (temp != NULL) data = temp; - else TraceLog(LOG_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(LOG_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(LOG_DEBUG, "Mipmap generated successfully (%ix%i)", width, height); - - return mipmap; -} -#endif - -#if defined(RLGL_STANDALONE) -// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -void TraceLog(int msgType, const char *text, ...) -{ - va_list args; - va_start(args, text); - - switch (msgType) - { - case LOG_INFO: fprintf(stdout, "INFO: "); break; - case LOG_ERROR: fprintf(stdout, "ERROR: "); break; - case LOG_WARNING: fprintf(stdout, "WARNING: "); break; - case LOG_DEBUG: fprintf(stdout, "DEBUG: "); break; - default: break; - } - - vfprintf(stdout, text, args); - fprintf(stdout, "\n"); - - va_end(args); - - if (msgType == LOG_ERROR) exit(1); -} -#endif diff --git a/src/rlgl.h b/src/rlgl.h index 0a66a8f0..c30dc79c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -21,6 +21,11 @@ * Those preprocessor defines are only used on rlgl module, if OpenGL version is * required by any other module, use rlGetVersion() tocheck it * +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* * #define RLGL_STANDALONE * Use rlgl as standalone library (no raylib dependency) * @@ -28,7 +33,7 @@ * Support VR simulation functionality (stereo rendering) * * #define SUPPORT_DISTORTION_SHADER -* Include stereo rendering distortion shader (shader_distortion.h) +* Include stereo rendering distortion shader (embedded) * * DEPENDENCIES: * raymath - 3D math functionality (Vector3, Matrix, Quaternion) @@ -37,7 +42,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2017 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2018 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. @@ -68,8 +73,11 @@ #include "raymath.h" // Required for: Vector3, Matrix // Security check in case no GRAPHICS_API_OPENGL_* defined -#if !defined(GRAPHICS_API_OPENGL_11) && !defined(GRAPHICS_API_OPENGL_21) && !defined(GRAPHICS_API_OPENGL_33) && !defined(GRAPHICS_API_OPENGL_ES2) - #define GRAPHICS_API_OPENGL_11 +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_ES2) + #define GRAPHICS_API_OPENGL_33 #endif // Security check in case multiple GRAPHICS_API_OPENGL_* defined @@ -498,3 +506,4259 @@ void TraceLog(int msgType, const char *text, ...); // Show trace log messag #endif #endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +#include "config.h" + +#include // Required for: fopen(), fclose(), fread()... [Used only on LoadText()] +#include // Required for: malloc(), free(), rand() +#include // Required for: strcmp(), strlen(), strtok() [Used only in extensions loading] +#include // Required for: atan2() + +#if !defined(RLGL_STANDALONE) + #include "raymath.h" // Required for: Vector3 and Matrix functions +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include + #else + // APIENTRY for OpenGL function pointer declarations is required + #ifndef APIENTRY + #ifdef _WIN32 + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 // OpenGL 2.1 uses mostly OpenGL 3.3 Core functionality +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #if defined(__APPLE__) + #include // OpenGL 3 library for OSX + #include // OpenGL 3 extensions 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 library + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library +#endif + +#if defined(RLGL_STANDALONE) + #include // Required for: va_list, va_start(), vfprintf(), va_end() [Used only on TraceLog()] +#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) + +#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 + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#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_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#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 DynamicBuffer { + 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 DrawCall { + int vertexCount; + GLuint vaoId; + GLuint textureId; + GLuint shaderId; + + Matrix projection; + Matrix modelview; + + // TODO: Store additional draw state data + //int blendMode; + //Guint fboId; +} DrawCall; + +#if defined(SUPPORT_VR_SIMULATOR) +// VR Stereo rendering configuration for simulator +typedef struct VrStereoConfig { + 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; +#endif + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +#if !defined(GRAPHICS_API_OPENGL_11) && defined(SUPPORT_DISTORTION_SHADER) + // Distortion shader embedded + static char distortionFShaderStr[] = + #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" + #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) + "uniform vec2 leftLensCenter; \n" + "uniform vec2 rightLensCenter; \n" + "uniform vec2 leftScreenCenter; \n" + "uniform vec2 rightScreenCenter; \n" + "uniform vec2 scale; \n" + "uniform vec2 scaleIn; \n" + "uniform vec4 hmdWarpParam; \n" + "uniform vec4 chromaAbParam; \n" + #elif defined(GRAPHICS_API_OPENGL_33) + "uniform vec2 leftLensCenter = vec2(0.288, 0.5); \n" + "uniform vec2 rightLensCenter = vec2(0.712, 0.5); \n" + "uniform vec2 leftScreenCenter = vec2(0.25, 0.5); \n" + "uniform vec2 rightScreenCenter = vec2(0.75, 0.5); \n" + "uniform vec2 scale = vec2(0.25, 0.45); \n" + "uniform vec2 scaleIn = vec2(4, 2.2222); \n" + "uniform vec4 hmdWarpParam = vec4(1, 0.22, 0.24, 0); \n" + "uniform vec4 chromaAbParam = vec4(0.996, -0.004, 1.014, 0.0); \n" + #endif + "void main() \n" + "{ \n" + " vec2 lensCenter = fragTexCoord.x < 0.5 ? leftLensCenter : rightLensCenter; \n" + " vec2 screenCenter = fragTexCoord.x < 0.5 ? leftScreenCenter : rightScreenCenter; \n" + " vec2 theta = (fragTexCoord - lensCenter)*scaleIn; \n" + " float rSq = theta.x*theta.x + theta.y*theta.y; \n" + " vec2 theta1 = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq); \n" + " vec2 thetaBlue = theta1*(chromaAbParam.z + chromaAbParam.w*rSq); \n" + " vec2 tcBlue = lensCenter + scale*thetaBlue; \n" + " if (any(bvec2(clamp(tcBlue, screenCenter - vec2(0.25, 0.5), screenCenter + vec2(0.25, 0.5)) - tcBlue))) \n" + " { \n" + #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) + " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); \n" + #elif defined(GRAPHICS_API_OPENGL_33) + " finalColor = vec4(0.0, 0.0, 0.0, 1.0); \n" + #endif + " } \n" + " else \n" + " { \n" + #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) + " float blue = texture2D(texture0, tcBlue).b; \n" + " vec2 tcGreen = lensCenter + scale*theta1; \n" + " float green = texture2D(texture0, tcGreen).g; \n" + #elif defined(GRAPHICS_API_OPENGL_33) + " float blue = texture(texture0, tcBlue).b; \n" + " vec2 tcGreen = lensCenter + scale*theta1; \n" + " float green = texture(texture0, tcGreen).g; \n" + #endif + " vec2 thetaRed = theta1*(chromaAbParam.x + chromaAbParam.y*rSq); \n" + " vec2 tcRed = lensCenter + scale*thetaRed; \n" + #if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) + " float red = texture2D(texture0, tcRed).r; \n" + " gl_FragColor = vec4(red, green, blue, 1.0); \n" + #elif defined(GRAPHICS_API_OPENGL_33) + " float red = texture(texture0, tcRed).r; \n" + " finalColor = vec4(red, green, blue, 1.0); \n" + #endif + " } \n" + "} \n"; +#endif + +#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 int currentDrawMode; + +static float currentDepth = -1.0f; + +static DynamicBuffer lines; // Default dynamic buffer for lines data +static DynamicBuffer triangles; // Default dynamic buffer for triangles data +static DynamicBuffer quads; // Default dynamic buffer for quads data (used to draw textures) + +// 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; + +// Shaders +static unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) +static unsigned int defaultFShaderId; // Default fragment shader Id (used by default shader program) + +static Shader defaultShader; // Basic shader, support vertex color and diffuse texture +static Shader currentShader; // Shader to be used on rendering (by default, defaultShader) + +// Extension supported flag: VAO +static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension) + +// Extension supported flag: Compressed textures +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 + +#if defined(SUPPORT_VR_SIMULATOR) +// VR global variables +static VrStereoConfig vrConfig; // VR stereo configuration for simulator +static bool vrSimulatorReady = false; // VR simulator ready flag +static bool vrStereoRender = false; // VR stereo rendering enabled/disabled flag + // NOTE: This flag is useful to render data over stereo image (i.e. FPS) +#endif // defined(SUPPORT_VR_SIMULATOR) + +#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + +// Extension supported flag: Anisotropic filtering +static bool texAnisotropicFilterSupported = false; // Anisotropic texture filtering support +static float maxAnisotropicLevel = 0.0f; // Maximum anisotropy level supported (minimum is 2.0f) + +// Extension supported flag: Clamp mirror wrap mode +static bool texClampMirrorSupported = false; // Clamp mirror wrap mode supported + +#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 + +static bool debugMarkerSupported = false; + +// Compressed textures support flags +static bool texCompDXTSupported = false; // DDS texture compression support +static bool texNPOTSupported = false; // NPOT textures full support +static bool texFloatSupported = false; // float textures support (32 bit per channel) + +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 +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 unsigned int CompileShader(const char *shaderStr, int type); // Compile custom shader and return shader id +static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program + +static Shader LoadShaderDefault(void); // Load default shader (just vertex positioning and texture coloring) +static void SetShaderDefaultLocations(Shader *shader); // Bind default shader locations (attributes and uniforms) +static void UnloadShaderDefault(void); // Unload default shader + +static void LoadBuffersDefault(void); // Load default internal buffers (lines, triangles, quads) +static void UpdateBuffersDefault(void); // Update default internal buffers (VAOs/VBOs) with vertex data +static void DrawBuffersDefault(void); // Draw default internal buffers vertex data +static void UnloadBuffersDefault(void); // Unload default internal buffers vertex data from CPU and GPU + +static void GenDrawCube(void); // Generate and draw cube +static void GenDrawQuad(void); // Generate and draw quad + +#if defined(SUPPORT_VR_SIMULATOR) +static void SetStereoConfig(VrDeviceInfo info); // Configure stereo rendering (including distortion shader) with HMD device parameters +static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView); // Set internal projection and modelview matrix depending on eye +#endif + +#endif // defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + +// Get OpenGL internal formats and data type from raylib PixelFormat +static void GetGlFormats(int format, int *glInternalFormat, int *glFormat, int *glType); + +#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 + +//---------------------------------------------------------------------------------- +// 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 zNear, double zFar) +{ + glFrustum(left, right, bottom, top, zNear, zFar); +} + +void rlOrtho(double left, double right, double bottom, double top, double zNear, double zFar) +{ + glOrtho(left, right, bottom, top, zNear, zFar); +} + +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 *matf) { glMultMatrixf(matf); } + +#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(LOG_ERROR, "Stack Buffer Overflow (MAX %i Matrix)", MATRIX_STACK_SIZE); + } + + stack[stackCounter] = *currentMatrix; + rlLoadIdentity(); // TODO: Review matrix stack logic! + 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); + + // NOTE: We transpose matrix with multiplication order + *currentMatrix = MatrixMultiply(matTranslation, *currentMatrix); +} + +// 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 }; + matRotation = MatrixRotate(Vector3Normalize(axis), angleDeg*DEG2RAD); + + // NOTE: We transpose matrix with multiplication order + *currentMatrix = MatrixMultiply(matRotation, *currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = MatrixScale(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *currentMatrix = MatrixMultiply(matScale, *currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[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); + + *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); + + *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++) tempBuffer[i] = Vector3Transform(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); + + // Verify internal buffers limits + // NOTE: This check is combined with usage of rlCheckBufferLimit() + if ((lines.vCounter/2 >= MAX_LINES_BATCH - 2) || + (triangles.vCounter/3 >= MAX_TRIANGLES_BATCH - 3) || + (quads.vCounter/4 >= MAX_QUADS_BATCH - 4)) rlglDraw(); +} + +// 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(LOG_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(LOG_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(LOG_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++; + + if (drawsCounter >= MAX_DRAWS_BY_TEXTURE) + { + rlglDraw(); + drawsCounter = 1; + } + + 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); +#else + // NOTE: If quads batch limit is reached, + // we force a draw call and next batch starts + if (quads.vCounter/4 >= MAX_QUADS_BATCH) rlglDraw(); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if ((value == RL_WRAP_CLAMP_MIRROR) && !texClampMirrorSupported) TraceLog(LOG_WARNING, "Clamp mirror wrap mode not supported"); + else glTexParameteri(GL_TEXTURE_2D, param, value); + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_ANISOTROPIC_FILTER: + { + if (value <= maxAnisotropicLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (maxAnisotropicLevel > 0.0f) + { + TraceLog(LOG_WARNING, "[TEX ID %i] Maximum anisotropic filter level supported is %iX", id, maxAnisotropicLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TraceLog(LOG_WARNING, "Anisotropic filtering not supported"); + } break; + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// 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(LOG_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(LOG_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(LOG_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... +} + +//---------------------------------------------------------------------------------- +// 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(LOG_INFO, "GPU: Vendor: %s", glGetString(GL_VENDOR)); + TraceLog(LOG_INFO, "GPU: Renderer: %s", glGetString(GL_RENDERER)); + TraceLog(LOG_INFO, "GPU: Version: %s", glGetString(GL_VERSION)); + TraceLog(LOG_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(LOG_INFO, "GL_MAX_TEXTURE_SIZE: %i", maxTexSize); + + //GL_MAX_TEXTURE_IMAGE_UNITS + //GL_MAX_VIEWPORT_DIMS + + //int numAuxBuffers; + //glGetIntegerv(GL_AUX_BUFFERS, &numAuxBuffers); + //TraceLog(LOG_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(LOG_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(LOG_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; + texNPOTSupported = true; + texFloatSupported = 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); + +#ifdef _MSC_VER + const char **extList = malloc(sizeof(const char *)*numExt); +#else + const char *extList[numExt]; +#endif + + 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(LOG_INFO, "Number of supported extensions: %i", numExt); + + // Show supported extensions + //for (int i = 0; i < numExt; i++) TraceLog(LOG_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) texNPOTSupported = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) texFloatSupported = 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; + + // Anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) + { + texAnisotropicFilterSupported = true; + glGetFloatv(0x84FF, &maxAnisotropicLevel); // GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + } + + // Clamp mirror wrap mode supported + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) texClampMirrorSupported = true; + + // Debug marker support + if(strcmp(extList[i], (const char *)"GL_EXT_debug_marker") == 0) debugMarkerSupported = true; + } + +#if defined(_MSC_VER) + //free(extList); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + if (vaoSupported) TraceLog(LOG_INFO, "[EXTENSION] VAO extension detected, VAO functions initialized successfully"); + else TraceLog(LOG_WARNING, "[EXTENSION] VAO extension not found, VAO usage not supported"); + + if (texNPOTSupported) TraceLog(LOG_INFO, "[EXTENSION] NPOT textures extension detected, full NPOT textures supported"); + else TraceLog(LOG_WARNING, "[EXTENSION] NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); +#endif + + if (texCompDXTSupported) TraceLog(LOG_INFO, "[EXTENSION] DXT compressed textures supported"); + if (texCompETC1Supported) TraceLog(LOG_INFO, "[EXTENSION] ETC1 compressed textures supported"); + if (texCompETC2Supported) TraceLog(LOG_INFO, "[EXTENSION] ETC2/EAC compressed textures supported"); + if (texCompPVRTSupported) TraceLog(LOG_INFO, "[EXTENSION] PVRT compressed textures supported"); + if (texCompASTCSupported) TraceLog(LOG_INFO, "[EXTENSION] ASTC compressed textures supported"); + + if (texAnisotropicFilterSupported) TraceLog(LOG_INFO, "[EXTENSION] Anisotropic textures filtering supported (max: %.0fX)", maxAnisotropicLevel); + if (texClampMirrorSupported) TraceLog(LOG_INFO, "[EXTENSION] Clamp mirror wrap texture mode supported"); + + if (debugMarkerSupported) TraceLog(LOG_INFO, "[EXTENSION] Debug Marker 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 = rlLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1); + + if (whiteTexture != 0) TraceLog(LOG_INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture); + else TraceLog(LOG_WARNING, "Base white texture could not be loaded"); + + // Init default Shader (customized for GL 3.3 and ES2) + defaultShader = LoadShaderDefault(); + currentShader = defaultShader; + + // Init default vertex arrays buffers (lines, triangles, quads) + LoadBuffersDefault(); + + // 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] = Vector3Zero(); + + // 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(LOG_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) + UnloadShaderDefault(); // Unload default shader + UnloadBuffersDefault(); // Unload default buffers (lines, triangles, quads) + glDeleteTextures(1, &whiteTexture); // Unload default texture + + TraceLog(LOG_INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", whiteTexture); + + 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++) rlDrawMesh(models[i]->mesh, models[i]->material, models[i]->transform); + + // NOTE: Default buffers upload and draw + UpdateBuffersDefault(); + DrawBuffersDefault(); // NOTE: Stereo rendering is checked inside +#endif +} + +// Returns current OpenGL version +int rlGetVersion(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + return OPENGL_11; +#elif defined(GRAPHICS_API_OPENGL_21) + #if defined(__APPLE__) + return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX + #else + return OPENGL_21; + #endif +#elif defined(GRAPHICS_API_OPENGL_33) + return OPENGL_33; +#elif defined(GRAPHICS_API_OPENGL_ES2) + return OPENGL_ES_20; +#endif +} + +// Check internal buffer overflow for a given number of vertex +bool rlCheckBufferLimit(int type, int vCount) +{ + bool overflow = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (type) + { + case RL_LINES: overflow = ((lines.vCounter + vCount)/2 >= MAX_LINES_BATCH); break; + case RL_TRIANGLES: overflow = ((triangles.vCounter + vCount)/3 >= MAX_TRIANGLES_BATCH); break; + case RL_QUADS: overflow = ((quads.vCounter + vCount)/4 >= MAX_QUADS_BATCH); break; + default: break; + } +#endif + return overflow; +} + +// Set debug marker +void rlSetDebugMarker(const char *text) +{ +#if defined(GRAPHICS_API_OPENGL_33) + if (debugMarkerSupported) glInsertEventMarkerEXT(0, text); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function could be passed as a pointer +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + #if !defined(__APPLE__) + if (!gladLoadGLLoader((GLADloadproc)loader)) TraceLog(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TraceLog(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + + #if defined(GRAPHICS_API_OPENGL_21) + if (GLAD_GL_VERSION_2_1) TraceLog(LOG_INFO, "OpenGL 2.1 profile supported"); + #elif defined(GRAPHICS_API_OPENGL_33) + if(GLAD_GL_VERSION_3_3) TraceLog(LOG_INFO, "OpenGL 3.3 Core profile supported"); + else TraceLog(LOG_ERROR, "OpenGL 3.3 Core profile not supported"); + #endif + #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 rlUnproject(Vector3 source, Matrix proj, Matrix view) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it + Matrix matViewProj = MatrixMultiply(view, proj); + matViewProj = MatrixInvert(matViewProj); + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unproject matrix + quat = QuaternionTransform(quat, matViewProj); + + // 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 rlLoadTexture(void *data, int width, int height, int format, 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 (format >= COMPRESSED_DXT1_RGB) + { + TraceLog(LOG_WARNING, "OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#endif + + if ((!texCompDXTSupported) && ((format == COMPRESSED_DXT1_RGB) || (format == COMPRESSED_DXT1_RGBA) || + (format == COMPRESSED_DXT3_RGBA) || (format == COMPRESSED_DXT5_RGBA))) + { + TraceLog(LOG_WARNING, "DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!texCompETC1Supported) && (format == COMPRESSED_ETC1_RGB)) + { + TraceLog(LOG_WARNING, "ETC1 compressed texture format not supported"); + return id; + } + + if ((!texCompETC2Supported) && ((format == COMPRESSED_ETC2_RGB) || (format == COMPRESSED_ETC2_EAC_RGBA))) + { + TraceLog(LOG_WARNING, "ETC2 compressed texture format not supported"); + return id; + } + + if ((!texCompPVRTSupported) && ((format == COMPRESSED_PVRT_RGB) || (format == COMPRESSED_PVRT_RGBA))) + { + TraceLog(LOG_WARNING, "PVRT compressed texture format not supported"); + return id; + } + + if ((!texCompASTCSupported) && ((format == COMPRESSED_ASTC_4x4_RGBA) || (format == COMPRESSED_ASTC_8x8_RGBA))) + { + TraceLog(LOG_WARNING, "ASTC compressed texture format not supported"); + return id; + } +#endif + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + 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); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset + + TraceLog(LOG_DEBUG, "Load texture from data memory address: 0x%x", data); + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = GetPixelDataSize(mipWidth, mipHeight, format); + + int glInternalFormat, glFormat, glType; + GetGlFormats(format, &glInternalFormat, &glFormat, &glType); + + TraceLog(LOG_DEBUG, "Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != -1) + { + if (format < COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); + #if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, (unsigned char *)data + mipOffset); + #endif + + #if defined(GRAPHICS_API_OPENGL_33) + if (format == UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == UNCOMPRESSED_GRAY_ALPHA) + { + #if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; + #elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; + #endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + #endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // 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 (texNPOTSupported) + { + 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) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#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(LOG_INFO, "[TEX ID %i] Texture created successfully (%ix%i - %i mipmaps)", id, width, height, mipmapCount); + else TraceLog(LOG_WARNING, "Texture could not be created"); + + return id; +} + +// Update already loaded texture in GPU with new data +void rlUpdateTexture(unsigned int id, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + int glInternalFormat, glFormat, glType; + GetGlFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != -1) && (format < COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, glFormat, glType, (unsigned char *)data); + } + else TraceLog(LOG_WARNING, "Texture format updating not supported"); +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + if (id > 0) glDeleteTextures(1, &id); +} + + +// Load a texture to be used for rendering (fbo with color and depth attachments) +RenderTexture2D rlLoadRenderTexture(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_R8G8B8A8; + 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_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + +#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + #define USE_DEPTH_RENDERBUFFER +#else + #define USE_DEPTH_TEXTURE +#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(LOG_WARNING, "Framebuffer object could not be created..."); + + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TraceLog(LOG_WARNING, "Framebuffer is unsupported"); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer incomplete attachment"); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TraceLog(LOG_WARNING, "Framebuffer incomplete dimensions"); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TraceLog(LOG_WARNING, "Framebuffer incomplete missing attachment"); break; + default: break; + } + + glDeleteTextures(1, &target.texture.id); + glDeleteTextures(1, &target.depth.id); + glDeleteFramebuffers(1, &target.id); + } + else TraceLog(LOG_INFO, "[FBO ID %i] Framebuffer object created successfully", target.id); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif + + return target; +} + +// Generate mipmap data for selected texture +void rlGenerateMipmaps(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) || (texNPOTSupported)) + { +#if defined(GRAPHICS_API_OPENGL_11) + // WARNING: Manual mipmap generation only works for RGBA 32bit textures! + if (texture->format == UNCOMPRESSED_R8G8B8A8) + { + // Retrieve texture data from VRAM + void *data = rlReadTexturePixels(*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; + 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, (unsigned char *)data + offset); + + size = mipWidth*mipHeight*4; + offset += size; + + mipWidth /= 2; + mipHeight /= 2; + } + + texture->mipmaps = mipmapCount + 1; + free(data); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data + + TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps [%i] generated manually on CPU side", texture->id, texture->mipmaps); + } + else TraceLog(LOG_WARNING, "[TEX ID %i] Mipmaps could not be generated for texture format", texture->id); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + TraceLog(LOG_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 + + #define MIN(a,b) (((a)<(b))?(a):(b)) + #define MAX(a,b) (((a)>(b))?(a):(b)) + + texture->mipmaps = 1 + (int)floor(log(MAX(texture->width, texture->height))/log(2)); +#endif + } + else TraceLog(LOG_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 +// TODO: Check if mesh has already been loaded in GPU +void rlLoadMesh(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; + + if (vaoSupported) + { + // Initialize Quads VAO (Buffer A) + glGenVertexArrays(1, &mesh->vaoId); + glBindVertexArray(mesh->vaoId); + } + + // NOTE: Attributes must be uploaded considering default locations points + + // Enable vertex attributes: position (shader-location = 0) + glGenBuffers(1, &mesh->vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, mesh->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, &mesh->vboId[4]); + glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[4]); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*mesh->vertexCount, mesh->tangents, drawHint); + glVertexAttribPointer(4, 4, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(4); + } + else + { + // Default tangents vertex attribute + glVertexAttrib4f(4, 0.0f, 0.0f, 0.0f, 0.0f); + glDisableVertexAttribArray(4); + } + + // Default texcoord2 vertex attribute (shader-location = 5) + if (mesh->texcoords2 != NULL) + { + glGenBuffers(1, &mesh->vboId[5]); + glBindBuffer(GL_ARRAY_BUFFER, mesh->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 texcoord2 vertex attribute + glVertexAttrib2f(5, 0.0f, 0.0f); + glDisableVertexAttribArray(5); + } + + if (mesh->indices != NULL) + { + glGenBuffers(1, &mesh->vboId[6]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->vboId[6]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangleCount*3, mesh->indices, GL_STATIC_DRAW); + } + + if (vaoSupported) + { + if (mesh->vaoId > 0) TraceLog(LOG_INFO, "[VAO ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); + else TraceLog(LOG_WARNING, "Mesh could not be uploaded to VRAM (GPU)"); + } + else + { + TraceLog(LOG_INFO, "[VBOs] Mesh uploaded successfully to VRAM (GPU)"); + } +#endif +} + +// Update vertex data on GPU (upload new data to one buffer) +void rlUpdateMesh(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[2]); + 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[3]); + 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[4]); + if (numVertex >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*numVertex, mesh.tangents, GL_DYNAMIC_DRAW); + else glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*4*numVertex, mesh.tangents); + } break; + case 5: // Update texcoords2 (vertex second texture coordinates) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); + 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 rlDrawMesh(Mesh mesh, Material material, Matrix transform) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, material.maps[MAP_DIFFUSE].texture.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.maps[MAP_DIFFUSE].color.r, material.maps[MAP_DIFFUSE].color.g, material.maps[MAP_DIFFUSE].color.b, material.maps[MAP_DIFFUSE].color.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) + // Bind shader program + glUseProgram(material.shader.id); + + // Matrices and other values required by shader + //----------------------------------------------------- + // Calculate and send to shader model matrix (used by PBR shader) + if (material.shader.locs[LOC_MATRIX_MODEL] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_MODEL], transform); + + // Upload to shader material.colDiffuse + if (material.shader.locs[LOC_COLOR_DIFFUSE] != -1) + glUniform4f(material.shader.locs[LOC_COLOR_DIFFUSE], (float)material.maps[MAP_DIFFUSE].color.r/255.0f, + (float)material.maps[MAP_DIFFUSE].color.g/255.0f, + (float)material.maps[MAP_DIFFUSE].color.b/255.0f, + (float)material.maps[MAP_DIFFUSE].color.a/255.0f); + + // Upload to shader material.colSpecular (if available) + if (material.shader.locs[LOC_COLOR_SPECULAR] != -1) + glUniform4f(material.shader.locs[LOC_COLOR_SPECULAR], (float)material.maps[MAP_SPECULAR].color.r/255.0f, + (float)material.maps[MAP_SPECULAR].color.g/255.0f, + (float)material.maps[MAP_SPECULAR].color.b/255.0f, + (float)material.maps[MAP_SPECULAR].color.a/255.0f); + + if (material.shader.locs[LOC_MATRIX_VIEW] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_VIEW], modelview); + if (material.shader.locs[LOC_MATRIX_PROJECTION] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_PROJECTION], projection); + + // At this point the modelview matrix just contains the view matrix (camera) + // That's because BeginMode3D() 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 + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + glActiveTexture(GL_TEXTURE0 + i); + if ((i == MAP_IRRADIANCE) || (i == MAP_PREFILTER) || (i == MAP_CUBEMAP)) glBindTexture(GL_TEXTURE_CUBE_MAP, material.maps[i].texture.id); + else glBindTexture(GL_TEXTURE_2D, material.maps[i].texture.id); + + glUniform1i(material.shader.locs[LOC_MAP_DIFFUSE + i], i); + } + } + + // Bind vertex array objects (or VBOs) + if (vaoSupported) glBindVertexArray(mesh.vaoId); + else + { + // TODO: Simplify VBO binding into a for loop + + // Bind mesh VBO data: vertex position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]); + glVertexAttribPointer(material.shader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_POSITION]); + + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]); + glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TEXCOORD01]); + + // Bind mesh VBO data: vertex normals (shader-location = 2, if available) + if (material.shader.locs[LOC_VERTEX_NORMAL] != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]); + glVertexAttribPointer(material.shader.locs[LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_NORMAL]); + } + + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) + if (material.shader.locs[LOC_VERTEX_COLOR] != -1) + { + if (mesh.vboId[3] != 0) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]); + glVertexAttribPointer(material.shader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_COLOR]); + } + else + { + // Set default value for unused attribute + // NOTE: Required when using default shader and no VAO support + glVertexAttrib4f(material.shader.locs[LOC_VERTEX_COLOR], 1.0f, 1.0f, 1.0f, 1.0f); + glDisableVertexAttribArray(material.shader.locs[LOC_VERTEX_COLOR]); + } + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.locs[LOC_VERTEX_TANGENT] != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]); + glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TANGENT], 4, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TANGENT]); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.locs[LOC_VERTEX_TEXCOORD02] != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); + glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TEXCOORD02], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TEXCOORD02]); + } + + if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.vboId[6]); + } + + int eyesCount = 1; +#if defined(SUPPORT_VR_SIMULATOR) + if (vrStereoRender) eyesCount = 2; +#endif + + for (int eye = 0; eye < eyesCount; eye++) + { + if (eyesCount == 1) modelview = matModelView; + #if defined(SUPPORT_VR_SIMULATOR) + else SetStereoView(eye, matProjection, matModelView); + #endif + + // 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.locs[LOC_MATRIX_MVP], 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); + } + + // Unbind all binded texture maps + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + glActiveTexture(GL_TEXTURE0 + i); // Set shader active texture + if ((i == MAP_IRRADIANCE) || (i == MAP_PREFILTER) || (i == MAP_CUBEMAP)) glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + else glBindTexture(GL_TEXTURE_2D, 0); // Unbind current active texture + } + + // Unind vertex array objects (or VBOs) + if (vaoSupported) glBindVertexArray(0); + else + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + // Unbind shader program + glUseProgram(0); + + // Restore projection/modelview matrices + // NOTE: In stereo rendering matrices are being modified to fit every eye + projection = matProjection; + modelview = matView; +#endif +} + +// Unload mesh data from CPU and GPU +void rlUnloadMesh(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 *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)calloc(width*height*4, sizeof(unsigned char)); + + // 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 *rlReadTexturePixels(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 + */ + + // 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); + + int glInternalFormat, glFormat, glType; + GetGlFormats(texture.format, &glInternalFormat, &glFormat, &glType); + unsigned int size = GetPixelDataSize(texture.width, texture.height, texture.format); + + if ((glInternalFormat != -1) && (texture.format < COMPRESSED_DXT1_RGB)) + { + pixels = (unsigned char *)malloc(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TraceLog(LOG_WARNING, "Texture data retrieval not suported for pixel format"); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + RenderTexture2D fbo = rlLoadRenderTexture(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 RGBA + // 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: We read data as RGBA because FBO texture is configured as RGBA, despite binding a RGB texture... + 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); + //glDisable(GL_TEXTURE_2D); + glEnable(GL_DEPTH_TEST); + //glDisable(GL_BLEND); + + glViewport(0, 0, texture.width, texture.height); + rlOrtho(0.0, texture.width, texture.height, 0.0, 0.0, 1.0); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUseProgram(GetShaderDefault().id); + glBindTexture(GL_TEXTURE_2D, texture.id); + GenDrawQuad(); + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); + + pixels = (unsigned char *)malloc(texture.width*texture.height*4*sizeof(unsigned char)); + + glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Bind framebuffer 0, which means render to back buffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + glViewport(0, 0, screenWidth, screenHeight); + +#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 rlRecordDraw(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 GetTextureDefault(void) +{ + Texture2D texture; + + texture.id = whiteTexture; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = UNCOMPRESSED_R8G8B8A8; + + return texture; +} + +// Get default shader +Shader GetShaderDefault(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return defaultShader; +#else + Shader shader = { 0 }; + return shader; +#endif +} + +// Load text data from file +// NOTE: text chars array should be freed manually +char *LoadText(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(LOG_WARNING, "[%s] Text file could not be opened", fileName); + } + + return text; +} + +// Load shader from files and bind default locations +// NOTE: If shader string is NULL, using default vertex/fragment shaders +Shader LoadShader(const char *vsFileName, const char *fsFileName) +{ + Shader shader = { 0 }; + + char *vShaderStr = NULL; + char *fShaderStr = NULL; + + if (vsFileName != NULL) vShaderStr = LoadText(vsFileName); + if (fsFileName != NULL) fShaderStr = LoadText(fsFileName); + + shader = LoadShaderCode(vShaderStr, fShaderStr); + + if (vShaderStr != NULL) free(vShaderStr); + if (fShaderStr != NULL) free(fShaderStr); + + return shader; +} + +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +Shader LoadShaderCode(char *vsCode, char *fsCode) +{ + Shader shader = { 0 }; + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = defaultVShaderId; + unsigned int fragmentShaderId = defaultFShaderId; + + if (vsCode != NULL) vertexShaderId = CompileShader(vsCode, GL_VERTEX_SHADER); + if (fsCode != NULL) fragmentShaderId = CompileShader(fsCode, GL_FRAGMENT_SHADER); + + if ((vertexShaderId == defaultVShaderId) && (fragmentShaderId == defaultFShaderId)) shader = defaultShader; + else + { + shader.id = LoadShaderProgram(vertexShaderId, fragmentShaderId); + + if (vertexShaderId != defaultVShaderId) glDeleteShader(vertexShaderId); + if (fragmentShaderId != defaultFShaderId) glDeleteShader(fragmentShaderId); + + if (shader.id == 0) + { + TraceLog(LOG_WARNING, "Custom shader could not be loaded"); + shader = defaultShader; + } + + // After shader loading, we TRY to set default location names + if (shader.id > 0) SetShaderDefaultLocations(&shader); + } + + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + + glGetProgramiv(shader.id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for(int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256]; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(shader.id, i,sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + + // Get the location of the named uniform + GLuint location = glGetUniformLocation(shader.id, name); + + TraceLog(LOG_DEBUG, "[SHDR ID %i] Active uniform [%s] set at location: %i", shader.id, name, location); + } +#endif + + return shader; +} + +// Unload shader from GPU memory (VRAM) +void UnloadShader(Shader shader) +{ + if (shader.id > 0) + { + rlDeleteShader(shader.id); + TraceLog(LOG_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 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(LOG_WARNING, "[SHDR ID %i] Shader uniform [%s] COULD NOT BE FOUND", shader.id, uniformName); + else TraceLog(LOG_INFO, "[SHDR ID %i] Shader uniform [%s] set at location: %i", shader.id, uniformName, location); +#endif + return location; +} + +// Set shader uniform value (float) +void SetShaderValue(Shader shader, int uniformLoc, const 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(LOG_WARNING, "Shader value float array size not supported"); + + //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set +#endif +} + +// Set shader uniform value (int) +void SetShaderValuei(Shader shader, int uniformLoc, const 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(LOG_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 +} + +// Return internal modelview matrix +Matrix GetMatrixModelview() +{ + Matrix matrix = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); +#else + matrix = modelview; +#endif + return matrix; +} + +// Generate cubemap texture from HDR texture +// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 +Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size) +{ + Texture2D cubemap = { 0 }; +#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader + // Other locations should be setup externally in shader before calling the function + + // Set up depth face culling and cubemap seamless + glDisable(GL_CULL_FACE); +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Flag not supported on OpenGL ES 2.0 +#endif + + + // Setup framebuffer + unsigned int fbo, rbo; + glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &rbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); + + // Set up cubemap to render and attach to framebuffer + // NOTE: faces are stored with 16 bit floating point values + glGenTextures(1, &cubemap.id); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); + for (unsigned int i = 0; i < 6; i++) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Create projection (transposed) and different views for each face + Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); + //MatrixTranspose(&fboProjection); + Matrix fboViews[6] = { + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) + }; + + // Convert HDR equirectangular environment map to cubemap equivalent + glUseProgram(shader.id); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, skyHDR.id); + SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); + + // Note: don't forget to configure the viewport to the capture dimensions + glViewport(0, 0, size, size); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + for (unsigned int i = 0; i < 6; i++) + { + SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap.id, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GenDrawCube(); + } + + // Unbind framebuffer and textures + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + glViewport(0, 0, screenWidth, screenHeight); + //glEnable(GL_CULL_FACE); + + cubemap.width = size; + cubemap.height = size; +#endif + return cubemap; +} + +// Generate irradiance texture using cubemap data +// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 +Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) +{ + Texture2D irradiance = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader + // Other locations should be setup externally in shader before calling the function + + // Setup framebuffer + unsigned int fbo, rbo; + glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &rbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); + + // Create an irradiance cubemap, and re-scale capture FBO to irradiance scale + glGenTextures(1, &irradiance.id); + glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance.id); + for (unsigned int i = 0; i < 6; i++) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Create projection (transposed) and different views for each face + Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); + //MatrixTranspose(&fboProjection); + Matrix fboViews[6] = { + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) + }; + + // Solve diffuse integral by convolution to create an irradiance cubemap + glUseProgram(shader.id); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); + SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); + + // Note: don't forget to configure the viewport to the capture dimensions + glViewport(0, 0, size, size); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + for (unsigned int i = 0; i < 6; i++) + { + SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradiance.id, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GenDrawCube(); + } + + // Unbind framebuffer and textures + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + glViewport(0, 0, screenWidth, screenHeight); + + irradiance.width = size; + irradiance.height = size; +#endif + return irradiance; +} + +// Generate prefilter texture using cubemap data +// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 +Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) +{ + Texture2D prefilter = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader + // Other locations should be setup externally in shader before calling the function + // TODO: Locations should be taken out of this function... too shader dependant... + int roughnessLoc = GetShaderLocation(shader, "roughness"); + + // Setup framebuffer + unsigned int fbo, rbo; + glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &rbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); + + // Create a prefiltered HDR environment map + glGenTextures(1, &prefilter.id); + glBindTexture(GL_TEXTURE_CUBE_MAP, prefilter.id); + for (unsigned int i = 0; i < 6; i++) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Generate mipmaps for the prefiltered HDR texture + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + // Create projection (transposed) and different views for each face + Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, 0.01, 1000.0); + //MatrixTranspose(&fboProjection); + Matrix fboViews[6] = { + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), + MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) + }; + + // Prefilter HDR and store data into mipmap levels + glUseProgram(shader.id); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); + SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + #define MAX_MIPMAP_LEVELS 5 // Max number of prefilter texture mipmaps + + for (unsigned int mip = 0; mip < MAX_MIPMAP_LEVELS; mip++) + { + // Resize framebuffer according to mip-level size. + unsigned int mipWidth = size*powf(0.5f, mip); + unsigned int mipHeight = size*powf(0.5f, mip); + + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight); + glViewport(0, 0, mipWidth, mipHeight); + + float roughness = (float)mip/(float)(MAX_MIPMAP_LEVELS - 1); + glUniform1f(roughnessLoc, roughness); + + for (unsigned int i = 0; i < 6; ++i) + { + SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilter.id, mip); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GenDrawCube(); + } + } + + // Unbind framebuffer and textures + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + glViewport(0, 0, screenWidth, screenHeight); + + prefilter.width = size; + prefilter.height = size; +#endif + return prefilter; +} + +// Generate BRDF texture using cubemap data +// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 +Texture2D GenTextureBRDF(Shader shader, Texture2D cubemap, int size) +{ + Texture2D brdf = { 0 }; +#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) + // Generate BRDF convolution texture + glGenTextures(1, &brdf.id); + glBindTexture(GL_TEXTURE_2D, brdf.id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, size, size, 0, GL_RG, GL_FLOAT, 0); + 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); + + // Render BRDF LUT into a quad using FBO + unsigned int fbo, rbo; + glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &rbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdf.id, 0); + + glViewport(0, 0, size, size); + glUseProgram(shader.id); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GenDrawQuad(); + + // Unbind framebuffer and textures + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + glViewport(0, 0, screenWidth, screenHeight); + + brdf.width = size; + brdf.height = size; +#endif + return brdf; +} + +// 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); +} + +#if defined(SUPPORT_VR_SIMULATOR) +// Get VR device information for some standard devices +VrDeviceInfo GetVrDeviceInfo(int vrDeviceType) +{ + VrDeviceInfo hmd = { 0 }; // Current VR device info + + switch (vrDeviceType) + { + case HMD_DEFAULT_DEVICE: + case 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.0669f; // 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.lensDistortionValues[0] = 1.0f; // HMD lens distortion constant parameter 0 + hmd.lensDistortionValues[1] = 0.22f; // HMD lens distortion constant parameter 1 + hmd.lensDistortionValues[2] = 0.24f; // HMD lens distortion constant parameter 2 + hmd.lensDistortionValues[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 + + TraceLog(LOG_INFO, "Initializing VR Simulator (Oculus Rift CV1)"); + } break; + case 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.lensDistortionValues[0] = 1.0f; // HMD lens distortion constant parameter 0 + hmd.lensDistortionValues[1] = 0.22f; // HMD lens distortion constant parameter 1 + hmd.lensDistortionValues[2] = 0.24f; // HMD lens distortion constant parameter 2 + hmd.lensDistortionValues[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 + + TraceLog(LOG_INFO, "Initializing VR Simulator (Oculus Rift DK2)"); + } break; + case HMD_OCULUS_GO: + { + // TODO: Provide device display and lens parameters + } + case HMD_VALVE_HTC_VIVE: + { + // TODO: Provide device display and lens parameters + } + case HMD_SONY_PSVR: + { + // TODO: Provide device display and lens parameters + } + default: break; + } + + return hmd; +} + +// Init VR simulator for selected device parameters +// NOTE: It modifies the global variable: VrStereoConfig vrConfig +void InitVrSimulator(VrDeviceInfo info) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize framebuffer and textures for stereo rendering + // NOTE: Screen size should match HMD aspect ratio + vrConfig.stereoFbo = rlLoadRenderTexture(screenWidth, screenHeight); + +#if defined(SUPPORT_DISTORTION_SHADER) + // Load distortion shader + vrConfig.distortionShader = LoadShaderCode(NULL, distortionFShaderStr); + + if (vrConfig.distortionShader.id > 0) SetShaderDefaultLocations(&vrConfig.distortionShader); +#endif + + // Set VR configutarion parameters, including distortion shader + SetStereoConfig(info); + + vrSimulatorReady = true; +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + TraceLog(LOG_WARNING, "VR Simulator not supported on OpenGL 1.1"); +#endif +} + +// Close VR simulator for current device +void CloseVrSimulator(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (vrSimulatorReady) + { + rlDeleteRenderTextures(vrConfig.stereoFbo); // Unload stereo framebuffer and texture + #if defined(SUPPORT_DISTORTION_SHADER) + UnloadShader(vrConfig.distortionShader); // Unload distortion shader + #endif + } +#endif +} + +// Detect if VR simulator is running +bool IsVrSimulatorReady(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return vrSimulatorReady; +#else + return false; +#endif +} + +// Set VR distortion shader for stereoscopic rendering +// TODO: Review VR system to be more flexible, move distortion shader to user side +void SetVrDistortionShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + vrConfig.distortionShader = shader; + + //SetStereoConfig(info); // TODO: Must be reviewed to set new distortion shader uniform values... +#endif +} + +// Enable/Disable VR experience (device or simulator) +void ToggleVrMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + vrSimulatorReady = !vrSimulatorReady; + + if (!vrSimulatorReady) + { + vrStereoRender = false; + + // Reset viewport and default projection-modelview matrices + rlViewport(0, 0, screenWidth, screenHeight); + projection = MatrixOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); + modelview = MatrixIdentity(); + } + else vrStereoRender = true; +#endif +} + +// Update VR tracking (position and orientation) and camera +// NOTE: Camera (position, target, up) gets update with head tracking information +void UpdateVrTracking(Camera *camera) +{ + // TODO: Simulate 1st person camera system +} + +// Begin Oculus drawing configuration +void BeginVrDrawing(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (vrSimulatorReady) + { + // 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) + + vrStereoRender = true; + } +#endif +} + +// End Oculus drawing process (and desktop mirror) +void EndVrDrawing(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (vrSimulatorReady) + { + vrStereoRender = false; // Disable stereo render + + rlDisableRenderTexture(); // Unbind current framebuffer + + 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 + +#if defined(SUPPORT_DISTORTION_SHADER) + // Draw RenderTexture (stereoFbo) using distortion shader + currentShader = vrConfig.distortionShader; +#else + currentShader = GetShaderDefault(); +#endif + + 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(); + + // Update and draw render texture fbo with distortion to backbuffer + UpdateBuffersDefault(); + DrawBuffersDefault(); + + // Restore defaultShader + currentShader = defaultShader; + + // Reset viewport and default projection-modelview matrices + rlViewport(0, 0, screenWidth, screenHeight); + projection = MatrixOrtho(0.0, screenWidth, screenHeight, 0.0, 0.0, 1.0); + modelview = MatrixIdentity(); + + rlDisableDepthTest(); + } +#endif +} +#endif // SUPPORT_VR_SIMULATOR + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Compile custom shader and return shader id +static unsigned int CompileShader(const char *shaderStr, int type) +{ + unsigned int shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderStr, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) + { + TraceLog(LOG_WARNING, "[SHDR ID %i] Failed to compile shader...", shader); + int maxLength = 0; + int length; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + +#if defined(_MSC_VER) + char *log = malloc(maxLength); +#else + char log[maxLength]; +#endif + glGetShaderInfoLog(shader, maxLength, &length, log); + + TraceLog(LOG_INFO, "%s", log); + +#if defined(_MSC_VER) + free(log); +#endif + } + else TraceLog(LOG_INFO, "[SHDR ID %i] Shader compiled successfully", shader); + + return shader; +} + +// Load custom shader strings and return program id +static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // 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(LOG_WARNING, "[SHDR ID %i] Failed to link shader program...", program); + + int maxLength = 0; + int length; + + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + +#ifdef _MSC_VER + char *log = malloc(maxLength); +#else + char log[maxLength]; +#endif + glGetProgramInfoLog(program, maxLength, &length, log); + + TraceLog(LOG_INFO, "%s", log); + +#ifdef _MSC_VER + free(log); +#endif + glDeleteProgram(program); + + program = 0; + } + else TraceLog(LOG_INFO, "[SHDR ID %i] Shader program loaded successfully", program); +#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 LoadShaderDefault(void) +{ + Shader shader = { 0 }; + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + + // Vertex shader directly defined, no external file required + char defaultVShaderStr[] = +#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 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + char defaultFShaderStr[] = +#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"; + + // NOTE: Compiled vertex/fragment shaders are kept for re-use + defaultVShaderId = CompileShader(defaultVShaderStr, GL_VERTEX_SHADER); // Compile default vertex shader + defaultFShaderId = CompileShader(defaultFShaderStr, GL_FRAGMENT_SHADER); // Compile default fragment shader + + shader.id = LoadShaderProgram(defaultVShaderId, defaultFShaderId); + + if (shader.id > 0) + { + TraceLog(LOG_INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id); + + // Set default shader locations: attributes locations + shader.locs[LOC_VERTEX_POSITION] = glGetAttribLocation(shader.id, "vertexPosition"); + shader.locs[LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.locs[LOC_VERTEX_COLOR] = glGetAttribLocation(shader.id, "vertexColor"); + + // Set default shader locations: uniform locations + shader.locs[LOC_MATRIX_MVP] = glGetUniformLocation(shader.id, "mvp"); + shader.locs[LOC_COLOR_DIFFUSE] = glGetUniformLocation(shader.id, "colDiffuse"); + shader.locs[LOC_MAP_DIFFUSE] = glGetUniformLocation(shader.id, "texture0"); + + // NOTE: We could also use below function but in case DEFAULT_ATTRIB_* points are + // changed for external custom shaders, we just use direct bindings above + //SetShaderDefaultLocations(&shader); + } + else TraceLog(LOG_WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id); + + return shader; +} + +// Get location handlers to for shader attributes and uniforms +// NOTE: If any location is not found, loc point becomes -1 +static void SetShaderDefaultLocations(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->locs[LOC_VERTEX_POSITION] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_POSITION_NAME); + shader->locs[LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD_NAME); + shader->locs[LOC_VERTEX_TEXCOORD02] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TEXCOORD2_NAME); + shader->locs[LOC_VERTEX_NORMAL] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_NORMAL_NAME); + shader->locs[LOC_VERTEX_TANGENT] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_TANGENT_NAME); + shader->locs[LOC_VERTEX_COLOR] = glGetAttribLocation(shader->id, DEFAULT_ATTRIB_COLOR_NAME); + + // Get handles to GLSL uniform locations (vertex shader) + shader->locs[LOC_MATRIX_MVP] = glGetUniformLocation(shader->id, "mvp"); + shader->locs[LOC_MATRIX_PROJECTION] = glGetUniformLocation(shader->id, "projection"); + shader->locs[LOC_MATRIX_VIEW] = glGetUniformLocation(shader->id, "view"); + + // Get handles to GLSL uniform locations (fragment shader) + shader->locs[LOC_COLOR_DIFFUSE] = glGetUniformLocation(shader->id, "colDiffuse"); + shader->locs[LOC_MAP_DIFFUSE] = glGetUniformLocation(shader->id, "texture0"); + shader->locs[LOC_MAP_SPECULAR] = glGetUniformLocation(shader->id, "texture1"); + shader->locs[LOC_MAP_NORMAL] = glGetUniformLocation(shader->id, "texture2"); +} + +// Unload default shader +static void UnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(defaultShader.id, defaultVShaderId); + glDetachShader(defaultShader.id, defaultFShaderId); + glDeleteShader(defaultVShaderId); + glDeleteShader(defaultFShaderId); + + glDeleteProgram(defaultShader.id); +} + +// Load default internal buffers (lines, triangles, quads) +static void LoadBuffersDefault(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(unsigned int)*6*MAX_QUADS_BATCH); // 6 int by quad (indices) +#elif defined(GRAPHICS_API_OPENGL_ES2) + quads.indices = (unsigned short *)malloc(sizeof(unsigned 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(LOG_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.locs[LOC_VERTEX_POSITION]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 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.locs[LOC_VERTEX_COLOR]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (lines)", lines.vaoId); + else TraceLog(LOG_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.locs[LOC_VERTEX_POSITION]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 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.locs[LOC_VERTEX_COLOR]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + if (vaoSupported) TraceLog(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (triangles)", triangles.vaoId); + else TraceLog(LOG_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.locs[LOC_VERTEX_POSITION]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_POSITION], 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.locs[LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 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.locs[LOC_VERTEX_COLOR]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 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(LOG_INFO, "[VAO ID %i] Default buffers VAO initialized successfully (quads)", quads.vaoId); + else TraceLog(LOG_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 UpdateBuffersDefault(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 DrawBuffersDefault(void) +{ + Matrix matProjection = projection; + Matrix matModelView = modelview; + + int eyesCount = 1; +#if defined(SUPPORT_VR_SIMULATOR) + if (vrStereoRender) eyesCount = 2; +#endif + + for (int eye = 0; eye < eyesCount; eye++) + { + #if defined(SUPPORT_VR_SIMULATOR) + if (eyesCount == 2) SetStereoView(eye, matProjection, matModelView); + #endif + + // 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.locs[LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); + glUniform4f(currentShader.locs[LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(currentShader.locs[LOC_MAP_DIFFUSE], 0); + + // NOTE: Additional map textures not considered for default buffers drawing + } + + // Draw lines buffers + if (lines.vCounter > 0) + { + glActiveTexture(GL_TEXTURE0); + 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.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, lines.vboId[1]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); + } + + 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) + { + glActiveTexture(GL_TEXTURE0); + 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.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, triangles.vboId[1]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); + } + + 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.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[1]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, quads.vboId[2]); + glVertexAttribPointer(currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.locs[LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quads.vboId[3]); + } + + //TraceLog(LOG_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(LOG_DEBUG, "Quads to render: %i - Vertex Count: %i", quadsCount, draws[i].vertexCount); + + glActiveTexture(GL_TEXTURE0); + 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(LOG_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 UnloadBuffersDefault(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); +} + +// Renders a 1x1 XY quad in NDC +static void GenDrawQuad(void) +{ + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions // Texture Coords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Set up plane VAO + glGenVertexArrays(1, &quadVAO); + glGenBuffers(1, &quadVBO); + glBindVertexArray(quadVAO); + + // Fill buffer + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Link vertex attributes + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +} + +// Renders a 1x1 3D cube in NDC +static void GenDrawCube(void) +{ + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Set up cube VAO + glGenVertexArrays(1, &cubeVAO); + glGenBuffers(1, &cubeVBO); + + // Fill buffer + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Link vertex attributes + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +} + +#if defined(SUPPORT_VR_SIMULATOR) +// Configure stereo rendering (including distortion shader) with HMD device parameters +// NOTE: It modifies the global variable: VrStereoConfig vrConfig +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.25f + lensShift, 0.5f }; + float rightLensCenter[2] = { 0.75f - 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 = fabs(-1.0f - 4.0f*lensShift); + float lensRadiusSq = lensRadius*lensRadius; + float distortionScale = hmd.lensDistortionValues[0] + + hmd.lensDistortionValues[1]*lensRadiusSq + + hmd.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + + hmd.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; + + TraceLog(LOG_DEBUG, "VR: Distortion Scale: %f", distortionScale); + + float normScreenWidth = 0.5f; + float normScreenHeight = 1.0f; + float scaleIn[2] = { 2.0f/normScreenWidth, 2.0f/normScreenHeight/aspect }; + float scale[2] = { normScreenWidth*0.5f/distortionScale, normScreenHeight*0.5f*aspect/distortionScale }; + + TraceLog(LOG_DEBUG, "VR: Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]); + TraceLog(LOG_DEBUG, "VR: Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]); + TraceLog(LOG_DEBUG, "VR: Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]); + TraceLog(LOG_DEBUG, "VR: Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]); + +#if defined(SUPPORT_DISTORTION_SHADER) + // 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.lensDistortionValues, 4); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, 4); +#endif + + // Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance) + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + //float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance); // Really need distortionScale? + float fovy = 2.0f*(float)atan2(hmd.vScreenSize*0.5f, hmd.eyeToScreenDistance); + + // 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)); + + // 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) +{ + Matrix eyeProjection = matProjection; + Matrix eyeModelView = matModelView; + + // 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]); + + // Set current eye projection matrix + eyeProjection = vrConfig.eyesProjection[eye]; + + SetMatrixModelview(eyeModelView); + SetMatrixProjection(eyeProjection); +} +#endif // defined(SUPPORT_VR_SIMULATOR) + +#endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + +// Get OpenGL internal formats and data type from raylib PixelFormat +static void GetGlFormats(int format, int *glInternalFormat, int *glFormat, int *glType) +{ + *glInternalFormat = -1; + *glFormat = -1; + *glType = -1; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || 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 + case UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + case UNCOMPRESSED_R32: if (texFloatSupported) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case UNCOMPRESSED_R32G32B32: if (texFloatSupported) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case UNCOMPRESSED_R32: if (texFloatSupported) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case UNCOMPRESSED_R32G32B32: if (texFloatSupported) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TraceLog(LOG_WARNING, "Texture format not supported"); break; + } +} + +#if defined(GRAPHICS_API_OPENGL_11) +// Mipmaps data is generated after image data +// NOTE: Only works with RGBA (4 bytes) 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(LOG_DEBUG, "Next mipmap size: %i x %i", width, height); + + mipmapCount++; + + size += (width*height*4); // Add mipmap size (in bytes) + } + + TraceLog(LOG_DEBUG, "Total mipmaps required: %i", mipmapCount); + TraceLog(LOG_DEBUG, "Total size of data required: %i", size); + + unsigned char *temp = realloc(data, size); + + if (temp != NULL) data = temp; + else TraceLog(LOG_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(LOG_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(LOG_DEBUG, "Mipmap generated successfully (%ix%i)", width, height); + + return mipmap; +} +#endif + +#if defined(RLGL_STANDALONE) +// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +void TraceLog(int msgType, const char *text, ...) +{ + va_list args; + va_start(args, text); + + switch (msgType) + { + case LOG_INFO: fprintf(stdout, "INFO: "); break; + case LOG_ERROR: fprintf(stdout, "ERROR: "); break; + case LOG_WARNING: fprintf(stdout, "WARNING: "); break; + case LOG_DEBUG: fprintf(stdout, "DEBUG: "); break; + default: break; + } + + vfprintf(stdout, text, args); + fprintf(stdout, "\n"); + + va_end(args); + + if (msgType == LOG_ERROR) exit(1); +} +#endif + +#endif // RLGL_IMPLEMENTATION \ No newline at end of file diff --git a/src/shader_distortion.h b/src/shader_distortion.h deleted file mode 100644 index 3b37c630..00000000 --- a/src/shader_distortion.h +++ /dev/null @@ -1,106 +0,0 @@ - -// Vertex shader definition to embed, no external file required -static const char distortionVShaderStr[] = -#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 mvp; \n" -"void main() \n" -"{ \n" -" fragTexCoord = vertexTexCoord; \n" -" fragColor = vertexColor; \n" -" gl_Position = mvp*vec4(vertexPosition, 1.0); \n" -"} \n"; - -// Fragment shader definition to embed, no external file required -static const char distortionFShaderStr[] = -#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" -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) -"uniform vec2 leftLensCenter; \n" -"uniform vec2 rightLensCenter; \n" -"uniform vec2 leftScreenCenter; \n" -"uniform vec2 rightScreenCenter; \n" -"uniform vec2 scale; \n" -"uniform vec2 scaleIn; \n" -"uniform vec4 hmdWarpParam; \n" -"uniform vec4 chromaAbParam; \n" -#elif defined(GRAPHICS_API_OPENGL_33) -"uniform vec2 leftLensCenter = vec2(0.288, 0.5); \n" -"uniform vec2 rightLensCenter = vec2(0.712, 0.5); \n" -"uniform vec2 leftScreenCenter = vec2(0.25, 0.5); \n" -"uniform vec2 rightScreenCenter = vec2(0.75, 0.5); \n" -"uniform vec2 scale = vec2(0.25, 0.45); \n" -"uniform vec2 scaleIn = vec2(4, 2.2222); \n" -"uniform vec4 hmdWarpParam = vec4(1, 0.22, 0.24, 0); \n" -"uniform vec4 chromaAbParam = vec4(0.996, -0.004, 1.014, 0.0); \n" -#endif -"void main() \n" -"{ \n" -" vec2 lensCenter = fragTexCoord.x < 0.5 ? leftLensCenter : rightLensCenter; \n" -" vec2 screenCenter = fragTexCoord.x < 0.5 ? leftScreenCenter : rightScreenCenter; \n" -" vec2 theta = (fragTexCoord - lensCenter)*scaleIn; \n" -" float rSq = theta.x*theta.x + theta.y*theta.y; \n" -" vec2 theta1 = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq); \n" -" vec2 thetaBlue = theta1*(chromaAbParam.z + chromaAbParam.w*rSq); \n" -" vec2 tcBlue = lensCenter + scale*thetaBlue; \n" -" if (any(bvec2(clamp(tcBlue, screenCenter - vec2(0.25, 0.5), screenCenter + vec2(0.25, 0.5)) - tcBlue))) \n" -" { \n" -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) -" gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); \n" -#elif defined(GRAPHICS_API_OPENGL_33) -" finalColor = vec4(0.0, 0.0, 0.0, 1.0); \n" -#endif -" } \n" -" else \n" -" { \n" -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) -" float blue = texture2D(texture0, tcBlue).b; \n" -" vec2 tcGreen = lensCenter + scale*theta1; \n" -" float green = texture2D(texture0, tcGreen).g; \n" -#elif defined(GRAPHICS_API_OPENGL_33) -" float blue = texture(texture0, tcBlue).b; \n" -" vec2 tcGreen = lensCenter + scale*theta1; \n" -" float green = texture(texture0, tcGreen).g; \n" -#endif -" vec2 thetaRed = theta1*(chromaAbParam.x + chromaAbParam.y*rSq); \n" -" vec2 tcRed = lensCenter + scale*thetaRed; \n" -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) -" float red = texture2D(texture0, tcRed).r; \n" -" gl_FragColor = vec4(red, green, blue, 1.0); \n" -#elif defined(GRAPHICS_API_OPENGL_33) -" float red = texture(texture0, tcRed).r; \n" -" finalColor = vec4(red, green, blue, 1.0); \n" -#endif -" } \n" -"} \n";