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 351361146..3f59e8391 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 da8713a94..bc8745c6e 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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 diff --git a/src/core.c b/src/core.c index 45615f833..c578a8073 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 c806d35dd..000000000 --- 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 0a66a8f0e..c30dc79c7 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 3b37c6303..000000000 --- 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";