From 6595bab8ff57dd2b8f7bd5b97bf4db1e6c28917f Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:45:50 -0500 Subject: [PATCH 001/107] update make for examples (#4209) --- examples/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/Makefile b/examples/Makefile index be8d872045e4..39484f791a51 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -479,6 +479,7 @@ CORE = \ core/core_automation_events \ core/core_basic_screen_manager \ core/core_basic_window \ + core/core_basic_window_web \ core/core_custom_frame_control \ core/core_custom_logging \ core/core_drop_files \ @@ -528,6 +529,7 @@ TEXTURES = \ textures/textures_draw_tiled \ textures/textures_fog_of_war \ textures/textures_gif_player \ + textures/textures_image_channel \ textures/textures_image_drawing \ textures/textures_image_generation \ textures/textures_image_kernel \ @@ -566,6 +568,7 @@ TEXT = \ MODELS = \ models/models_animation \ models/models_billboard \ + models/models_bone_socket \ models/models_box_collisions \ models/models_cubicmap \ models/models_draw_cube_texture \ @@ -586,6 +589,7 @@ MODELS = \ SHADERS = \ shaders/shaders_basic_lighting \ + shaders/shaders_basic_pbr \ shaders/shaders_custom_uniform \ shaders/shaders_deferred_render \ shaders/shaders_eratosthenes \ From 92f60a99f67bc1c4c502e7e80790a94e6d84f91d Mon Sep 17 00:00:00 2001 From: Randy Palamar <55642015+rnpnr@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:53:56 -0600 Subject: [PATCH 002/107] [rlgl] use GLint64 for glGetBufferParameteri64v (#4197) --- src/rlgl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 38f9ae8bd7f2..64c7a1ab6394 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4397,7 +4397,7 @@ void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSi // Get SSBO buffer size unsigned int rlGetShaderBufferSize(unsigned int id) { - long long size = 0; + GLint64 size = 0; #if defined(GRAPHICS_API_OPENGL_43) glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); From 43b0c9410eb3c52153e3c9b94132da3ea21ad12b Mon Sep 17 00:00:00 2001 From: Alex ZH Date: Sun, 4 Aug 2024 15:58:26 -0400 Subject: [PATCH 003/107] [examples] Add new example: `shaders_vertex_displacement` (#4186) * shaders-vertex_displacement init * implement simulation of wave in ocean * update examples/README & add some comments * update comments * add gl100 shaders --- examples/Makefile | 3 +- examples/Makefile.Web | 3 +- examples/README.md | 19 +-- .../shaders/glsl100/vertex_displacement.fs | 18 +++ .../shaders/glsl100/vertex_displacement.vs | 45 +++++++ .../shaders/glsl330/vertex_displacement.fs | 16 +++ .../shaders/glsl330/vertex_displacement.vs | 46 +++++++ .../shaders/shaders_vertex_displacement.c | 122 ++++++++++++++++++ .../shaders/shaders_vertex_displacement.png | Bin 0 -> 309007 bytes 9 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 examples/shaders/resources/shaders/glsl100/vertex_displacement.fs create mode 100644 examples/shaders/resources/shaders/glsl100/vertex_displacement.vs create mode 100644 examples/shaders/resources/shaders/glsl330/vertex_displacement.fs create mode 100644 examples/shaders/resources/shaders/glsl330/vertex_displacement.vs create mode 100644 examples/shaders/shaders_vertex_displacement.c create mode 100644 examples/shaders/shaders_vertex_displacement.png diff --git a/examples/Makefile b/examples/Makefile index 39484f791a51..ee540606dab1 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -612,7 +612,8 @@ SHADERS = \ shaders/shaders_texture_outline \ shaders/shaders_texture_tiling \ shaders/shaders_texture_waves \ - shaders/shaders_write_depth + shaders/shaders_write_depth \ + shaders/shaders_vertex_displacement AUDIO = \ audio/audio_mixed_processor \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index af3325aa5650..46a8a98ea077 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -476,7 +476,8 @@ SHADERS = \ shaders/shaders_texture_outline \ shaders/shaders_texture_tiling \ shaders/shaders_texture_waves \ - shaders/shaders_write_depth + shaders/shaders_write_depth \ + shaders/shaders_vertex_displacement AUDIO = \ audio/audio_mixed_processor \ diff --git a/examples/README.md b/examples/README.md index d790f7d84b4a..317f43c899a3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -178,6 +178,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | 115 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | | 116 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | | 117 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | +| 118 | [shaders_vertex_displacement](shaders/shaders_vertex_displacement.c) | shaders_deferred_render | ⭐️☆☆☆ | 1.5 | 1.5 | [Alex ZH](https://github.com/ZzzhHe) | ### category: audio @@ -185,10 +186,10 @@ Examples using raylib audio functionality, including sound/music loading and pla | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------| -| 118 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 119 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | **4.2** | [Ray](https://github.com/raysan5) | -| 120 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | **4.2** | [Ray](https://github.com/raysan5) | -| 121 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | +| 119 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 120 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | **4.2** | [Ray](https://github.com/raysan5) | +| 121 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | **4.2** | [Ray](https://github.com/raysan5) | +| 122 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | ### category: others @@ -196,11 +197,11 @@ Examples showing raylib misc functionality that does not fit in other categories | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------| -| 122 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | **4.0** | [Ray](https://github.com/raysan5) | -| 123 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Teddy Astie](https://github.com/tsnake41) | -| 124 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | -| 125 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Stephan Soller](https://github.com/arkanis) | -| 126 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | +| 123 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | **4.0** | [Ray](https://github.com/raysan5) | +| 124 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Teddy Astie](https://github.com/tsnake41) | +| 125 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | +| 126 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Stephan Soller](https://github.com/arkanis) | +| 127 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | As always contributions are welcome, feel free to send new examples! Here is an [examples template](examples_template.c) to start with! diff --git a/examples/shaders/resources/shaders/glsl100/vertex_displacement.fs b/examples/shaders/resources/shaders/glsl100/vertex_displacement.fs new file mode 100644 index 000000000000..3328a91e7569 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/vertex_displacement.fs @@ -0,0 +1,18 @@ +#version 100 + +precision mediump float; + +// Input vertex attributes (from fragment shader) +varying vec2 fragTexCoord; +varying float height; + + +void main() +{ + vec4 darkblue = vec4(0.0, 0.13, 0.18, 1.0); + vec4 lightblue = vec4(1.0, 1.0, 1.0, 1.0); + // Interpolate between two colors based on height + vec4 finalColor = mix(darkblue, lightblue, height); + + gl_FragColor = finalColor; +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl100/vertex_displacement.vs b/examples/shaders/resources/shaders/glsl100/vertex_displacement.vs new file mode 100644 index 000000000000..c7b926d4b5f3 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/vertex_displacement.vs @@ -0,0 +1,45 @@ +#version 100 + +precision mediump float; + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; +attribute vec4 vertexColor; + +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +uniform float time; + +uniform sampler2D perlinNoiseMap; + +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec3 fragNormal; +varying float height; + +void main() +{ + // Calculate animated texture coordinates based on time and vertex position + vec2 animatedTexCoord = sin(vertexTexCoord + vec2(sin(time + vertexPosition.x * 0.1), cos(time + vertexPosition.z * 0.1)) * 0.3); + + // Normalize animated texture coordinates to range [0, 1] + animatedTexCoord = animatedTexCoord * 0.5 + 0.5; + + // Fetch displacement from the perlin noise map + float displacement = texture2D(perlinNoiseMap, animatedTexCoord).r * 7.0; // Amplified displacement + + // Displace vertex position + vec3 displacedPosition = vertexPosition + vec3(0.0, displacement, 0.0); + + // Send vertex attributes to fragment shader + fragPosition = vec3(matModel * vec4(displacedPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 1.0))); + height = displacedPosition.y * 0.2; // send height to fragment shader for coloring + + // Calculate final vertex position + gl_Position = mvp * vec4(displacedPosition, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl330/vertex_displacement.fs b/examples/shaders/resources/shaders/glsl330/vertex_displacement.fs new file mode 100644 index 000000000000..424f526e4d04 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/vertex_displacement.fs @@ -0,0 +1,16 @@ +#version 330 + +// Input fragment attributes (from fragment shader) +in vec2 fragTexCoord; +in float height; + +// Output fragment color +out vec4 finalColor; + +void main() +{ + vec4 darkblue = vec4(0.0, 0.13, 0.18, 1.0); + vec4 lightblue = vec4(1.0, 1.0, 1.0, 1.0); + // interplate between two colors based on height + finalColor = mix(darkblue, lightblue, height); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl330/vertex_displacement.vs b/examples/shaders/resources/shaders/glsl330/vertex_displacement.vs new file mode 100644 index 000000000000..73cf16109ca2 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/vertex_displacement.vs @@ -0,0 +1,46 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +uniform float time; + +uniform sampler2D perlinNoiseMap; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec3 fragNormal; +out float height; + +void main() +{ + // Calculate animated texture coordinates based on time and vertex position + vec2 animatedTexCoord = sin(vertexTexCoord + vec2(sin(time + vertexPosition.x * 0.1), cos(time + vertexPosition.z * 0.1)) * 0.3); + + // Normalize animated texture coordinates to range [0, 1] + animatedTexCoord = animatedTexCoord * 0.5 + 0.5; + + // Fetch displacement from the perlin noise map + float displacement = texture(perlinNoiseMap, animatedTexCoord).r * 7; // Amplified displacement + + // Displace vertex position + vec3 displacedPosition = vertexPosition + vec3(0.0, displacement, 0.0); + + // Send vertex attributes to fragment shader + fragPosition = vec3(matModel*vec4(displacedPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragNormal = normalize(vec3(matNormal*vec4(vertexNormal, 1.0))); + height = displacedPosition.y * 0.2; // send height to fragment shader for coloring + + // Calculate final vertex position + gl_Position = mvp*vec4(displacedPosition , 1.0); +} diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c new file mode 100644 index 000000000000..edce07ce7c0e --- /dev/null +++ b/examples/shaders/shaders_vertex_displacement.c @@ -0,0 +1,122 @@ +/******************************************************************************************* +* +* raylib [shaders] example - Vertex displacement +* +* Example originally created with raylib 5.0, last time updated with raylib 4.5 +* +* Example contributed by (@ZzzhHe) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2023 (@ZzzhHe) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" + +#include + +#define RLIGHTS_IMPLEMENTATION +#include "rlights.h" + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - vertex displacement"); + + // set up camera + Camera camera = {0}; + camera.position = (Vector3) {20.0f, 5.0f, -20.0f}; + camera.target = (Vector3) {0.0f, 0.0f, 0.0f}; + camera.up = (Vector3) {0.0f, 1.0f, 0.0f}; + camera.fovy = 60.0f; + camera.projection = CAMERA_PERSPECTIVE; + + // Load vertex and fragment shaders + Shader shader = LoadShader( + TextFormat("resources/shaders/glsl%i/vertex_displacement.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/vertex_displacement.fs", GLSL_VERSION) + ); + + // Load perlin noise texture + Image perlinNoiseImage = GenImagePerlinNoise(512, 512, 0, 0, 1.0f); + Texture perlinNoiseMap = LoadTextureFromImage(perlinNoiseImage); + UnloadImage(perlinNoiseImage); + + // Set shader uniform location + int perlinNoiseMapLoc = GetShaderLocation(shader, "perlinNoiseMap"); + rlEnableShader(shader.id); + rlActiveTextureSlot(1); + rlEnableTexture(perlinNoiseMap.id); + rlSetUniformSampler(perlinNoiseMapLoc, 1); + + // Create a plane mesh and model + Mesh planeMesh = GenMeshPlane(50, 50, 50, 50); + Model planeModel = LoadModelFromMesh(planeMesh); + // Set plane model material + planeModel.materials[0].shader = shader; + + float time = 0.0f; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_FREE); // Update camera + + time += GetFrameTime(); // Update time variable + SetShaderValue(shader, GetShaderLocation(shader, "time"), &time, SHADER_UNIFORM_FLOAT); // Send time value to shader + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + BeginShaderMode(shader); + // Draw plane model + DrawModel(planeModel, (Vector3){ 0.0f, 0.0f, 0.0f }, 1.0f, (Color) {255, 255, 255, 255}); + EndShaderMode(); + + EndMode3D(); + + DrawText("Vertex displacement", 10, 10, 20, DARKGRAY); + DrawFPS(10, 40); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + + UnloadShader(shader); + UnloadModel(planeModel); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shaders/shaders_vertex_displacement.png b/examples/shaders/shaders_vertex_displacement.png new file mode 100644 index 0000000000000000000000000000000000000000..e7acf5c651a8961d3a6982db2087069105572c8e GIT binary patch literal 309007 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

1mP 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%0D0( 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 z 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 E3F*N$W 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ᗺ`v%&OWELjS{6v_xu{D5E&35nnT<*o{ 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 literal 0 HcmV?d00001 From 596cc3a645f86f3e38b40abdb3fb82cc0cae593e Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sun, 4 Aug 2024 21:59:48 +0200 Subject: [PATCH 004/107] [rcore][desktop_glfw] Set AUTO_ICONIFY flag to false per default (#4188) * GLFW AUTO_ICONIFY flag is now set to false per default. Previously AUTO_ICONIFY was only disabled if the user requested a Fullscreen window from the start. After that it was not possible to change this behavior on the user side anymore, even when changing to a Fullscreen window. The AUTO_ICONIFY causes problems on macOS. On macOS if the window is minimized because of AUTO_ICONIFY than the only way to restore it is to click on the icon in the dock. In other words when AUTO_ICONIFY is enabled alt/cmd-tabbing through windows does not work correctly. On windows it works even when AUTO_ICONIFY is enabled. Additionally if a raylib window is in Fullscreen mode on another monitor the AUTO_ICONIFY behavior is a problem because the user might want to window to stay on the monitor even if it loses focus. (problem on all OS's) AUTO_ICONIFY also restores the monitor hardware resolution if a fullscreen window loses focus. * Update rcore_desktop_glfw.c Extra space removed and comments updated with a space at the beginning --- src/platforms/rcore_desktop_glfw.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 8377b880ff4a..d64bf60db888 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1275,6 +1275,11 @@ int InitPlatform(void) //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + // Disable GlFW auto iconify behaviour + // Auto Iconify automatically minimizes (iconifies) the window if the window loses focus + // additionally auto iconify restores the hardware resolution of the monitor if the window that loses focus is a fullscreen window + glfwWindowHint(GLFW_AUTO_ICONIFY, 0); + // Check window creation flags if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; @@ -1454,11 +1459,6 @@ int InitPlatform(void) // No-fullscreen window creation bool requestWindowedFullscreen = (CORE.Window.screen.height == 0) && (CORE.Window.screen.width == 0); - // If we are windowed fullscreen, ensures that window does not minimize when focus is lost. - // This hinting code will not work if the user already specified the correct monitor dimensions; - // at this point we don't know the monitor's dimensions. (Though, how did the user then?) - if (requestWindowedFullscreen) glfwWindowHint(GLFW_AUTO_ICONIFY, 0); - // Default to at least one pixel in size, as creation with a zero dimension is not allowed. int creationWidth = CORE.Window.screen.width != 0 ? CORE.Window.screen.width : 1; int creationHeight = CORE.Window.screen.height != 0 ? CORE.Window.screen.height : 1; From b2d48ff17258bc22aa4453506e20fa7bde1bdd76 Mon Sep 17 00:00:00 2001 From: lnc3l0t Date: Sun, 4 Aug 2024 22:01:28 +0200 Subject: [PATCH 005/107] [build.zig] Override config.h definitions (#4193) * [build.zig] Overridable definitions from config.h The new Options field "config" holds a string the user can set in the format "-Dflag_a=1 -Dflag_b=0 ..." to override the values set in `config.h`. The file is parsed and the default values are appended to the compilation flags, if the user doesn't override them. The user string is appended to the compilation flags. The "-DEXTERNAL_CONFIG_FLAGS" is added to prevent "config.h" inclusion. Note: a certain format is assumed for the formatting of config.h Note: this commit references the closed issue #3516 * [build.zig] Only SUPPORT_* definitions are overridable Lines from `config.h` which contains "SUPPORT" are added to compilation after being parsed: - remove whitespace - format to preprocessor option https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html The user supplied flags have priority over the ones read from the file. NOTE: extension to commit 4da7f82e6f912485351167da3bbc91807371fdee, the logic is simplified because the SUPPORT flags only have binary values, which makes them easier to parse. --- src/build.zig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/build.zig b/src/build.zig index 2da5cbd04d37..2c9519a0e7d6 100644 --- a/src/build.zig +++ b/src/build.zig @@ -28,6 +28,7 @@ pub fn addRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. .shared = options.shared, .linux_display_backend = options.linux_display_backend, .opengl_version = options.opengl_version, + .config = options.config, }); const raylib = raylib_dep.artifact("raylib"); @@ -52,6 +53,36 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. "-DGL_SILENCE_DEPRECATION=199309L", "-fno-sanitize=undefined", // https://github.com/raysan5/raylib/issues/3674 }); + if (options.config) |config| { + const file = try std.fs.path.join(b.allocator, &.{ std.fs.path.dirname(@src().file) orelse ".", "config.h" }); + defer b.allocator.free(file); + const content = try std.fs.cwd().readFileAlloc(b.allocator, file, std.math.maxInt(usize)); + defer b.allocator.free(content); + + var lines = std.mem.split(u8, content, "\n"); + while (lines.next()) |line| { + if (!std.mem.containsAtLeast(u8, line, 1, "SUPPORT")) continue; + if (std.mem.startsWith(u8, line, "//")) continue; + + var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace + flag = flag["#define ".len - 1 ..]; // Remove #define + flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace + flag = flag[0..std.mem.indexOf(u8, flag, " ").?]; // Flag is only one word, so capture till space + flag = try std.fmt.allocPrint(b.allocator, "-D{s}", .{flag}); // Prepend with -D + + // If user specifies the flag skip it + if (std.mem.containsAtLeast(u8, config, 1, flag)) continue; + + // Append default value from config.h to compile flags + try raylib_flags_arr.append(b.allocator, flag); + } + + // Append config flags supplied by user to compile flags + try raylib_flags_arr.append(b.allocator, config); + + try raylib_flags_arr.append(b.allocator, "-DEXTERNAL_CONFIG_FLAGS"); + } + if (options.shared) { try raylib_flags_arr.appendSlice(b.allocator, shared_flags); } @@ -253,6 +284,7 @@ pub const Options = struct { shared: bool = false, linux_display_backend: LinuxDisplayBackend = .Both, opengl_version: OpenglVersion = .auto, + config: ?[]const u8 = null, raygui_dependency_name: []const u8 = "raygui", }; @@ -307,6 +339,7 @@ pub fn build(b: *std.Build) !void { .shared = b.option(bool, "shared", "Compile as shared library") orelse defaults.shared, .linux_display_backend = b.option(LinuxDisplayBackend, "linux_display_backend", "Linux display backend to use") orelse defaults.linux_display_backend, .opengl_version = b.option(OpenglVersion, "opengl_version", "OpenGL version to use") orelse defaults.opengl_version, + .config = b.option([]const u8, "config", "Compile with custom define macros overriding config.h") orelse null, }; const lib = try compileRaylib(b, target, optimize, options); From b657001e0d7119532d01de27ad69d962ad736119 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 4 Aug 2024 22:06:20 +0200 Subject: [PATCH 006/107] REVIEWED: shaders_vertex_displacement --- examples/Makefile | 2 +- examples/shaders/shaders_vertex_displacement.c | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index ee540606dab1..93b05a0c7caa 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -613,7 +613,7 @@ SHADERS = \ shaders/shaders_texture_tiling \ shaders/shaders_texture_waves \ shaders/shaders_write_depth \ - shaders/shaders_vertex_displacement + shaders/shaders_vertex_displacement AUDIO = \ audio/audio_mixed_processor \ diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c index edce07ce7c0e..c211d349fa52 100644 --- a/examples/shaders/shaders_vertex_displacement.c +++ b/examples/shaders/shaders_vertex_displacement.c @@ -14,10 +14,8 @@ ********************************************************************************************/ #include "raylib.h" -#include "raymath.h" -#include "rlgl.h" -#include +#include "rlgl.h" #define RLIGHTS_IMPLEMENTATION #include "rlights.h" @@ -51,8 +49,7 @@ int main(void) // Load vertex and fragment shaders Shader shader = LoadShader( TextFormat("resources/shaders/glsl%i/vertex_displacement.vs", GLSL_VERSION), - TextFormat("resources/shaders/glsl%i/vertex_displacement.fs", GLSL_VERSION) - ); + TextFormat("resources/shaders/glsl%i/vertex_displacement.fs", GLSL_VERSION)); // Load perlin noise texture Image perlinNoiseImage = GenImagePerlinNoise(512, 512, 0, 0, 1.0f); @@ -111,7 +108,6 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - UnloadShader(shader); UnloadModel(planeModel); From 923f983719a0c06be9d64efb597034aca509bb4e Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 4 Aug 2024 22:08:38 +0200 Subject: [PATCH 007/107] REVIEWED: Possible overflow #4206 --- src/rlgl.h | 3 ++- src/rtextures.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 64c7a1ab6394..7eb1bb1d3622 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4993,7 +4993,8 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - dataSize = width*height*bpp/8; // Total data size in bytes + char bytesPerPixel = bpp/8; + dataSize = width*height*bytesPerPixel; // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 diff --git a/src/rtextures.c b/src/rtextures.c index 60ce58f4b09f..94ad81e2035f 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5403,7 +5403,8 @@ int GetPixelDataSize(int width, int height, int format) default: break; } - dataSize = width*height*bpp/8; // Total data size in bytes + char bytesPerPixel = bpp/8; + dataSize = width*height*bytesPerPixel; // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 From 9c2ba3bfb7166b71a852d4a4912363d0977c8676 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 4 Aug 2024 23:22:27 +0200 Subject: [PATCH 008/107] REVIEWED: possible overflow... again #4206 --- src/rlgl.h | 4 ++-- src/rtextures.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 7eb1bb1d3622..3ff6d7e2a750 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4993,8 +4993,8 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - char bytesPerPixel = bpp/8; - dataSize = width*height*bytesPerPixel; // Total data size in bytes + float bytesPerPixel = (float)bpp/8.0f; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 diff --git a/src/rtextures.c b/src/rtextures.c index 94ad81e2035f..9359f9219842 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5403,8 +5403,8 @@ int GetPixelDataSize(int width, int height, int format) default: break; } - char bytesPerPixel = bpp/8; - dataSize = width*height*bytesPerPixel; // Total data size in bytes + float bytesPerPixel = (float)bpp/8.0f; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 From 8b714e9dd9fb7ddeb13f21a13d61bd77eed2e2a9 Mon Sep 17 00:00:00 2001 From: lnc3l0t Date: Tue, 6 Aug 2024 18:28:33 +0200 Subject: [PATCH 009/107] [build.zig] check if wayland-scanner is installed (#4217) #4150 introduced a default value for linux_display_backend, which makes X11-only systems fail to build. --- src/build.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/build.zig b/src/build.zig index 2c9519a0e7d6..65379c03b4ab 100644 --- a/src/build.zig +++ b/src/build.zig @@ -155,6 +155,13 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. } if (options.linux_display_backend == .Wayland or options.linux_display_backend == .Both) { + _ = b.findProgram(&.{"wayland-scanner"}, &.{}) catch { + std.log.err( + \\ Wayland may not be installed on the system. + \\ You can switch to X11 in your `build.zig` by changing `Options.linux_display_backend` + , .{}); + @panic("No Wayland"); + }; raylib.defineCMacro("_GLFW_WAYLAND", null); raylib.linkSystemLibrary("wayland-client"); raylib.linkSystemLibrary("wayland-cursor"); From 4b84b5563e53d6ef2c37795bc91f7088313c8d9e Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:29:10 -0500 Subject: [PATCH 010/107] Update audio mixed processor (#4214) * updated audio mixed processor * remove float cast, better parenthesis --- examples/audio/audio_mixed_processor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/audio/audio_mixed_processor.c b/examples/audio/audio_mixed_processor.c index fd970dd19b0a..2df381b26552 100644 --- a/examples/audio/audio_mixed_processor.c +++ b/examples/audio/audio_mixed_processor.c @@ -97,7 +97,7 @@ int main(void) DrawRectangle(199, 199, 402, 34, LIGHTGRAY); for (int i = 0; i < 400; i++) { - DrawLine(201 + i, 232 - (int)averageVolume[i] * 32, 201 + i, 232, MAROON); + DrawLine(201 + i, 232 - (averageVolume[i] * 32), 201 + i, 232, MAROON); } DrawRectangleLines(199, 199, 402, 34, GRAY); From b44b759b8f646dfd7823978b59d2cf0f700e14a7 Mon Sep 17 00:00:00 2001 From: CDM15y Date: Tue, 6 Aug 2024 17:30:15 +0100 Subject: [PATCH 011/107] [examples][shaders_raymarching] Add `raymarching.fs` for GLSL120 (#4183) * Create raymarching.fs * Update raymarching.fs * Update raymarching.fs * Update raymarching.fs Remove `fragColor` as it is unused Move the license to the top of the code to improve readability. --- .../resources/shaders/glsl120/raymarching.fs | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl120/raymarching.fs diff --git a/examples/shaders/resources/shaders/glsl120/raymarching.fs b/examples/shaders/resources/shaders/glsl120/raymarching.fs new file mode 100644 index 000000000000..d3180ac12a0d --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/raymarching.fs @@ -0,0 +1,451 @@ +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// A list of useful distance function to simple primitives, and an example on how to +// do some interesting boolean operations, repetition and displacement. +// +// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +#version 120 + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; + +uniform vec3 viewEye; +uniform vec3 viewCenter; +uniform float runTime; +uniform vec2 resolution; + +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// A list of useful distance function to simple primitives, and an example on how to +// do some interesting boolean operations, repetition and displacement. +// +// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +#define AA 1 // make this 1 if your machine is too slow + +//------------------------------------------------------------------ + +float sdPlane( vec3 p ) +{ + return p.y; +} + +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +float sdBox( vec3 p, vec3 b ) +{ + vec3 d = abs(p) - b; + return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); +} + +float sdEllipsoid( in vec3 p, in vec3 r ) +{ + return (length( p/r ) - 1.0) * min(min(r.x,r.y),r.z); +} + +float udRoundBox( vec3 p, vec3 b, float r ) +{ + return length(max(abs(p)-b,0.0))-r; +} + +float sdTorus( vec3 p, vec2 t ) +{ + return length( vec2(length(p.xz)-t.x,p.y) )-t.y; +} + +float sdHexPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); +#if 0 + return max(q.z-h.y,max((q.x*0.866025+q.y*0.5),q.y)-h.x); +#else + float d1 = q.z-h.y; + float d2 = max((q.x*0.866025+q.y*0.5),q.y)-h.x; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +#endif +} + +float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; +} + +float sdEquilateralTriangle( in vec2 p ) +{ + const float k = sqrt(3.0); + p.x = abs(p.x) - 1.0; + p.y = p.y + 1.0/k; + if( p.x + k*p.y > 0.0 ) p = vec2( p.x - k*p.y, -k*p.x - p.y )/2.0; + p.x += 2.0 - 2.0*clamp( (p.x+2.0)/2.0, 0.0, 1.0 ); + return -length(p)*sign(p.y); +} + +float sdTriPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); + float d1 = q.z-h.y; +#if 1 + // distance bound + float d2 = max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5; +#else + // correct distance + h.x *= 0.866025; + float d2 = sdEquilateralTriangle(p.xy/h.x)*h.x; +#endif + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdCylinder( vec3 p, vec2 h ) +{ + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdCone( in vec3 p, in vec3 c ) +{ + vec2 q = vec2( length(p.xz), p.y ); + float d1 = -q.y-c.z; + float d2 = max( dot(q,c.xy), q.y); + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdConeSection( in vec3 p, in float h, in float r1, in float r2 ) +{ + float d1 = -p.y - h; + float q = p.y - h; + float si = 0.5*(r1-r2)/h; + float d2 = max( sqrt( dot(p.xz,p.xz)*(1.0-si*si)) + q*si - r2, q ); + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdPryamid4(vec3 p, vec3 h ) // h = { cos a, sin a, height } +{ + // Tetrahedron = Octahedron - Cube + float box = sdBox( p - vec3(0,-2.0*h.z,0), vec3(2.0*h.z) ); + + float d = 0.0; + d = max( d, abs( dot(p, vec3( -h.x, h.y, 0 )) )); + d = max( d, abs( dot(p, vec3( h.x, h.y, 0 )) )); + d = max( d, abs( dot(p, vec3( 0, h.y, h.x )) )); + d = max( d, abs( dot(p, vec3( 0, h.y,-h.x )) )); + float octa = d - h.z; + return max(-box,octa); // Subtraction + } + +float length2( vec2 p ) +{ + return sqrt( p.x*p.x + p.y*p.y ); +} + +float length6( vec2 p ) +{ + p = p*p*p; p = p*p; + return pow( p.x + p.y, 1.0/6.0 ); +} + +float length8( vec2 p ) +{ + p = p*p; p = p*p; p = p*p; + return pow( p.x + p.y, 1.0/8.0 ); +} + +float sdTorus82( vec3 p, vec2 t ) +{ + vec2 q = vec2(length2(p.xz)-t.x,p.y); + return length8(q)-t.y; +} + +float sdTorus88( vec3 p, vec2 t ) +{ + vec2 q = vec2(length8(p.xz)-t.x,p.y); + return length8(q)-t.y; +} + +float sdCylinder6( vec3 p, vec2 h ) +{ + return max( length6(p.xz)-h.x, abs(p.y)-h.y ); +} + +//------------------------------------------------------------------ + +float opS( float d1, float d2 ) +{ + return max(-d2,d1); +} + +vec2 opU( vec2 d1, vec2 d2 ) +{ + return (d1.x0.0 ) tmax = min( tmax, tp1 ); + float tp2 = (1.6-ro.y)/rd.y; if( tp2>0.0 ) { if( ro.y>1.6 ) tmin = max( tmin, tp2 ); + else tmax = min( tmax, tp2 ); } +#endif + + float t = tmin; + float m = -1.0; + for( int i=0; i<64; i++ ) + { + float precis = 0.0005*t; + vec2 res = map( ro+rd*t ); + if( res.xtmax ) break; + t += res.x; + m = res.y; + } + + if( t>tmax ) m=-1.0; + return vec2( t, m ); +} + + +float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax ) +{ + float res = 1.0; + float t = mint; + for( int i=0; i<16; i++ ) + { + float h = map( ro + rd*t ).x; + res = min( res, 8.0*h/t ); + t += clamp( h, 0.02, 0.10 ); + if( h<0.001 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); +} + +vec3 calcNormal( in vec3 pos ) +{ + vec2 e = vec2(1.0,-1.0)*0.5773*0.0005; + return normalize( e.xyy*map( pos + e.xyy ).x + + e.yyx*map( pos + e.yyx ).x + + e.yxy*map( pos + e.yxy ).x + + e.xxx*map( pos + e.xxx ).x ); + /* + vec3 eps = vec3( 0.0005, 0.0, 0.0 ); + vec3 nor = vec3( + map(pos+eps.xyy).x - map(pos-eps.xyy).x, + map(pos+eps.yxy).x - map(pos-eps.yxy).x, + map(pos+eps.yyx).x - map(pos-eps.yyx).x ); + return normalize(nor); + */ +} + +float calcAO( in vec3 pos, in vec3 nor ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=0; i<5; i++ ) + { + float hr = 0.01 + 0.12*float(i)/4.0; + vec3 aopos = nor * hr + pos; + float dd = map( aopos ).x; + occ += -(dd-hr)*sca; + sca *= 0.95; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ); +} + +// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm +float checkersGradBox( in vec2 p ) +{ + // filter kernel + vec2 w = fwidth(p) + 0.001; + // analytical integral (box filter) + vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; + // xor pattern + return 0.5 - 0.5*i.x*i.y; +} + +vec3 render( in vec3 ro, in vec3 rd ) +{ + vec3 col = vec3(0.7, 0.9, 1.0) +rd.y*0.8; + vec2 res = castRay(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.45 + 0.35*sin( vec3(0.05,0.08,0.10)*(m-1.0) ); + if( m<1.5 ) + { + + float f = checkersGradBox( 5.0*pos.xz ); + col = 0.3 + f*vec3(0.1); + } + + // lighting + float occ = calcAO( pos, nor ); + vec3 lig = normalize( vec3(cos(-0.4 * runTime), sin(0.7 * runTime), -0.6) ); + vec3 hal = normalize( lig-rd ); + float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + float bac = clamp( dot( nor, normalize(vec3(-lig.x,0.0,-lig.z))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + float dom = smoothstep( -0.1, 0.1, ref.y ); + float fre = pow( clamp(1.0+dot(nor,rd),0.0,1.0), 2.0 ); + + dif *= calcSoftshadow( pos, lig, 0.02, 2.5 ); + dom *= calcSoftshadow( pos, ref, 0.02, 2.5 ); + + float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0)* + dif * + (0.04 + 0.96*pow( clamp(1.0+dot(hal,rd),0.0,1.0), 5.0 )); + + vec3 lin = vec3(0.0); + lin += 1.30*dif*vec3(1.00,0.80,0.55); + lin += 0.40*amb*vec3(0.40,0.60,1.00)*occ; + lin += 0.50*dom*vec3(0.40,0.60,1.00)*occ; + lin += 0.50*bac*vec3(0.25,0.25,0.25)*occ; + lin += 0.25*fre*vec3(1.00,1.00,1.00)*occ; + col = col*lin; + col += 10.00*spe*vec3(1.00,0.90,0.70); + + col = mix( col, vec3(0.8,0.9,1.0), 1.0-exp( -0.0002*t*t*t ) ); + } + + return vec3( clamp(col,0.0,1.0) ); +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = normalize( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +void main() +{ + vec3 tot = vec3(0.0); +#if AA>1 + for( int m=0; m1 + } + tot /= float(AA*AA); +#endif + + gl_FragColor = vec4( tot, 1.0 ); +} From db8b1993632c909f7ad98138e54ebcc09d80e9f6 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 6 Aug 2024 18:32:14 +0200 Subject: [PATCH 012/107] Reviewed shader --- .../resources/shaders/glsl100/raymarching.fs | 2 +- .../resources/shaders/glsl120/raymarching.fs | 26 +------------------ 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/raymarching.fs b/examples/shaders/resources/shaders/glsl100/raymarching.fs index d58d9d3318ab..a7339d2166c7 100644 --- a/examples/shaders/resources/shaders/glsl100/raymarching.fs +++ b/examples/shaders/resources/shaders/glsl100/raymarching.fs @@ -38,7 +38,7 @@ uniform vec2 resolution; // // More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm -#define AA 1 // make this 1 is your machine is too slow +#define AA 1 // make this 1 if your machine is too slow //------------------------------------------------------------------ diff --git a/examples/shaders/resources/shaders/glsl120/raymarching.fs b/examples/shaders/resources/shaders/glsl120/raymarching.fs index d3180ac12a0d..efe4fa108282 100644 --- a/examples/shaders/resources/shaders/glsl120/raymarching.fs +++ b/examples/shaders/resources/shaders/glsl120/raymarching.fs @@ -1,32 +1,8 @@ -// The MIT License -// Copyright © 2013 Inigo Quilez -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// A list of useful distance function to simple primitives, and an example on how to -// do some interesting boolean operations, repetition and displacement. -// -// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm - #version 120 // Input vertex attributes (from vertex shader) varying vec2 fragTexCoord; +varying vec4 fragColor; uniform vec3 viewEye; uniform vec3 viewCenter; From 5af331d708e0fb4d0a978893aa035e72488cf0e5 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 7 Aug 2024 01:01:45 +0200 Subject: [PATCH 013/107] REVIEWED #4206 --- src/rlgl.h | 2 +- src/rtextures.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 3ff6d7e2a750..8ee6026446bb 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4993,7 +4993,7 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - float bytesPerPixel = (float)bpp/8.0f; + double bytesPerPixel = (double)bpp/8.0; dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, diff --git a/src/rtextures.c b/src/rtextures.c index 9359f9219842..5fa01c9dff2d 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5403,7 +5403,7 @@ int GetPixelDataSize(int width, int height, int format) default: break; } - float bytesPerPixel = (float)bpp/8.0f; + double bytesPerPixel = (double)bpp/8.0; dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, From 97c02b2425225982da6f3569d50fb490fab53c10 Mon Sep 17 00:00:00 2001 From: kai-z99 <147789796+kai-z99@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:05:53 -0700 Subject: [PATCH 014/107] [examples] Fix some examples (#4211) * Add text * small typo --- examples/shapes/shapes_draw_circle_sector.c | 8 ++++---- examples/shapes/shapes_draw_rectangle_rounded.c | 10 +++++----- examples/shapes/shapes_draw_ring.c | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/shapes/shapes_draw_circle_sector.c b/examples/shapes/shapes_draw_circle_sector.c index 30eaaf4bd1e6..cb9ab859b4a5 100644 --- a/examples/shapes/shapes_draw_circle_sector.c +++ b/examples/shapes/shapes_draw_circle_sector.c @@ -63,11 +63,11 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 600, 40, 120, 20}, "StartAngle", NULL, &startAngle, 0, 720); - GuiSliderBar((Rectangle){ 600, 70, 120, 20}, "EndAngle", NULL, &endAngle, 0, 720); + GuiSliderBar((Rectangle){ 600, 40, 120, 20}, "StartAngle", TextFormat("%.2f", startAngle), &startAngle, 0, 720); + GuiSliderBar((Rectangle){ 600, 70, 120, 20}, "EndAngle", TextFormat("%.2f", endAngle), &endAngle, 0, 720); - GuiSliderBar((Rectangle){ 600, 140, 120, 20}, "Radius", NULL, &outerRadius, 0, 200); - GuiSliderBar((Rectangle){ 600, 170, 120, 20}, "Segments", NULL, &segments, 0, 100); + GuiSliderBar((Rectangle){ 600, 140, 120, 20}, "Radius", TextFormat("%.2f", outerRadius), &outerRadius, 0, 200); + GuiSliderBar((Rectangle){ 600, 170, 120, 20}, "Segments", TextFormat("%.2f", segments), &segments, 0, 100); //------------------------------------------------------------------------------ minSegments = truncf(ceilf((endAngle - startAngle) / 90)); diff --git a/examples/shapes/shapes_draw_rectangle_rounded.c b/examples/shapes/shapes_draw_rectangle_rounded.c index 19280ed008f2..58991884c025 100644 --- a/examples/shapes/shapes_draw_rectangle_rounded.c +++ b/examples/shapes/shapes_draw_rectangle_rounded.c @@ -66,11 +66,11 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 640, 40, 105, 20 }, "Width", NULL, &width, 0, (float)GetScreenWidth() - 300); - GuiSliderBar((Rectangle){ 640, 70, 105, 20 }, "Height", NULL, &height, 0, (float)GetScreenHeight() - 50); - GuiSliderBar((Rectangle){ 640, 140, 105, 20 }, "Roundness", NULL, &roundness, 0.0f, 1.0f); - GuiSliderBar((Rectangle){ 640, 170, 105, 20 }, "Thickness", NULL, &lineThick, 0, 20); - GuiSliderBar((Rectangle){ 640, 240, 105, 20}, "Segments", NULL, &segments, 0, 60); + GuiSliderBar((Rectangle){ 640, 40, 105, 20 }, "Width", TextFormat("%.2f", width), &width, 0, (float)GetScreenWidth() - 300); + GuiSliderBar((Rectangle){ 640, 70, 105, 20 }, "Height", TextFormat("%.2f", height), &height, 0, (float)GetScreenHeight() - 50); + GuiSliderBar((Rectangle){ 640, 140, 105, 20 }, "Roundness", TextFormat("%.2f", roundness), &roundness, 0.0f, 1.0f); + GuiSliderBar((Rectangle){ 640, 170, 105, 20 }, "Thickness", TextFormat("%.2f", lineThick), &lineThick, 0, 20); + GuiSliderBar((Rectangle){ 640, 240, 105, 20}, "Segments", TextFormat("%.2f", segments), &segments, 0, 60); GuiCheckBox((Rectangle){ 640, 320, 20, 20 }, "DrawRoundedRect", &drawRoundedRect); GuiCheckBox((Rectangle){ 640, 350, 20, 20 }, "DrawRoundedLines", &drawRoundedLines); diff --git a/examples/shapes/shapes_draw_ring.c b/examples/shapes/shapes_draw_ring.c index 60c95a4c0e15..8bccdf729bc9 100644 --- a/examples/shapes/shapes_draw_ring.c +++ b/examples/shapes/shapes_draw_ring.c @@ -69,13 +69,13 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 600, 40, 120, 20 }, "StartAngle", NULL, &startAngle, -450, 450); - GuiSliderBar((Rectangle){ 600, 70, 120, 20 }, "EndAngle", NULL, &endAngle, -450, 450); + GuiSliderBar((Rectangle){ 600, 40, 120, 20 }, "StartAngle", TextFormat("%.2f", startAngle), &startAngle, -450, 450); + GuiSliderBar((Rectangle){ 600, 70, 120, 20 }, "EndAngle", TextFormat("%.2f", endAngle), &endAngle, -450, 450); - GuiSliderBar((Rectangle){ 600, 140, 120, 20 }, "InnerRadius", NULL, &innerRadius, 0, 100); - GuiSliderBar((Rectangle){ 600, 170, 120, 20 }, "OuterRadius", NULL, &outerRadius, 0, 200); + GuiSliderBar((Rectangle){ 600, 140, 120, 20 }, "InnerRadius", TextFormat("%.2f", innerRadius), &innerRadius, 0, 100); + GuiSliderBar((Rectangle){ 600, 170, 120, 20 }, "OuterRadius", TextFormat("%.2f", outerRadius), &outerRadius, 0, 200); - GuiSliderBar((Rectangle){ 600, 240, 120, 20 }, "Segments", NULL, &segments, 0, 100); + GuiSliderBar((Rectangle){ 600, 240, 120, 20 }, "Segments", TextFormat("%.2f", segments), &segments, 0, 100); GuiCheckBox((Rectangle){ 600, 320, 20, 20 }, "Draw Ring", &drawRing); GuiCheckBox((Rectangle){ 600, 350, 20, 20 }, "Draw RingLines", &drawRingLines); From cae0946764a7ef81e92decd5ae279a48ff22fd04 Mon Sep 17 00:00:00 2001 From: freakmangd <53349189+freakmangd@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:53:29 -0400 Subject: [PATCH 015/107] Fix build.zig and use zig fmt (#4242) + `std.mem.split` is deprecated, `splitScalar` seems like the intended choice here + zig files should be formatted according to `zig fmt` --- src/build.zig | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/build.zig b/src/build.zig index 65379c03b4ab..4e112d78f01e 100644 --- a/src/build.zig +++ b/src/build.zig @@ -59,15 +59,15 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. const content = try std.fs.cwd().readFileAlloc(b.allocator, file, std.math.maxInt(usize)); defer b.allocator.free(content); - var lines = std.mem.split(u8, content, "\n"); + var lines = std.mem.splitScalar(u8, content, '\n'); while (lines.next()) |line| { if (!std.mem.containsAtLeast(u8, line, 1, "SUPPORT")) continue; if (std.mem.startsWith(u8, line, "//")) continue; - var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace - flag = flag["#define ".len - 1 ..]; // Remove #define - flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace - flag = flag[0..std.mem.indexOf(u8, flag, " ").?]; // Flag is only one word, so capture till space + var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace + flag = flag["#define ".len - 1 ..]; // Remove #define + flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace + flag = flag[0..std.mem.indexOf(u8, flag, " ").?]; // Flag is only one word, so capture till space flag = try std.fmt.allocPrint(b.allocator, "-D{s}", .{flag}); // Prepend with -D // If user specifies the flag skip it @@ -149,9 +149,8 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. raylib.addLibraryPath(.{ .cwd_relative = "/usr/lib" }); raylib.addIncludePath(.{ .cwd_relative = "/usr/include" }); if (options.linux_display_backend == .X11 or options.linux_display_backend == .Both) { - - raylib.defineCMacro("_GLFW_X11", null); - raylib.linkSystemLibrary("X11"); + raylib.defineCMacro("_GLFW_X11", null); + raylib.linkSystemLibrary("X11"); } if (options.linux_display_backend == .Wayland or options.linux_display_backend == .Both) { From 9ef678d90ae3973113eace62425508fe267bed5d Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 8 Aug 2024 23:54:22 -0700 Subject: [PATCH 016/107] Fix warnings (#4239) * Update raylib_api.* by CI * Fix typecast warnings --------- Co-authored-by: github-actions[bot] --- examples/audio/audio_mixed_processor.c | 2 +- src/rcore.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/audio/audio_mixed_processor.c b/examples/audio/audio_mixed_processor.c index 2df381b26552..df46edc4eaa9 100644 --- a/examples/audio/audio_mixed_processor.c +++ b/examples/audio/audio_mixed_processor.c @@ -97,7 +97,7 @@ int main(void) DrawRectangle(199, 199, 402, 34, LIGHTGRAY); for (int i = 0; i < 400; i++) { - DrawLine(201 + i, 232 - (averageVolume[i] * 32), 201 + i, 232, MAROON); + DrawLine(201 + i, 232 - (int)(averageVolume[i] * 32), 201 + i, 232, MAROON); } DrawRectangleLines(199, 199, 402, 34, GRAY); diff --git a/src/rcore.c b/src/rcore.c index b4bc19f344d4..93ae4f3bb598 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2955,7 +2955,7 @@ float GetGamepadAxisMovement(int gamepad, int axis) float value = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS)) { - float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabs(CORE.Input.Gamepad.axisState[gamepad][axis]); + float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]); // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA if (movement > value + 0.1f) value = CORE.Input.Gamepad.axisState[gamepad][axis]; From 5233b80fb73c557c6836153c4db525c1ffe6c36f Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 01:55:16 -0500 Subject: [PATCH 017/107] update raygui (#4238) --- examples/shapes/raygui.h | 670 +++++++++++++++++++++++++-------------- 1 file changed, 434 insertions(+), 236 deletions(-) diff --git a/examples/shapes/raygui.h b/examples/shapes/raygui.h index 623115fcec0d..16e01a28f423 100644 --- a/examples/shapes/raygui.h +++ b/examples/shapes/raygui.h @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raygui v4.0 - A simple and easy-to-use immediate-mode gui library +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library * * DESCRIPTION: * raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also @@ -26,7 +26,7 @@ * NOTES: * - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for * font atlas recs and glyphs, freeing that memory is (usually) up to the user, -* no unload function is explicitly provided... but note that GuiLoadStyleDefaulf() unloads +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads * by default any previously loaded font (texture, recs, glyphs). * - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions * @@ -141,6 +141,24 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: +* 4.5-dev (Sep-2024) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: Multiple new icons +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* * 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() * ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() * ADDED: Multiple new icons, mostly compiler related @@ -314,9 +332,9 @@ #define RAYGUI_H #define RAYGUI_VERSION_MAJOR 4 -#define RAYGUI_VERSION_MINOR 0 +#define RAYGUI_VERSION_MINOR 5 #define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.0" +#define RAYGUI_VERSION "4.5-dev" #if !defined(RAYGUI_STANDALONE) #include "raylib.h" @@ -441,7 +459,6 @@ } Font; #endif - // Style property // NOTE: Used when exporting style as code for convenience typedef struct GuiStyleProp { @@ -546,7 +563,6 @@ typedef enum { // TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and // should be configured by user as needed while defining the UI layout - // Gui extended properties depend on control // NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) //---------------------------------------------------------------------------------- @@ -562,12 +578,12 @@ typedef enum { TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) TEXT_WRAP_MODE // Text wrap-mode inside text bounds //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline - //TEXT_DECORATION_THICK // Text decoration line thikness + //TEXT_DECORATION_THICK // Text decoration line thickness } GuiDefaultProperty; // Other possible text properties: // TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change -// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... // Label //typedef enum { } GuiLabelProperty; @@ -615,7 +631,9 @@ typedef enum { // DropdownBox typedef enum { ARROW_PADDING = 16, // DropdownBox arrow separation from border and items - DROPDOWN_ITEMS_SPACING // DropdownBox items separation + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) } GuiDropdownBoxProperty; // TextBox/TextBoxMulti/ValueBox/Spinner @@ -635,6 +653,7 @@ typedef enum { LIST_ITEMS_SPACING, // ListView items separation SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_WIDTH // ListView items border width } GuiListViewProperty; // ColorPicker @@ -698,7 +717,6 @@ RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position #endif - // Controls //---------------------------------------------------------------------------------------------------------- // Container/separator controls, useful for controls organization @@ -710,29 +728,30 @@ RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *act RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control // Basic controls set -RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control, shows text +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked -RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, show true when clicked -RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control, returns true when active -RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control, returns active toggle index -RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active -RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control, returns selected item index +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control -RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control, returns selected item -RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control, returns selected value +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text -RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control, returns selected value -RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control, returns selected value -RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control, shows current progress value +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders -RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control, returns mouse cell position +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control // Advance controls set -RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control, returns selected list item index +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret @@ -741,10 +760,9 @@ RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) -RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that returns HSV color value, used by GuiColorPickerHSV() +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() //---------------------------------------------------------------------------------------------------------- - #if !defined(RAYGUI_NO_ICONS) #if !defined(RAYGUI_CUSTOM_ICONS) @@ -972,14 +990,14 @@ typedef enum { ICON_FOLDER = 217, ICON_FILE = 218, ICON_SAND_TIMER = 219, - ICON_220 = 220, - ICON_221 = 221, - ICON_222 = 222, - ICON_223 = 223, - ICON_224 = 224, - ICON_225 = 225, - ICON_226 = 226, - ICON_227 = 227, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, ICON_228 = 228, ICON_229 = 229, ICON_230 = 230, @@ -1290,14 +1308,14 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_220 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_221 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_222 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_223 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_224 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_225 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_226 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_227 + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_228 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 @@ -1328,7 +1346,7 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 }; -// NOTE: We keep a pointer to the icons array, useful to point to other sets if required +// NOTE: A pointer to current icons array should be defined static unsigned int *guiIconsPtr = guiIcons; #endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS @@ -1363,8 +1381,8 @@ static unsigned int guiIconScale = 1; // Gui icon default scale (if ic static bool guiTooltip = false; // Tooltip enabled/disabled static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) -static bool guiSliderDragging = false; // Gui slider drag state (no inputs processed except dragged slider) -static Rectangle guiSliderActive = { 0 }; // Gui slider active bounds rectangle, used as an unique identifier +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking @@ -1450,6 +1468,7 @@ static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) @@ -1744,6 +1763,9 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) if (toggle) *active = i; } + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); @@ -1775,10 +1797,10 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector { #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 int result = 0; GuiState state = guiState; - float mouseWheelSpeed = 20.0f; // Default movement speed with mouse wheel Rectangle temp = { 0 }; if (view == NULL) view = &temp; @@ -1820,17 +1842,8 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector }; // Make sure scroll bars have a minimum width/height - // NOTE: If content >>> bounds, size could be very small or even 0 - if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) - { - horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; - mouseWheelSpeed = 30.0f; // TODO: Calculate speed increment based on content.height vs bounds.height - } - if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) - { - verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; - mouseWheelSpeed = 30.0f; // TODO: Calculate speed increment based on content.width vs bounds.width - } + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; // Calculate view area (area without the scrollbars) *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? @@ -1873,9 +1886,14 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector #endif float wheelMove = GetMouseWheelMove(); + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + // Horizontal and vertical scrolling with mouse wheel - if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed; - else scrollPos.y += wheelMove*mouseWheelSpeed; // Vertical scroll + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll } } @@ -1921,7 +1939,7 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector } // Draw scrollbar lines depending on current state - GuiDrawRectangle(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); // Set scrollbar slider size back to the way it was before GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); @@ -1959,7 +1977,7 @@ int GuiButton(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -1997,7 +2015,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2020,7 +2038,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) return pressed; } -// Toggle Button control, returns true when active +// Toggle Button control int GuiToggle(Rectangle bounds, const char *text, bool *active) { int result = 0; @@ -2031,7 +2049,7 @@ int GuiToggle(Rectangle bounds, const char *text, bool *active) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2068,7 +2086,7 @@ int GuiToggle(Rectangle bounds, const char *text, bool *active) return result; } -// Toggle Group control, returns toggled button codepointIndex +// Toggle Group control int GuiToggleGroup(Rectangle bounds, const char *text, int *active) { #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) @@ -2117,7 +2135,7 @@ int GuiToggleGroup(Rectangle bounds, const char *text, int *active) return result; } -// Toggle Slider control extended, returns true when clicked +// Toggle Slider control extended int GuiToggleSlider(Rectangle bounds, const char *text, int *active) { int result = 0; @@ -2211,7 +2229,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2256,7 +2274,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) return result; } -// Combo Box control, returns selected item codepointIndex +// Combo Box control int GuiComboBox(Rectangle bounds, const char *text, int *active) { int result = 0; @@ -2279,7 +2297,7 @@ int GuiComboBox(Rectangle bounds, const char *text, int *active) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2327,21 +2345,28 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod int result = 0; GuiState state = guiState; + int temp = 0; + if (active == NULL) active = &temp; + int itemSelected = *active; int itemFocused = -1; + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + // Get substrings items from text (items pointers, lengths and count) int itemCount = 0; const char **items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle boundsOpen = bounds; boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); Rectangle itemBounds = bounds; // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiSliderDragging) + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2362,7 +2387,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod for (int i = 0; i < itemCount; i++) { // Update item rectangle y position for next item - itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); if (CheckCollisionPointRec(mousePoint, itemBounds)) { @@ -2406,7 +2432,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod for (int i = 0; i < itemCount; i++) { // Update item rectangle y position for next item - itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); if (i == itemSelected) { @@ -2422,14 +2449,17 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod } } - // Draw arrows (using icon if available) + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) #if defined(RAYGUI_NO_ICONS) - GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); #else - GuiDrawText("#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL #endif + } //-------------------------------------------------------------------- *active = itemSelected; @@ -2440,7 +2470,7 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod // Text Box control // NOTE: Returns true on ENTER pressed (useful for data validation) -int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement @@ -2456,7 +2486,10 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textWidth = GetTextWidth(text) - GetTextWidth(text + textBoxCursorIndex); + int textLength = (int)strlen(text); // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); int textIndexOffset = 0; // Text index offset to start drawing in the box // Cursor rectangle @@ -2496,7 +2529,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if ((state != STATE_DISABLED) && // Control not disabled !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode !guiLocked && // Gui not locked - !guiSliderDragging && // No gui slider on dragging + !guiControlExclusiveMode && // No gui slider on dragging (wrapMode == TEXT_WRAP_NONE)) // No wrap mode { Vector2 mousePosition = GetMousePosition(); @@ -2505,6 +2538,8 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) { state = STATE_PRESSED; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + // If text does not fit in the textbox and current cursor position is out of bounds, // we add an index offset to text for drawing only what requires depending on cursor while (textWidth >= textBounds.width) @@ -2517,19 +2552,16 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); } - int textLength = (int)strlen(text); // Get current text length int codepoint = GetCharPressed(); // Get Unicode codepoint if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - // Encode codepoint as UTF-8 int codepointSize = 0; const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); // Add codepoint to text, at current cursor position // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < bufferSize)) + if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) { // Move forward data from cursor position for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; @@ -2564,6 +2596,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; textLength -= codepointSize; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; // Make sure text last character is EOL text[textLength] = '\0'; @@ -2583,6 +2616,8 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) // Move backward text from cursor position for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; + // TODO Check: >= cursor+codepointsize and <= length-codepointsize + // Prevent cursor index from decrementing past 0 if (textBoxCursorIndex > 0) { @@ -2653,7 +2688,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) { mouseCursor.x = textBounds.x + textEndWidth; - mouseCursorIndex = (int)strlen(text); + mouseCursorIndex = textLength; } // Place cursor at required index on mouse click @@ -2685,7 +2720,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - textBoxCursorIndex = (int)strlen(text); // GLOBAL: Place cursor index to the end of current text + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text result = 1; } } @@ -2727,7 +2762,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) /* // Text Box control with multiple lines and word-wrap // NOTE: This text-box is readonly, no editing supported by default -bool GuiTextBoxMulti(Rectangle bounds, char *text, int bufferSize, bool editMode) +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) { bool pressed = false; @@ -2736,7 +2771,7 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int bufferSize, bool editMode GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); // TODO: Implement methods to calculate cursor position properly - pressed = GuiTextBox(bounds, text, bufferSize, editMode); + pressed = GuiTextBox(bounds, text, textSize, editMode); GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); @@ -2771,7 +2806,7 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2846,7 +2881,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2890,7 +2925,13 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in //if (*value > maxValue) *value = maxValue; //else if (*value < minValue) *value = minValue; - if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } } else { @@ -2930,55 +2971,152 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in return result; } +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //sprintf(textValue, "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) { int result = 0; - float oldValue = *value; GuiState state = guiState; float temp = (maxValue - minValue)/2.0f; if (value == NULL) value = &temp; - - int sliderValue = (int)(((*value - minValue)/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + float oldValue = *value; Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - if (sliderWidth > 0) // Slider - { - slider.x += (sliderValue - sliderWidth/2); - slider.width = (float)sliderWidth; - } - else if (sliderWidth == 0) // SliderBar - { - slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); - slider.width = (float)sliderValue; - } - // Update control //-------------------------------------------------------------------- if ((state != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; - // Get equivalent value and slider position from mousePosition.x - *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; } } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds)) @@ -2986,16 +3124,13 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts if (!CheckCollisionPointRec(mousePoint, slider)) { // Get equivalent value and slider position from mousePosition.x - *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; - - if (sliderWidth > 0) slider.x = mousePoint.x - slider.width/2; // Slider - else if (sliderWidth == 0) slider.width = (float)sliderValue; // SliderBar + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; } } else state = STATE_FOCUSED; @@ -3006,17 +3141,22 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, } // Control value change check - if(oldValue == *value) result = 0; + if (oldValue == *value) result = 0; else result = 1; - // Bar limits check + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); if (sliderWidth > 0) // Slider { + slider.x += sliderValue; + slider.width = (float)sliderWidth; if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); } else if (sliderWidth == 0) // SliderBar { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); } //-------------------------------------------------------------------- @@ -3171,7 +3311,7 @@ int GuiDummyRec(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -3238,7 +3378,7 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -3291,6 +3431,8 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Draw visible items for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) { + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + if (state == STATE_DISABLED) { if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); @@ -3353,83 +3495,32 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd return result; } -// Color Panel control +// Color Panel control - Color (RGBA) variant. int GuiColorPanel(Rectangle bounds, const char *text, Color *color) { int result = 0; - GuiState state = guiState; - Vector2 pickerSelector = { 0 }; - - const Color colWhite = { 255, 255, 255, 255 }; - const Color colBlack = { 0, 0, 0, 255 }; Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. - pickerSelector.x = bounds.x + (float)hsv.y*bounds.width; // HSV: Saturation - pickerSelector.y = bounds.y + (1.0f - (float)hsv.z)*bounds.height; // HSV: Value - - Vector3 maxHue = { hsv.x, 1.0f, 1.0f }; - Vector3 rgbHue = ConvertHSVtoRGB(maxHue); - Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), - (unsigned char)(255.0f*rgbHue.y), - (unsigned char)(255.0f*rgbHue.z), 255 }; + GuiColorPanelHSV(bounds, text, &hsv); - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + // Check if the hsv was changed, only then change the color. + // This is required, because the Color->HSV->Color conversion has precision errors. + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. + // Otherwise GuiColorPanel would often modify it's color without user input. + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) { - Vector2 mousePoint = GetMousePosition(); + Vector3 rgb = ConvertHSVtoRGB(hsv); - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - pickerSelector = mousePoint; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - hsv.y = colorPick.x; - hsv.z = 1.0f - colorPick.y; - - Vector3 rgb = ConvertHSVtoRGB(hsv); - - // NOTE: Vector3ToColor() only available on raylib 1.8.1 - *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), - (unsigned char)(255.0f*rgb.y), - (unsigned char)(255.0f*rgb.z), - (unsigned char)(255.0f*(float)color->a/255.0f) }; - - } - else state = STATE_FOCUSED; - } + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); - DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); - - // Draw color picker: selector - Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; - GuiDrawRectangle(selector, 0, BLANK, colWhite); - } - else - { - DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); - } - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - //-------------------------------------------------------------------- - return result; } @@ -3451,11 +3542,11 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -3466,8 +3557,8 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) @@ -3475,8 +3566,8 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts *alpha = (mousePoint.x - bounds.x)/bounds.width; if (*alpha <= 0.0f) *alpha = 0.0f; @@ -3537,11 +3628,11 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -3552,8 +3643,8 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) @@ -3561,8 +3652,8 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts *hue = (mousePoint.y - bounds.y)*360/bounds.height; if (*hue <= 0.0f) *hue = 0.0f; @@ -3615,6 +3706,7 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) // float GuiColorBarAlpha(Rectangle bounds, float alpha) // float GuiColorBarHue(Rectangle bounds, float value) // NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead int GuiColorPicker(Rectangle bounds, const char *text, Color *color) { int result = 0; @@ -3627,6 +3719,7 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); GuiColorBarHue(boundsHue, NULL, &hsv.x); @@ -3668,8 +3761,7 @@ int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) return result; } -// Color Panel control, returns HSV color value in *colorHsv. -// Used by GuiColorPickerHSV() +// Color Panel control - HSV variant int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) { int result = 0; @@ -3690,15 +3782,47 @@ int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); - if (CheckCollisionPointRec(mousePoint, bounds)) + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; pickerSelector = mousePoint; // Calculate color from picker @@ -3760,9 +3884,9 @@ int GuiMessageBox(Rectangle bounds, const char *title, const char *message, cons int textWidth = GetTextWidth(message) + 2; Rectangle textBounds = { 0 }; - textBounds.x = bounds.x + bounds.width/2 - textWidth/2; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.width = (float)textWidth; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; // Draw control @@ -3905,7 +4029,7 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { if (CheckCollisionPointRec(mousePoint, bounds)) { @@ -3925,14 +4049,14 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect // Draw vertical grid lines for (int i = 0; i < linesV; i++) { - Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height }; + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); } // Draw horizontal grid lines for (int i = 0; i < linesH; i++) { - Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width, 1 }; + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); } } @@ -3954,7 +4078,6 @@ void GuiDisableTooltip(void) { guiTooltip = false; } // Set tooltip string void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } - //---------------------------------------------------------------------------------- // Styles loading functions //---------------------------------------------------------------------------------- @@ -3967,6 +4090,7 @@ void GuiLoadStyle(const char *fileName) #define MAX_LINE_BUFFER_SIZE 256 bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); // Try reading the files as text file first FILE *rgsFile = fopen(fileName, "rt"); @@ -4600,7 +4724,7 @@ static int GetTextWidth(const char *text) } } - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE - ICON_TEXT_PADDING); + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); } return (int)textSize.x; @@ -4773,6 +4897,7 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C // Get text position depending on alignment and iconId //--------------------------------------------------------------------------------- Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; // NOTE: We get text size after icon has been processed // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? @@ -4798,6 +4923,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C default: break; } + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + switch (alignmentVertical) { // Only valid in case of wordWrap = 0; @@ -4820,7 +4947,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C { // NOTE: We consider icon height, probably different than text size GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); - textBoundsPosition.x += (RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); } #endif // Get size in bytes of text, @@ -4829,9 +4957,15 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + int textOffsetY = 0; float textOffsetX = 0.0f; float glyphWidth = 0; + + int ellipsisWidth = GetTextWidth("..."); + bool textOverflow = false; for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) { int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); @@ -4839,36 +4973,51 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - // Wrap mode text measuring to space to validate if it can be drawn or - // a new line is required + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required if (wrapMode == TEXT_WRAP_CHAR) { - // Get glyph width to check if it goes out of bounds - if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); - else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - // Jump to next line if current character reach end of the box limits - if ((textOffsetX + glyphWidth) > textBounds.width) + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) { textOffsetX = 0.0f; textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } } } else if (wrapMode == TEXT_WRAP_WORD) { + if (codepoint == 32) lastSpaceIndex = c; + // Get width to next space in line int nextSpaceIndex = 0; float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); - if ((textOffsetX + nextSpaceWidth) > textBounds.width) + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) { textOffsetX = 0.0f; textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); } - - // TODO: Consider case: (nextSpaceWidth >= textBounds.width) } if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint @@ -4881,7 +5030,23 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C if (wrapMode == TEXT_WRAP_NONE) { // Draw only required text glyphs fitting the textBounds.width - if (textOffsetX <= (textBounds.width - glyphWidth)) + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else { DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); } @@ -4937,7 +5102,7 @@ static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, // Draw tooltip using control bounds static void GuiTooltip(Rectangle controlRec) { - if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiSliderDragging) + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) { Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); @@ -5003,7 +5168,7 @@ static const char **GuiTextSplit(const char *text, char delimiter, int *count, i buffer[i] = '\0'; // Set an end of string at this point counter++; - if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; } } @@ -5163,8 +5328,11 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) if (value > maxValue) value = maxValue; if (value < minValue) value = minValue; - const int valueRange = maxValue - minValue; + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size // Calculate rectangles for all of the components arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ @@ -5205,13 +5373,13 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && !CheckCollisionPointRec(mousePoint, arrowUpLeft) && !CheckCollisionPointRec(mousePoint, arrowDownRight)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -5221,8 +5389,8 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds)) @@ -5236,8 +5404,8 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) // Handle mouse button down if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts // Check arrows click if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); @@ -5437,6 +5605,37 @@ static int TextToInteger(const char *text) return value*sign; } +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + // Encode codepoint into UTF-8 text (char array size returned as parameter) static const char *CodepointToUTF8(int codepoint, int *byteSize) { @@ -5490,21 +5689,21 @@ static int GetCodepointNext(const char *text, int *codepointSize) if (0xf0 == (0xf8 & ptr[0])) { // 4 byte UTF-8 codepoint - if(((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); *codepointSize = 4; } else if (0xe0 == (0xf0 & ptr[0])) { // 3 byte UTF-8 codepoint */ - if(((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); *codepointSize = 3; } else if (0xc0 == (0xe0 & ptr[0])) { // 2 byte UTF-8 codepoint - if((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); *codepointSize = 2; } @@ -5515,9 +5714,8 @@ static int GetCodepointNext(const char *text, int *codepointSize) *codepointSize = 1; } - return codepoint; } #endif // RAYGUI_STANDALONE -#endif // RAYGUI_IMPLEMENTATION +#endif // RAYGUI_IMPLEMENTATION \ No newline at end of file From 070c1c9d6357e07672db6e2fc36e37a2a30ee146 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:03:27 -0500 Subject: [PATCH 018/107] update examples missing unloadtexture (#4234) --- examples/models/models_yaw_pitch_roll.c | 1 + examples/shaders/shaders_lightmap.c | 2 ++ examples/shaders/shaders_texture_drawing.c | 1 + examples/shaders/shaders_vertex_displacement.c | 1 + 4 files changed, 5 insertions(+) diff --git a/examples/models/models_yaw_pitch_roll.c b/examples/models/models_yaw_pitch_roll.c index 91a11f73c4ac..5374c035ea95 100644 --- a/examples/models/models_yaw_pitch_roll.c +++ b/examples/models/models_yaw_pitch_roll.c @@ -114,6 +114,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- UnloadModel(model); // Unload model data + UnloadTexture(texture); // Unload texture data CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_lightmap.c b/examples/shaders/shaders_lightmap.c index 01b80a6fbb75..91ed8bfd27ad 100644 --- a/examples/shaders/shaders_lightmap.c +++ b/examples/shaders/shaders_lightmap.c @@ -164,6 +164,8 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadMesh(mesh); // Unload the mesh UnloadShader(shader); // Unload shader + UnloadTexture(texture); // Unload texture + UnloadTexture(light); // Unload texture CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_texture_drawing.c b/examples/shaders/shaders_texture_drawing.c index 52b8967f60f8..ada44299d11e 100644 --- a/examples/shaders/shaders_texture_drawing.c +++ b/examples/shaders/shaders_texture_drawing.c @@ -77,6 +77,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- UnloadShader(shader); + UnloadTexture(texture); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c index c211d349fa52..207a237d0548 100644 --- a/examples/shaders/shaders_vertex_displacement.c +++ b/examples/shaders/shaders_vertex_displacement.c @@ -110,6 +110,7 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadShader(shader); UnloadModel(planeModel); + UnloadTexture(perlinNoiseMap); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- From fe9e371f277f8f906f2b4ed9485437fa68f53058 Mon Sep 17 00:00:00 2001 From: NishiOwO <89888985+NishiOwO@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:04:18 +0900 Subject: [PATCH 019/107] [miniaudio] Fixing miniaudio and Makefile for NetBSD (#4212) * fixing miniaudio * another fix for NetBSD * adding wl-r. should not affect other bsd --- src/Makefile | 4 ++-- src/external/miniaudio.h | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index c68a037978f9..4740f1f01b8b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -467,7 +467,7 @@ INCLUDE_PATHS = -I. ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) INCLUDE_PATHS += -Iexternal/glfw/include ifeq ($(PLATFORM_OS),BSD) - INCLUDE_PATHS += -I/usr/local/include + INCLUDE_PATHS += -I/usr/local/include -I/usr/pkg/include -I/usr/X11R7/include endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) @@ -522,7 +522,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) LDFLAGS += -Wl,-soname,lib$(RAYLIB_LIB_NAME).so.$(RAYLIB_API_VERSION) endif ifeq ($(PLATFORM_OS),BSD) - LDFLAGS += -Wl,-soname,lib$(RAYLIB_LIB_NAME).$(RAYLIB_API_VERSION).so -Lsrc -L/usr/local/lib + LDFLAGS += -Wl,-soname,lib$(RAYLIB_LIB_NAME).$(RAYLIB_API_VERSION).so -Lsrc -L/usr/local/lib -L/usr/pkg/lib -Wl,-R/usr/pkg/lib endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) diff --git a/src/external/miniaudio.h b/src/external/miniaudio.h index 787c626ad18c..ad113337ee95 100644 --- a/src/external/miniaudio.h +++ b/src/external/miniaudio.h @@ -36078,9 +36078,15 @@ static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext ma_uint32 channels; ma_uint32 sampleRate; +#ifdef __NetBSD__ + if (ioctl(fd, AUDIO_GETFORMAT, &fdInfo) < 0) { + return MA_ERROR; + } +#else if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { return MA_ERROR; } +#endif if (deviceType == ma_device_type_playback) { channels = fdInfo.play.channels; @@ -36358,7 +36364,11 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We're using a default device. Get the info from the /dev/audioctl file instead of /dev/audio. */ int fdctl = open(pDefaultDeviceCtlNames[iDefaultDevice], fdFlags, 0); if (fdctl != -1) { +#ifdef __NetBSD__ + fdInfoResult = ioctl(fdctl, AUDIO_GETFORMAT, &fdInfo); +#else fdInfoResult = ioctl(fdctl, AUDIO_GETINFO, &fdInfo); +#endif close(fdctl); } } From 2590a30d04b4aa4d35787052af55fb96bd699c42 Mon Sep 17 00:00:00 2001 From: Maxim Knyazkin <42771130+MaximKn1@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:05:46 +0300 Subject: [PATCH 020/107] [rlgl] Adding warnings in case OpenGL 4.3 is not enabled (#4202) * Adding warnings for OpenGL 4.3 * Removed logging from frequently called functions --- src/rlgl.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index 8ee6026446bb..baf89a78231c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4083,6 +4083,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #else + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; } @@ -4108,6 +4110,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #else + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; } @@ -4348,6 +4352,8 @@ unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); #endif return program; @@ -4372,6 +4378,8 @@ unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHi glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); #endif return ssbo; @@ -4382,7 +4390,10 @@ void rlUnloadShaderBuffer(unsigned int ssboId) { #if defined(GRAPHICS_API_OPENGL_43) glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); #endif + } // Update SSBO buffer data @@ -4442,6 +4453,8 @@ void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool re rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); #endif } From 243801c2d1592910669ff11bc6e9b45c2e7507d9 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:07:56 -0500 Subject: [PATCH 021/107] update text writing anim (#4230) --- examples/text/text_writing_anim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_writing_anim.c b/examples/text/text_writing_anim.c index 1e7cd569f48d..8e2820fc93b5 100644 --- a/examples/text/text_writing_anim.c +++ b/examples/text/text_writing_anim.c @@ -52,7 +52,7 @@ int main(void) DrawText(TextSubtext(message, 0, framesCounter/10), 210, 160, 20, MAROON); DrawText("PRESS [ENTER] to RESTART!", 240, 260, 20, LIGHTGRAY); - DrawText("PRESS [SPACE] to SPEED UP!", 239, 300, 20, LIGHTGRAY); + DrawText("HOLD [SPACE] to SPEED UP!", 239, 300, 20, LIGHTGRAY); EndDrawing(); //---------------------------------------------------------------------------------- From 85c6489d811fc7f5de4d379ad10b4bb071bc871c Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:12:26 -0500 Subject: [PATCH 022/107] update models box collisions (#4224) --- examples/models/models_box_collisions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/models/models_box_collisions.c b/examples/models/models_box_collisions.c index 936f65f38a3e..4d98a776a539 100644 --- a/examples/models/models_box_collisions.c +++ b/examples/models/models_box_collisions.c @@ -109,7 +109,7 @@ int main(void) EndMode3D(); - DrawText("Move player with cursors to collide", 220, 40, 20, GRAY); + DrawText("Move player with arrow keys to collide", 220, 40, 20, GRAY); DrawFPS(10, 10); From 06aeb214296a9c51660e4695114244d4acfc58b1 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:15:07 -0500 Subject: [PATCH 023/107] update shaders basic pbr (#4225) --- examples/shaders/shaders_basic_pbr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c index d025ab8a9d88..c3f2bad88bc3 100644 --- a/examples/shaders/shaders_basic_pbr.c +++ b/examples/shaders/shaders_basic_pbr.c @@ -236,7 +236,7 @@ int main() float emissiveIntensity = 0.01f; SetShaderValue(shader, emissiveIntensityLoc, &emissiveIntensity, SHADER_UNIFORM_FLOAT); - DrawModel(car, (Vector3){ 0.0f, 0.0f, 0.0f }, 0.005f, WHITE); // Draw car model + DrawModel(car, (Vector3){ 0.0f, 0.0f, 0.0f }, 0.25f, WHITE); // Draw car model // Draw spheres to show the lights positions for (int i = 0; i < MAX_LIGHTS; i++) From 3c5bdae7abffeefe90d927572b5ea443ea50411b Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:16:05 -0500 Subject: [PATCH 024/107] update shapes bouncing ball (#4226) --- examples/shapes/shapes_bouncing_ball.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shapes/shapes_bouncing_ball.c b/examples/shapes/shapes_bouncing_ball.c index 7a30681639e2..df0c37c1c95f 100644 --- a/examples/shapes/shapes_bouncing_ball.c +++ b/examples/shapes/shapes_bouncing_ball.c @@ -62,7 +62,7 @@ int main(void) ClearBackground(RAYWHITE); DrawCircleV(ballPosition, (float)ballRadius, MAROON); - //DrawText("PRESS SPACE to PAUSE BALL MOVEMENT", 10, GetScreenHeight() - 25, 20, LIGHTGRAY); + DrawText("PRESS SPACE to PAUSE BALL MOVEMENT", 10, GetScreenHeight() - 25, 20, LIGHTGRAY); // On pause, we draw a blinking message if (pause && ((framesCounter/30)%2)) DrawText("PAUSED", 350, 200, 30, GRAY); From e4529ff8f9d2e03f29e5271fa3eea0c603174925 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:18:00 -0500 Subject: [PATCH 025/107] update text input box (#4229) --- examples/text/text_input_box.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_input_box.c b/examples/text/text_input_box.c index eeb338f1f7b3..c530577111a7 100644 --- a/examples/text/text_input_box.c +++ b/examples/text/text_input_box.c @@ -35,7 +35,7 @@ int main(void) int framesCounter = 0; - SetTargetFPS(10); // Set our game to run at 10 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop From 13491a485a5f29d00f8fe23e796501bec67a7378 Mon Sep 17 00:00:00 2001 From: Maxim Knyazkin <42771130+MaximKn1@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:02:18 +0300 Subject: [PATCH 026/107] Fixed compilation for OpenGL ES (#4243) --- src/rlgl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index baf89a78231c..19c529398be2 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4083,7 +4083,7 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; - #else + #elif defined(GRAPHICS_API_OPENGL_33) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; @@ -4110,7 +4110,7 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; - #else + #elif defined(GRAPHICS_API_OPENGL_33) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; From 46cb6af4375da00563c68c6bbe4c24dcdf885b60 Mon Sep 17 00:00:00 2001 From: Asdqwe Date: Fri, 9 Aug 2024 14:03:14 -0300 Subject: [PATCH 027/107] Fix core_input_gamepad_info example so all buttons are displayed within the window (#4241) --- examples/core/core_input_gamepad_info.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/core/core_input_gamepad_info.c b/examples/core/core_input_gamepad_info.c index 66fedda90065..0962946810ee 100644 --- a/examples/core/core_input_gamepad_info.c +++ b/examples/core/core_input_gamepad_info.c @@ -47,25 +47,25 @@ int main(void) ClearBackground(RAYWHITE); - for (int i = 0, y = 10; i < 4; i++) // MAX_GAMEPADS = 4 + for (int i = 0, y = 5; i < 4; i++) // MAX_GAMEPADS = 4 { if (IsGamepadAvailable(i)) { - DrawText(TextFormat("Gamepad name: %s", GetGamepadName(i)), 10, y, 20, BLACK); - y += 30; - DrawText(TextFormat("\tAxis count: %d", GetGamepadAxisCount(i)), 10, y, 20, BLACK); - y += 30; + DrawText(TextFormat("Gamepad name: %s", GetGamepadName(i)), 10, y, 10, BLACK); + y += 11; + DrawText(TextFormat("\tAxis count: %d", GetGamepadAxisCount(i)), 10, y, 10, BLACK); + y += 11; for (int axis = 0; axis < GetGamepadAxisCount(i); axis++) { - DrawText(TextFormat("\tAxis %d = %f", axis, GetGamepadAxisMovement(i, axis)), 10, y, 20, BLACK); - y += 30; + DrawText(TextFormat("\tAxis %d = %f", axis, GetGamepadAxisMovement(i, axis)), 10, y, 10, BLACK); + y += 11; } for (int button = 0; button < 32; button++) { - DrawText(TextFormat("\tButton %d = %d", button, IsGamepadButtonDown(i, button)), 10, y, 20, BLACK); - y += 30; + DrawText(TextFormat("\tButton %d = %d", button, IsGamepadButtonDown(i, button)), 10, y, 10, BLACK); + y += 11; } } } @@ -80,4 +80,4 @@ int main(void) //-------------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- -} \ No newline at end of file +} From 418b8780533e6d7822aefcd1a1dd3cb6cca95fa5 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:07:23 -0500 Subject: [PATCH 028/107] [Examples] set FPS to 60 (#4235) * set FPS to 60 * remove extra commented lines --- examples/core/core_vr_simulator.c | 2 +- examples/textures/textures_blend_modes.c | 3 +++ examples/textures/textures_image_rotate.c | 2 ++ examples/textures/textures_logo_raylib.c | 2 ++ examples/textures/textures_raw_data.c | 2 ++ examples/textures/textures_sprite_explosion.c | 4 ++-- examples/textures/textures_to_image.c | 2 ++ 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/core/core_vr_simulator.c b/examples/core/core_vr_simulator.c index c98fce9313a9..021698edb35d 100644 --- a/examples/core/core_vr_simulator.c +++ b/examples/core/core_vr_simulator.c @@ -100,7 +100,7 @@ int main(void) DisableCursor(); // Limit cursor to relative movement inside the window - SetTargetFPS(90); // Set our game to run at 90 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_blend_modes.c b/examples/textures/textures_blend_modes.c index 660aa57a789b..d97432cb222f 100644 --- a/examples/textures/textures_blend_modes.c +++ b/examples/textures/textures_blend_modes.c @@ -43,6 +43,9 @@ int main(void) const int blendCountMax = 4; BlendMode blendMode = 0; + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- + // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { diff --git a/examples/textures/textures_image_rotate.c b/examples/textures/textures_image_rotate.c index 51724e0372ed..94c42034b6eb 100644 --- a/examples/textures/textures_image_rotate.c +++ b/examples/textures/textures_image_rotate.c @@ -43,6 +43,8 @@ int main(void) textures[2] = LoadTextureFromImage(imageNeg90); int currentTexture = 0; + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_logo_raylib.c b/examples/textures/textures_logo_raylib.c index f170dab48b2f..35baa1223747 100644 --- a/examples/textures/textures_logo_raylib.c +++ b/examples/textures/textures_logo_raylib.c @@ -27,6 +27,8 @@ int main(void) // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) Texture2D texture = LoadTexture("resources/raylib_logo.png"); // Texture loading + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_raw_data.c b/examples/textures/textures_raw_data.c index 38229f1ba49e..6fb41b143ab7 100644 --- a/examples/textures/textures_raw_data.c +++ b/examples/textures/textures_raw_data.c @@ -63,6 +63,8 @@ int main(void) Texture2D checked = LoadTextureFromImage(checkedIm); UnloadImage(checkedIm); // Unload CPU (RAM) image data (pixels) + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_sprite_explosion.c b/examples/textures/textures_sprite_explosion.c index 06b4f1dad769..a65426cb6267 100644 --- a/examples/textures/textures_sprite_explosion.c +++ b/examples/textures/textures_sprite_explosion.c @@ -48,8 +48,8 @@ int main(void) bool active = false; int framesCounter = 0; - SetTargetFPS(120); - //-------------------------------------------------------------------------------------- + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key diff --git a/examples/textures/textures_to_image.c b/examples/textures/textures_to_image.c index 2d09623ebb28..e1d88c4cd498 100644 --- a/examples/textures/textures_to_image.c +++ b/examples/textures/textures_to_image.c @@ -38,6 +38,8 @@ int main(void) texture = LoadTextureFromImage(image); // Recreate texture from retrieved image data (RAM -> VRAM) UnloadImage(image); // Unload retrieved image data from CPU memory (RAM) + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop From 65c400354646fd43f54b9f832acc80ee6f63d1e2 Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:47:52 +0300 Subject: [PATCH 029/107] Make camera movement independant of framerate (#4247) Instead of moving camera with constant speed per frame, speed is multiplied with delta time before movement. --- src/rcamera.h | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/rcamera.h b/src/rcamera.h index d3042d3064bc..c7f6fed3ac43 100644 --- a/src/rcamera.h +++ b/src/rcamera.h @@ -196,12 +196,12 @@ RLAPI Matrix GetCameraProjectionMatrix(Camera* camera, float aspect); //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define CAMERA_MOVE_SPEED 0.09f +#define CAMERA_MOVE_SPEED 5.4f // Units per second #define CAMERA_ROTATION_SPEED 0.03f #define CAMERA_PAN_SPEED 0.2f // Camera mouse movement sensitivity -#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f // TODO: it should be independant of framerate +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f // Camera orbital speed in CAMERA_ORBITAL mode #define CAMERA_ORBITAL_SPEED 0.5f // Radians per second @@ -481,10 +481,11 @@ void UpdateCamera(Camera *camera, int mode) } // Keyboard support - if (IsKeyDown(KEY_W)) CameraMoveForward(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); - if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (IsKeyDown(KEY_S)) CameraMoveForward(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (IsKeyDown(KEY_D)) CameraMoveRight(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); + float cameraMoveSpeed = CAMERA_MOVE_SPEED*GetFrameTime(); + if (IsKeyDown(KEY_W)) CameraMoveForward(camera, cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_S)) CameraMoveForward(camera, -cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_D)) CameraMoveRight(camera, cameraMoveSpeed, moveInWorldPlane); // Gamepad movement if (IsGamepadAvailable(0)) @@ -493,16 +494,16 @@ void UpdateCamera(Camera *camera, int mode) CameraYaw(camera, -(GetGamepadAxisMovement(0, GAMEPAD_AXIS_RIGHT_X)*2)*CAMERA_MOUSE_MOVE_SENSITIVITY, rotateAroundTarget); CameraPitch(camera, -(GetGamepadAxisMovement(0, GAMEPAD_AXIS_RIGHT_Y)*2)*CAMERA_MOUSE_MOVE_SENSITIVITY, lockView, rotateAroundTarget, rotateUp); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) <= -0.25f) CameraMoveForward(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) <= -0.25f) CameraMoveRight(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) >= 0.25f) CameraMoveForward(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) >= 0.25f) CameraMoveRight(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) <= -0.25f) CameraMoveForward(camera, cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) <= -0.25f) CameraMoveRight(camera, -cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) >= 0.25f) CameraMoveForward(camera, -cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) >= 0.25f) CameraMoveRight(camera, cameraMoveSpeed, moveInWorldPlane); } if (mode == CAMERA_FREE) { - if (IsKeyDown(KEY_SPACE)) CameraMoveUp(camera, CAMERA_MOVE_SPEED); - if (IsKeyDown(KEY_LEFT_CONTROL)) CameraMoveUp(camera, -CAMERA_MOVE_SPEED); + if (IsKeyDown(KEY_SPACE)) CameraMoveUp(camera, cameraMoveSpeed); + if (IsKeyDown(KEY_LEFT_CONTROL)) CameraMoveUp(camera, -cameraMoveSpeed); } } From 6e644a27fc1c3d3499750a0e58a0ba5c2a4a0ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:16:07 -0400 Subject: [PATCH 030/107] Change some global variables to have internal linkage (#4252) * Change some global variables to have internal linkage * Update rcore.c * Update rcore.c --- src/rcore.c | 6 +++--- src/rshapes.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 93ae4f3bb598..5485ce8ce032 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -367,9 +367,9 @@ static int screenshotCounter = 0; // Screenshots counter #endif #if defined(SUPPORT_GIF_RECORDING) -unsigned int gifFrameCounter = 0; // GIF frames counter -bool gifRecording = false; // GIF recording state -MsfGifState gifState = { 0 }; // MSGIF context state +static unsigned int gifFrameCounter = 0; // GIF frames counter +static bool gifRecording = false; // GIF recording state +static MsfGifState gifState = { 0 }; // MSGIF context state #endif #if defined(SUPPORT_AUTOMATION_EVENTS) diff --git a/src/rshapes.c b/src/rshapes.c index 89339708a319..676b2521a06b 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -79,8 +79,8 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (white pixel loaded by rlgl) -Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; // Texture source rectangle used on shapes drawing +static Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (white pixel loaded by rlgl) +static Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; // Texture source rectangle used on shapes drawing //---------------------------------------------------------------------------------- // Module specific Functions Declaration From 63ae57d2e31afa8bc8cab7f17f0e44afa4e7552d Mon Sep 17 00:00:00 2001 From: Brian E <72316548+Brian-ED@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:05:34 +0100 Subject: [PATCH 031/107] Added raylib-APL to BINDINGS.md (#4253) * Added raylib-APL to BINDINGS.md * added back the newline * Made APL binding be version 5.0 instead of auto Also alligned the bars of the new entry. --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 3e840d58791f..7fd0dfd93a5e 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -83,6 +83,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-raku](https://github.com/vushu/raylib-raku) | **auto** | [Raku](https://www.raku.org) | Artistic License 2.0 | | [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) | 4.5 | [Lean4](https://lean-lang.org) | BSD-3-Clause | | [raylib-cobol](https://codeberg.org/glowiak/raylib-cobol) | **auto** | [COBOL](https://gnucobol.sourceforge.io) | Public domain | +| [raylib-apl](https://github.com/Brian-ED/raylib-apl) | **5.0** | [Dyalog APL](https://www.dyalog.com/) | MIT | ### Utility Wrapers From fa374f9cc902a37836cc66ea7d958d9be87057a3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 17 Aug 2024 00:44:33 +0200 Subject: [PATCH 032/107] Update webassembly.yml --- .github/workflows/webassembly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/webassembly.yml b/.github/workflows/webassembly.yml index f9e51680dd90..f676877fdb41 100644 --- a/.github/workflows/webassembly.yml +++ b/.github/workflows/webassembly.yml @@ -29,7 +29,7 @@ jobs: - name: Setup emsdk uses: mymindstorm/setup-emsdk@v14 with: - version: 3.1.54 + version: 3.1.64 actions-cache-folder: 'emsdk-cache' - name: Setup Release Version From f70d8a33cb8a3cefe0cc261e945d4f6715c6c0bc Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 17 Aug 2024 00:46:08 +0200 Subject: [PATCH 033/107] REVIEWED: Shader load failing returns 0, instead of fallback --- src/rlgl.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 19c529398be2..5fb9497cd20a 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3994,18 +3994,18 @@ unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) unsigned int fragmentShaderId = 0; // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - // In case no vertex shader was provided or compilation failed, we use default vertex shader - if (vertexShaderId == 0) vertexShaderId = RLGL.State.defaultVShaderId; + else vertexShaderId = RLGL.State.defaultVShaderId; // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - // In case no fragment shader was provided or compilation failed, we use default fragment shader - if (fragmentShaderId == 0) fragmentShaderId = RLGL.State.defaultFShaderId; + else fragmentShaderId = RLGL.State.defaultFShaderId; // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; - else + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) { // One of or both shader are new, we need to compile a new shader program id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); @@ -4100,6 +4100,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); RL_FREE(log); } + + shader = 0; } else { From 308b77cd42c42406a3bd4eac250761476ff0407e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:07:23 -0400 Subject: [PATCH 034/107] Fix warnings (#4251) * Update rmodels.c fix these warnings: ``` /src/rmodels.c:5744:17: warning: missing initializer for field 'w' of 'Vector4' [-Wmissing-field-initializers] [build] 5744 | Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2]}; [build] | ^~~~~~~ ``` * Update rcore_web.c fix warnings --- src/platforms/rcore_web.c | 22 +++++++++++----------- src/rmodels.c | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 937e15acd491..ff7a92dbccce 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -129,7 +129,7 @@ static void CursorEnterCallback(GLFWwindow *window, int enter); // Emscripten window callback events static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); -static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +// static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); // Emscripten input callback events @@ -981,7 +981,7 @@ void PollInputEvents(void) default: break; } - if (button != -1) // Check for valid button + if (button + 1 != 0) // Check for valid button { if (gamepadState.digitalButton[j] == 1) { @@ -1572,12 +1572,12 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte } // Register window resize event -static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) -{ - // TODO: Implement EmscriptenWindowResizedCallback()? +// static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +// { +// // TODO: Implement EmscriptenWindowResizedCallback()? - return 1; // The event was consumed by the callback handler -} +// return 1; // The event was consumed by the callback handler +// } EM_JS(int, GetWindowInnerWidth, (), { return window.innerWidth; }); EM_JS(int, GetWindowInnerHeight, (), { return window.innerHeight; }); @@ -1593,11 +1593,11 @@ static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent * int width = GetWindowInnerWidth(); int height = GetWindowInnerHeight(); - if (width < CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; - else if (width > CORE.Window.screenMax.width && CORE.Window.screenMax.width > 0) width = CORE.Window.screenMax.width; + if (width < (int)CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; + else if (width > (int)CORE.Window.screenMax.width && CORE.Window.screenMax.width > 0) width = CORE.Window.screenMax.width; - if (height < CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; - else if (height > CORE.Window.screenMax.height && CORE.Window.screenMax.height > 0) height = CORE.Window.screenMax.height; + if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; + else if (height > (int)CORE.Window.screenMax.height && CORE.Window.screenMax.height > 0) height = CORE.Window.screenMax.height; emscripten_set_canvas_element_size("#canvas", width, height); diff --git a/src/rmodels.c b/src/rmodels.c index c1d56765e80d..36a7e254d1f3 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5741,11 +5741,11 @@ static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_ cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 4); Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 4); - Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2]}; + Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2], 0.0f}; cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4); Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4); - Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2]}; + Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2], 0.0f}; Vector4 *r = data; v1 = QuaternionNormalize(v1); From b432aa2b9cd5eb35391636b6e147db8d9a9cf1cc Mon Sep 17 00:00:00 2001 From: Colleague Riley Date: Sat, 17 Aug 2024 08:00:54 -0400 Subject: [PATCH 035/107] Update RGFW (#4259) * update RGFW * fix bug with GetCurrentMonitor * update RGFW * update RGFW * clean up merge * update RGFW --- src/external/RGFW.h | 3178 ++++++++++++++++++++-------- src/platforms/rcore_desktop_rgfw.c | 6 +- 2 files changed, 2274 insertions(+), 910 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 5420ccab1eab..367a46fa608e 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -57,6 +57,8 @@ #define RGFW_EXPORT - Use when building RGFW #define RGFW_IMPORT - Use when linking with RGFW (not as a single-header) + + #define RGFW_STD_INT - force the use stdint.h (for systems that might not have stdint.h (msvc)) */ /* @@ -81,6 +83,7 @@ Code-Nycticebus -> bug fixes Rob Rohan -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs AICDG (@THISISAGOODNAME) -> vulkan support (example) + @Easymode -> support, testing/debugging, bug fixes and reviews */ #if _MSC_VER @@ -93,6 +96,11 @@ #ifndef RGFW_MALLOC #include + + #ifndef __USE_POSIX199309 + #define __USE_POSIX199309 + #endif + #include #define RGFW_MALLOC malloc #define RGFW_CALLOC calloc @@ -131,7 +139,7 @@ #endif #ifndef RGFWDEF - #ifdef __APPLE__ + #ifdef __clang__ #define RGFWDEF static inline #else #define RGFWDEF inline @@ -146,7 +154,7 @@ #define RGFW_UNUSED(x) (void)(x); #endif -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) extern "C" { #endif @@ -156,7 +164,7 @@ #define RGFW_HEADER #if !defined(u8) - #if defined(_MSC_VER) || defined(__SYMBIAN32__) /* MSVC might not have stdint.h */ + #if ((defined(_MSC_VER) || defined(__SYMBIAN32__)) && !defined(RGFW_STD_INT)) /* MSVC might not have stdint.h */ typedef unsigned char u8; typedef signed char i8; typedef unsigned short u16; @@ -182,10 +190,11 @@ #if !defined(b8) /* RGFW bool type */ typedef u8 b8; typedef u32 b32; - #define RGFW_TRUE 1 - #define RGFW_FALSE 0 #endif +#define RGFW_TRUE 1 +#define RGFW_FALSE 0 + /* thse OS macros looks better & are standardized */ /* plus it helps with cross-compiling */ @@ -242,6 +251,14 @@ #endif #endif +#elif defined(RGFW_WAYLAND) + #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) + #define RGFW_EGL + #define RGFW_OPENGL + #include + #endif + + #include #elif (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WEBASM) #define RGFW_MACOS_X11 #define RGFW_X11 @@ -299,65 +316,65 @@ #define RGFW_NO_GPU_RENDER (1L<<14) /* don't render (using the GPU based API)*/ #define RGFW_NO_CPU_RENDER (1L<<15) /* don't render (using the CPU based buffer rendering)*/ +#define RGFW_WINDOW_HIDE (1L << 16)/* the window is hidden */ + +typedef RGFW_ENUM(u8, RGFW_event_types) { + /*! event codes */ + RGFW_keyPressed = 1, /* a key has been pressed */ + RGFW_keyReleased, /*!< a key has been released*/ + /*! key event note + the code of the key pressed is stored in + RGFW_Event.keyCode + !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! + + while a string version is stored in + RGFW_Event.KeyString + + RGFW_Event.lockState holds the current lockState + this means if CapsLock, NumLock are active or not + */ + RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right)*/ + RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right)*/ + RGFW_mousePosChanged, /*!< the position of the mouse has been changed*/ + /*! mouse event note + the x and y of the mouse can be found in the vector, RGFW_Event.point -/*! event codes */ -#define RGFW_keyPressed 2 /* a key has been pressed */ -#define RGFW_keyReleased 3 /*!< a key has been released*/ -/*! key event note - the code of the key pressed is stored in - RGFW_Event.keyCode - !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! - - while a string version is stored in - RGFW_Event.KeyString - - RGFW_Event.lockState holds the current lockState - this means if CapsLock, NumLock are active or not -*/ -#define RGFW_mouseButtonPressed 4 /*!< a mouse button has been pressed (left,middle,right)*/ -#define RGFW_mouseButtonReleased 5 /*!< a mouse button has been released (left,middle,right)*/ -#define RGFW_mousePosChanged 6 /*!< the position of the mouse has been changed*/ -/*! mouse event note - the x and y of the mouse can be found in the vector, RGFW_Event.point - - RGFW_Event.button holds which mouse button was pressed -*/ -#define RGFW_jsButtonPressed 7 /*!< a joystick button was pressed */ -#define RGFW_jsButtonReleased 8 /*!< a joystick button was released */ -#define RGFW_jsAxisMove 9 /*!< an axis of a joystick was moved*/ -/*! joystick event note - RGFW_Event.joystick holds which joystick was altered, if any - RGFW_Event.button holds which joystick button was pressed - - RGFW_Event.axis holds the data of all the axis - RGFW_Event.axisCount says how many axis there are -*/ -#define RGFW_windowMoved 10 /*!< the window was moved (by the user) */ -#define RGFW_windowResized 11 /*!< the window was resized (by the user), [on webASM this means the browser was resized] */ - -#define RGFW_focusIn 12 /*!< window is in focus now */ -#define RGFW_focusOut 13 /*!< window is out of focus now */ - -#define RGFW_mouseEnter 14 /* mouse entered the window */ -#define RGFW_mouseLeave 15 /* mouse left the window */ - -#define RGFW_windowRefresh 16 /* The window content needs to be refreshed */ - -/* attribs change event note - The event data is sent straight to the window structure - with win->r.x, win->r.y, win->r.w and win->r.h -*/ -#define RGFW_quit 33 /*!< the user clicked the quit button*/ -#define RGFW_dnd 34 /*!< a file has been dropped into the window*/ -#define RGFW_dnd_init 35 /*!< the start of a dnd event, when the place where the file drop is known */ -/* dnd data note - The x and y coords of the drop are stored in the vector RGFW_Event.point + RGFW_Event.button holds which mouse button was pressed + */ + RGFW_jsButtonPressed, /*!< a joystick button was pressed */ + RGFW_jsButtonReleased, /*!< a joystick button was released */ + RGFW_jsAxisMove, /*!< an axis of a joystick was moved*/ + /*! joystick event note + RGFW_Event.joystick holds which joystick was altered, if any + RGFW_Event.button holds which joystick button was pressed + + RGFW_Event.axis holds the data of all the axis + RGFW_Event.axisCount says how many axis there are + */ + RGFW_windowMoved, /*!< the window was moved (by the user) */ + RGFW_windowResized, /*!< the window was resized (by the user), [on webASM this means the browser was resized] */ + RGFW_focusIn, /*!< window is in focus now */ + RGFW_focusOut, /*!< window is out of focus now */ + RGFW_mouseEnter, /* mouse entered the window */ + RGFW_mouseLeave, /* mouse left the window */ + RGFW_windowRefresh, /* The window content needs to be refreshed */ + + /* attribs change event note + The event data is sent straight to the window structure + with win->r.x, win->r.y, win->r.w and win->r.h + */ + RGFW_quit, /*!< the user clicked the quit button*/ + RGFW_dnd, /*!< a file has been dropped into the window*/ + RGFW_dnd_init /*!< the start of a dnd event, when the place where the file drop is known */ + /* dnd data note + The x and y coords of the drop are stored in the vector RGFW_Event.point - RGFW_Event.droppedFilesCount holds how many files were dropped + RGFW_Event.droppedFilesCount holds how many files were dropped - This is also the size of the array which stores all the dropped file string, - RGFW_Event.droppedFiles -*/ + This is also the size of the array which stores all the dropped file string, + RGFW_Event.droppedFiles + */ +}; /*! mouse button codes (RGFW_Event.button) */ #define RGFW_mouseLeft 1 /*!< left mouse button is pressed*/ @@ -381,64 +398,72 @@ /*! joystick button codes (based on xbox/playstation), you may need to change these values per controller */ #ifndef RGFW_joystick_codes typedef RGFW_ENUM(u8, RGFW_joystick_codes) { - RGFW_JS_A = 0, /* or PS X button */ - RGFW_JS_B = 1, /* or PS circle button */ - RGFW_JS_Y = 2, /* or PS triangle button */ - RGFW_JS_X = 3, /* or PS square button */ - RGFW_JS_START = 9, /* start button */ - RGFW_JS_SELECT = 8, /* select button */ - RGFW_JS_HOME = 10, /* home button */ - RGFW_JS_UP = 13, /* dpad up */ - RGFW_JS_DOWN = 14, /* dpad down*/ - RGFW_JS_LEFT = 15, /* dpad left */ - RGFW_JS_RIGHT = 16, /* dpad right */ - RGFW_JS_L1 = 4, /* left bump */ - RGFW_JS_L2 = 5, /* left trigger*/ - RGFW_JS_R1 = 6, /* right bumper */ - RGFW_JS_R2 = 7, /* right trigger */ + RGFW_JS_A = 0, /*!< or PS X button */ + RGFW_JS_B = 1, /*!< or PS circle button */ + RGFW_JS_Y = 2, /*!< or PS triangle button */ + RGFW_JS_X = 3, /*!< or PS square button */ + RGFW_JS_START = 9, /*!< start button */ + RGFW_JS_SELECT = 8, /*!< select button */ + RGFW_JS_HOME = 10, /*!< home button */ + RGFW_JS_UP = 13, /*!< dpad up */ + RGFW_JS_DOWN = 14, /*!< dpad down*/ + RGFW_JS_LEFT = 15, /*!< dpad left */ + RGFW_JS_RIGHT = 16, /*!< dpad right */ + RGFW_JS_L1 = 4, /*!< left bump */ + RGFW_JS_L2 = 5, /*!< left trigger*/ + RGFW_JS_R1 = 6, /*!< right bumper */ + RGFW_JS_R2 = 7, /*!< right trigger */ }; #endif -/* basic vector type, if there's not already a point/vector type of choice */ +/*! basic vector type, if there's not already a point/vector type of choice */ #ifndef RGFW_point typedef struct { i32 x, y; } RGFW_point; #endif - /* basic rect type, if there's not already a rect type of choice */ +/*! basic rect type, if there's not already a rect type of choice */ #ifndef RGFW_rect typedef struct { i32 x, y, w, h; } RGFW_rect; #endif - /* basic area type, if there's not already a area type of choice */ +/*! basic area type, if there's not already a area type of choice */ #ifndef RGFW_area typedef struct { u32 w, h; } RGFW_area; #endif -#define RGFW_POINT(x, y) (RGFW_point){x, y} -#define RGFW_RECT(x, y, w, h) (RGFW_rect){x, y, w, h} -#define RGFW_AREA(w, h) (RGFW_area){w, h} +#ifndef __cplusplus +#define RGFW_POINT(x, y) (RGFW_point){(i32)(x), (i32)(y)} +#define RGFW_RECT(x, y, w, h) (RGFW_rect){(i32)(x), (i32)(y), (i32)(w), (i32)(h)} +#define RGFW_AREA(w, h) (RGFW_area){(u32)(w), (u32)(h)} +#else +#define RGFW_POINT(x, y) {(i32)(x), (i32)(y)} +#define RGFW_RECT(x, y, w, h) {(i32)(x), (i32)(y), (i32)(w), (i32)(h)} +#define RGFW_AREA(w, h) {(u32)(w), (u32)(h)} +#endif #ifndef RGFW_NO_MONITOR + /*! structure for monitor data */ typedef struct RGFW_monitor { - char name[128]; /* monitor name */ - RGFW_rect rect; /* monitor Workarea */ - float scaleX, scaleY; /* monitor content scale*/ - float physW, physH; /* monitor physical size */ + char name[128]; /*!< monitor name */ + RGFW_rect rect; /*!< monitor Workarea */ + float scaleX, scaleY; /*!< monitor content scale*/ + float physW, physH; /*!< monitor physical size */ } RGFW_monitor; /* - NOTE : Monitor functions should be ran only as many times as needed (not in a loop) + NOTE : Monitor functions should be ran only as many times as needed (not in a loop) */ - /* get an array of all the monitors (max 6) */ + /*! get an array of all the monitors (max 6) */ RGFWDEF RGFW_monitor* RGFW_getMonitors(void); - /* get the primary monitor */ + /*! get the primary monitor */ RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); #endif /* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_Event struct) */ +/*! Event structure for checking/getting events */ typedef struct RGFW_Event { - char keyName[16]; /* key name of event*/ + char keyName[16]; /*!< key name of event*/ /*! drag and drop data */ /* 260 max paths with a max length of 260 */ @@ -452,67 +477,85 @@ typedef struct RGFW_Event { u32 type; /*!< which event has been sent?*/ RGFW_point point; /*!< mouse x, y of event (or drop point) */ - u32 fps; /*the current fps of the window [the fps is checked when events are checked]*/ - u64 frameTime, frameTime2; /* this is used for counting the fps */ + u8 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - u8 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - - b8 inFocus; /*if the window is in focus or not (this is always true for MacOS windows due to the api being weird) */ + b8 repeat; /*!< key press event repeated (the key is being held) */ + b8 inFocus; /*!< if the window is in focus or not (this is always true for MacOS windows due to the api being weird) */ u8 lockState; + + u8 button; /* !< which mouse button was pressed */ + double scroll; /*!< the raw mouse scroll value */ - u16 joystick; /* which joystick this event applies to (if applicable to any) */ - - u8 button; /*!< which mouse button has been clicked (0) left (1) middle (2) right OR which joystick button was pressed*/ - double scroll; /* the raw mouse scroll value */ - - u8 axisesCount; /* number of axises */ - RGFW_point axis[2]; /* x, y of axises (-100 to 100) */ -} RGFW_Event; /*!< Event structure for checking/getting events */ + u16 joystick; /*! which joystick this event applies to (if applicable to any) */ + u8 axisesCount; /*!< number of axises */ + RGFW_point axis[2]; /*!< x, y of axises (-100 to 100) */ -/* source data for the window (used by the APIs) */ + u64 frameTime, frameTime2; /*!< this is used for counting the fps */ +} RGFW_Event; +/*! source data for the window (used by the APIs) */ typedef struct RGFW_window_src { #ifdef RGFW_WINDOWS HWND window; /*!< source window */ HDC hdc; /*!< source HDC */ u32 hOffset; /*!< height offset for window */ -#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) HGLRC ctx; /*!< source graphics context */ -#elif defined(RGFW_OSMESA) + #elif defined(RGFW_OSMESA) OSMesaContext ctx; -#elif defined(RGFW_DIRECTX) + #elif defined(RGFW_DIRECTX) IDXGISwapChain* swapchain; ID3D11RenderTargetView* renderTargetView; ID3D11DepthStencilView* pDepthStencilView; -#elif defined(RGFW_EGL) + #elif defined(RGFW_EGL) EGLSurface EGL_surface; EGLDisplay EGL_display; EGLContext EGL_context; -#endif + #endif -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) HDC hdcMem; HBITMAP bitmap; -#endif - RGFW_area maxSize, minSize; /* for setting max/min resize (RGFW_WINDOWS) */ + #endif + RGFW_area maxSize, minSize; /*!< for setting max/min resize (RGFW_WINDOWS) */ #elif defined(RGFW_X11) Display* display; /*!< source display */ Window window; /*!< source window */ -#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) GLXContext ctx; /*!< source graphics context */ -#elif defined(RGFW_OSMESA) + #elif defined(RGFW_OSMESA) OSMesaContext ctx; -#elif defined(RGFW_EGL) + #elif defined(RGFW_EGL) EGLSurface EGL_surface; EGLDisplay EGL_display; EGLContext EGL_context; -#endif + #endif #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) XImage* bitmap; GC gc; #endif +#elif defined(RGFW_WAYLAND) + struct wl_display* display; + struct wl_surface* surface; + struct wl_buffer* wl_buffer; + struct wl_keyboard* keyboard; + + struct xdg_surface* xdg_surface; + struct xdg_toplevel* xdg_toplevel; + struct zxdg_toplevel_decoration_v1* decoration; + RGFW_Event events[20]; + i32 eventLen; + size_t eventIndex; + #if defined(RGFW_EGL) + struct wl_egl_window* window; + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; + #elif defined(RGFW_OSMESA) + OSMesaContext ctx; + #endif #elif defined(RGFW_MACOS) u32 display; void* displayLink; @@ -531,7 +574,7 @@ typedef struct RGFW_window_src { void* view; /*apple viewpoint thingy*/ #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - void* bitmap; /* API's bitmap for storing or managing */ + void* bitmap; /*!< API's bitmap for storing or managing */ void* image; #endif #elif defined(RGFW_WEBASM) @@ -542,33 +585,34 @@ typedef struct RGFW_window_src { typedef struct RGFW_window { - RGFW_window_src src; + RGFW_window_src src; /*!< src window data */ #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - u8* buffer; /* buffer for non-GPU systems (OSMesa, basic software rendering) */ + u8* buffer; /*!< buffer for non-GPU systems (OSMesa, basic software rendering) */ /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ #endif - + void* userPtr; /* ptr for usr data */ + RGFW_Event event; /*!< current event */ - RGFW_rect r; /* the x, y, w and h of the struct */ + RGFW_rect r; /*!< the x, y, w and h of the struct */ - RGFW_point _lastMousePoint; /* last cusor point (for raw mouse data) */ - - u32 fpsCap; /*!< the fps cap of the window should run at (change this var to change the fps cap, 0 = no limit)*/ - /*[the fps is capped when events are checked]*/ - - u32 _winArgs; /* windows args (for RGFW to check) */ + RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ + + u32 _winArgs; /*!< windows args (for RGFW to check) */ } RGFW_window; /*!< Window structure for managing the window */ #if defined(RGFW_X11) || defined(RGFW_MACOS) - typedef u64 RGFW_thread; /* thread type unix */ + typedef u64 RGFW_thread; /*!< thread type unix */ #else - typedef void* RGFW_thread; /* thread type for window */ + typedef void* RGFW_thread; /*!< thread type for window */ #endif -/* this has to be set before createWindow is called, else the fulscreen size is used */ -RGFWDEF void RGFW_setBufferSize(RGFW_area size); /* the buffer cannot be resized (by RGFW) */ +/** * @defgroup Window_management +* @{ */ + +/*! this has to be set before createWindow is called, else the fulscreen size is used */ +RGFWDEF void RGFW_setBufferSize(RGFW_area size); /*!< the buffer cannot be resized (by RGFW) */ RGFW_window* RGFW_createWindow( const char* name, /* name of the window */ @@ -576,10 +620,10 @@ RGFW_window* RGFW_createWindow( u16 args /* extra arguments (NULL / (u16)0 means no args used)*/ ); /*!< function to create a window struct */ -/* get the size of the screen to an area struct */ +/*! get the size of the screen to an area struct */ RGFWDEF RGFW_area RGFW_getScreenSize(void); -/* +/*! this function checks an *individual* event (and updates window structure attributes) this means, using this function without a while loop may cause event lag @@ -593,7 +637,7 @@ RGFWDEF RGFW_area RGFW_getScreenSize(void); RGFW_Event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ -/* +/*! for RGFW_window_eventWait and RGFW_window_checkEvents waitMS -> Allows th e function to keep checking for events even after `RGFW_window_checkEvent == NULL` if waitMS == 0, the loop will not wait for events @@ -605,16 +649,16 @@ typedef RGFW_ENUM(i32, RGFW_eventWait) { RGFW_NO_WAIT = 0 }; -/* sleep until RGFW gets an event or the timer ends (defined by OS) */ +/*! sleep until RGFW gets an event or the timer ends (defined by OS) */ RGFWDEF void RGFW_window_eventWait(RGFW_window* win, i32 waitMS); -/* +/*! check all the events until there are none left, this should only be used if you're using callbacks only */ RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS); -/* +/*! Tell RGFW_window_eventWait to stop waiting, to be ran from another thread */ RGFWDEF void RGFW_stopCheckEvents(void); @@ -622,42 +666,42 @@ RGFWDEF void RGFW_stopCheckEvents(void); /*! window managment functions*/ RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ -/* moves window to a given point */ +/*! moves window to a given point */ RGFWDEF void RGFW_window_move(RGFW_window* win, - RGFW_point v/* new pos*/ + RGFW_point v/*!< new pos*/ ); #ifndef RGFW_NO_MONITOR - /* move to a specific monitor */ + /*! move to a specific monitor */ RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); #endif -/* resize window to a current size/area */ -RGFWDEF void RGFW_window_resize(RGFW_window* win, - RGFW_area a/* new size*/ +/*! resize window to a current size/area */ +RGFWDEF void RGFW_window_resize(RGFW_window* win, /*!< source window */ + RGFW_area a/*!< new size*/ ); -/* set the minimum size a user can shrink a window to a given size/area */ +/*! set the minimum size a user can shrink a window to a given size/area */ RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); -/* set the minimum size a user can extend a window to a given size/area */ +/*! set the minimum size a user can extend a window to a given size/area */ RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); -RGFWDEF void RGFW_window_maximize(RGFW_window* win); /* maximize the window size */ -RGFWDEF void RGFW_window_minimize(RGFW_window* win); /* minimize the window (in taskbar (per OS))*/ -RGFWDEF void RGFW_window_restore(RGFW_window* win); /* restore the window from minimized (per OS)*/ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); /*!< maximize the window size */ +RGFWDEF void RGFW_window_minimize(RGFW_window* win); /*!< minimize the window (in taskbar (per OS))*/ +RGFWDEF void RGFW_window_restore(RGFW_window* win); /*!< restore the window from minimized (per OS)*/ -/* if the window should have a border or not (borderless) based on bool value of `border` */ +/*! if the window should have a border or not (borderless) based on bool value of `border` */ RGFWDEF void RGFW_window_setBorder(RGFW_window* win, b8 border); -/* turn on / off dnd (RGFW_ALLOW_DND stil must be passed to the window)*/ +/*! turn on / off dnd (RGFW_ALLOW_DND stil must be passed to the window)*/ RGFWDEF void RGFW_window_setDND(RGFW_window* win, b8 allow); #ifndef RGFW_NO_PASSTHROUGH - /* turn on / off mouse passthrough */ + /*!! turn on / off mouse passthrough */ RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough); #endif -/* rename window to a given string */ +/*! rename window to a given string */ RGFWDEF void RGFW_window_setName(RGFW_window* win, char* name ); @@ -674,7 +718,7 @@ RGFWDEF void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 /*!< sets the mouse to a standard API cursor (based on RGFW_MOUSE, as seen at the end of the RGFW_HEADER part of this file) */ RGFWDEF void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); -RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /* sets the mouse to the default mouse icon */ +RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /*!< sets the mouse to the default mouse icon */ /* Locks cursor at the center of the window win->event.point become raw mouse movement data @@ -682,12 +726,12 @@ RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /* sets the mouse to this is useful for a 3D camera */ RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); -/* stop holding the mouse and let it move freely */ +/*! stop holding the mouse and let it move freely */ RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); -/* hide the window */ +/*! hide the window */ RGFWDEF void RGFW_window_hide(RGFW_window* win); -/* show the window */ +/*! show the window */ RGFWDEF void RGFW_window_show(RGFW_window* win); /* @@ -696,28 +740,32 @@ RGFWDEF void RGFW_window_show(RGFW_window* win); */ RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win); -/* where the mouse is on the screen */ +/*! where the mouse is on the screen */ RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); -/* where the mouse is on the window */ +/*! where the mouse is on the window */ RGFWDEF RGFW_point RGFW_window_getMousePoint(RGFW_window* win); -/* show the mouse or hide the mouse*/ +/*! show the mouse or hide the mouse*/ RGFWDEF void RGFW_window_showMouse(RGFW_window* win, i8 show); -/* move the mouse to a set x, y pos*/ +/*! move the mouse to a set x, y pos*/ RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v); -/* if the window should close (RGFW_close was sent or escape was pressed) */ +/*! if the window should close (RGFW_close was sent or escape was pressed) */ RGFWDEF b8 RGFW_window_shouldClose(RGFW_window* win); -/* if window is fullscreen'd */ +/*! if window is fullscreen'd */ RGFWDEF b8 RGFW_window_isFullscreen(RGFW_window* win); -/* if window is hidden */ +/*! if window is hidden */ RGFWDEF b8 RGFW_window_isHidden(RGFW_window* win); -/* if window is minimized */ +/*! if window is minimized */ RGFWDEF b8 RGFW_window_isMinimized(RGFW_window* win); -/* if window is maximized */ +/*! if window is maximized */ RGFWDEF b8 RGFW_window_isMaximized(RGFW_window* win); +/** @} */ + +/** * @defgroup Monitor +* @{ */ #ifndef RGFW_NO_MONITOR /* @@ -725,17 +773,27 @@ scale the window to the monitor, this is run by default if the user uses the arg `RGFW_SCALE_TO_MONITOR` during window creation */ RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); -/* get the struct of the window's monitor */ +/*! get the struct of the window's monitor */ RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); #endif -/*!< make the window the current opengl drawing context */ -RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); +/** @} */ + +/** * @defgroup Input +* @{ */ /*error handling*/ -RGFWDEF b8 RGFW_Error(void); /* returns true if an error has occurred (doesn't print errors itself) */ +RGFWDEF b8 RGFW_Error(void); /*!< returns true if an error has occurred (doesn't print errors itself) */ + +/*! returns true if the key should be shifted */ +RGFWDEF b8 RGFW_shouldShift(u32 keycode, u8 lockState); + +/*! get char from RGFW keycode (using a LUT), uses shift'd version if shift = true */ +RGFWDEF char RGFW_keyCodeToChar(u32 keycode, b8 shift); +/*! get char from RGFW keycode (using a LUT), uses lockState for shouldShift) */ +RGFWDEF char RGFW_keyCodeToCharAuto(u32 keycode, u8 lockState); -/*!< if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus.*/ +/*! if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus.*/ RGFWDEF b8 RGFW_isPressed(RGFW_window* win, u8 key); /*!< if key is pressed (key code)*/ RGFWDEF b8 RGFW_wasPressed(RGFW_window* win, u8 key); /*!< if key was pressed (checks previous state only) (key code)*/ @@ -744,90 +802,100 @@ RGFWDEF b8 RGFW_isHeld(RGFW_window* win, u8 key); /*!< if key is held (key code) RGFWDEF b8 RGFW_isReleased(RGFW_window* win, u8 key); /*!< if key is released (key code)*/ /* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ -RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key /* key code*/); - -/* if a mouse button is pressed */ -RGFWDEF b8 RGFW_isMousePressed(RGFW_window* win, u8 button /* mouse button code */ ); -/* if a mouse button is held */ -RGFWDEF b8 RGFW_isMouseHeld(RGFW_window* win, u8 button /* mouse button code */ ); -/* if a mouse button was released */ -RGFWDEF b8 RGFW_isMouseReleased(RGFW_window* win, u8 button /* mouse button code */ ); -/* if a mouse button was pressed (checks previous state only) */ -RGFWDEF b8 RGFW_wasMousePressed(RGFW_window* win, u8 button /* mouse button code */ ); - -/*! clipboard functions*/ +RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key /*!< key code*/); + +/*! if a mouse button is pressed */ +RGFWDEF b8 RGFW_isMousePressed(RGFW_window* win, u8 button /*!< mouse button code */ ); +/*! if a mouse button is held */ +RGFWDEF b8 RGFW_isMouseHeld(RGFW_window* win, u8 button /*!< mouse button code */ ); +/*! if a mouse button was released */ +RGFWDEF b8 RGFW_isMouseReleased(RGFW_window* win, u8 button /*!< mouse button code */ ); +/*! if a mouse button was pressed (checks previous state only) */ +RGFWDEF b8 RGFW_wasMousePressed(RGFW_window* win, u8 button /*!< mouse button code */ ); +/** @} */ + +/** * @defgroup Clipboard +* @{ */ RGFWDEF char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ -RGFWDEF void RGFW_clipboardFree(char* str); /* the string returned from RGFW_readClipboard must be freed */ +RGFWDEF void RGFW_clipboardFree(char* str); /*!< the string returned from RGFW_readClipboard must be freed */ RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ +/** @} */ -/* +/** Event callbacks, these are completely optional, you can use the normal RGFW_checkEvent() method if you prefer that +* @defgroup Callbacks +* @{ */ -/* RGFW_windowMoved, the window and its new rect value */ +/*! RGFW_windowMoved, the window and its new rect value */ typedef void (* RGFW_windowmovefunc)(RGFW_window* win, RGFW_rect r); -/* RGFW_windowResized, the window and its new rect value */ +/*! RGFW_windowResized, the window and its new rect value */ typedef void (* RGFW_windowresizefunc)(RGFW_window* win, RGFW_rect r); -/* RGFW_quit, the window that was closed */ +/*! RGFW_quit, the window that was closed */ typedef void (* RGFW_windowquitfunc)(RGFW_window* win); -/* RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its inFocus */ +/*! RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its inFocus */ typedef void (* RGFW_focusfunc)(RGFW_window* win, b8 inFocus); -/* RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ +/*! RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, RGFW_point point, b8 status); -/* RGFW_mousePosChanged, the window that the move happened on and the new point of the mouse */ +/*! RGFW_mousePosChanged, the window that the move happened on and the new point of the mouse */ typedef void (* RGFW_mouseposfunc)(RGFW_window* win, RGFW_point point); -/* RGFW_dnd_init, the window, the point of the drop on the windows */ +/*! RGFW_dnd_init, the window, the point of the drop on the windows */ typedef void (* RGFW_dndInitfunc)(RGFW_window* win, RGFW_point point); -/* RGFW_windowRefresh, the window that needs to be refreshed */ +/*! RGFW_windowRefresh, the window that needs to be refreshed */ typedef void (* RGFW_windowrefreshfunc)(RGFW_window* win); -/* RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the keycode, the string version, the state of mod keys, if it was a press (else it's a release) */ +/*! RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the keycode, the string version, the state of mod keys, if it was a press (else it's a release) */ typedef void (* RGFW_keyfunc)(RGFW_window* win, u32 keycode, char keyName[16], u8 lockState, b8 pressed); -/* RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +/*! RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ typedef void (* RGFW_mousebuttonfunc)(RGFW_window* win, u8 button, double scroll, b8 pressed); -/* RGFW_jsButtonPressed / RGFW_jsButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +/*! RGFW_jsButtonPressed / RGFW_jsButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ typedef void (* RGFW_jsButtonfunc)(RGFW_window* win, u16 joystick, u8 button, b8 pressed); -/* RGFW_jsAxisMove, the window that got the event, the joystick in question, the axis values and the amount of axises */ +/*! RGFW_jsAxisMove, the window that got the event, the joystick in question, the axis values and the amount of axises */ typedef void (* RGFW_jsAxisfunc)(RGFW_window* win, u16 joystick, RGFW_point axis[2], u8 axisesCount); -/* RGFW_dnd, the window that had the drop, the drop data and the amount files dropped */ + +/*! RGFW_dnd, the window that had the drop, the drop data and the amount files dropped returns previous callback function (if it was set) */ #ifdef RGFW_ALLOC_DROPFILES typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount); #else typedef void (* RGFW_dndfunc)(RGFW_window* win, char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH], u32 droppedFilesCount); #endif -/* set callback for a window move event */ -RGFWDEF void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); -/* set callbacksfor a window resize event */ -RGFWDEF void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); -/* set callbacksfor a window quit event */ -RGFWDEF void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); -/* set callbacksfor a mouse move event */ -RGFWDEF void RGFW_setMousePosCallback(RGFW_mouseposfunc func); -/* set callbacksfor a window refresh event */ -RGFWDEF void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); -/* set callbacksfor a window focus change event */ -RGFWDEF void RGFW_setFocusCallback(RGFW_focusfunc func); -/* set callbacksfor a mouse notify event */ -RGFWDEF void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); -/* set callbacksfor a drop event event */ -RGFWDEF void RGFW_setDndCallback(RGFW_dndfunc func); -/* set callbacksfor a start of a drop event */ -RGFWDEF void RGFW_setDndInitCallback(RGFW_dndInitfunc func); -/* set callbacksfor a key (press / release ) event */ -RGFWDEF void RGFW_setKeyCallback(RGFW_keyfunc func); -/* set callbacksfor a mouse button (press / release ) event */ -RGFWDEF void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); -/* set callbacksfor a controller button (press / release ) event */ -RGFWDEF void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func); -/* set callbacksfor a joystick axis mov event */ -RGFWDEF void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); - +/*! set callback for a window move event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowmovefunc RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); +/*! set callback for a window resize event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowresizefunc RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); +/*! set callback for a window quit event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowquitfunc RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); +/*! set callback for a mouse move event returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseposfunc RGFW_setMousePosCallback(RGFW_mouseposfunc func); +/*! set callback for a window refresh event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowrefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); +/*! set callback for a window focus change event returns previous callback function (if it was set) */ +RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); +/*! set callback for a mouse notify event returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); +/*! set callback for a drop event event returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func); +/*! set callback for a start of a drop event returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func); +/*! set callback for a key (press / release ) event returns previous callback function (if it was set) */ +RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); +/*! set callback for a mouse button (press / release ) event returns previous callback function (if it was set) */ +RGFWDEF RGFW_mousebuttonfunc RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); +/*! set callback for a controller button (press / release ) event returns previous callback function (if it was set) */ +RGFWDEF RGFW_jsButtonfunc RGFW_setjsButtonCallback(RGFW_jsButtonfunc func); +/*! set callback for a joystick axis mov event returns previous callback function (if it was set) */ +RGFWDEF RGFW_jsAxisfunc RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); + +/** @} */ + +/** * @defgroup Threads +* @{ */ #ifndef RGFW_NO_THREADS /*! threading functions*/ @@ -851,7 +919,10 @@ RGFWDEF void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ #endif -/*! gamepad/joystick functions (linux-only currently) */ +/** @} */ + +/** * @defgroup joystick +* @{ */ /*! joystick count starts at 0*/ /*!< register joystick to window based on a number (the number is based on when it was connected eg. /dev/js0)*/ @@ -860,27 +931,45 @@ RGFWDEF u16 RGFW_registerJoystickF(RGFW_window* win, char* file); RGFWDEF u32 RGFW_isPressedJS(RGFW_window* win, u16 controller, u8 button); +/** @} */ + +/** * @defgroup graphics_API +* @{ */ + +/*!< make the window the current opengl drawing context + + NOTE: + if you want to switch the graphics context's thread, + you have to run RGFW_window_makeCurrent(NULL); on the old thread + then RGFW_window_makeCurrent(valid_window) on the new thread +*/ +RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); + +/*< updates fps / sets fps to cap (must by ran manually by the user at the end of a frame), returns current fps */ +RGFWDEF u32 RGFW_window_checkFPS(RGFW_window* win, u32 fpsCap); + /* supports openGL, directX, OSMesa, EGL and software rendering */ -RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /* swap the rendering buffer */ +RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /*!< swap the rendering buffer */ RGFWDEF void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval); RGFWDEF void RGFW_window_setGPURender(RGFW_window* win, i8 set); RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); /*! native API functions */ -#ifdef RGFW_OPENGL - /*! Get max OpenGL version */ - RGFWDEF u8* RGFW_getMaxGLVersion(void); - /* OpenGL init hints */ - RGFWDEF void RGFW_setGLStencil(i32 stencil); /* set stencil buffer bit size (8 by default) */ - RGFWDEF void RGFW_setGLSamples(i32 samples); /* set number of sampiling buffers (4 by default) */ - RGFWDEF void RGFW_setGLStereo(i32 stereo); /* use GL_STEREO (GL_FALSE by default) */ - RGFWDEF void RGFW_setGLAuxBuffers(i32 auxBuffers); /* number of aux buffers (0 by default) */ - - /*! Set OpenGL version hint */ - RGFWDEF void RGFW_setGLVersion(i32 major, i32 minor); - RGFWDEF void* RGFW_getProcAddress(const char* procname); /* get native opengl proc address */ - RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /* to be called by RGFW_window_makeCurrent */ +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + /*! OpenGL init hints */ + RGFWDEF void RGFW_setGLStencil(i32 stencil); /*!< set stencil buffer bit size (8 by default) */ + RGFWDEF void RGFW_setGLSamples(i32 samples); /*!< set number of sampiling buffers (4 by default) */ + RGFWDEF void RGFW_setGLStereo(i32 stereo); /*!< use GL_STEREO (GL_FALSE by default) */ + RGFWDEF void RGFW_setGLAuxBuffers(i32 auxBuffers); /*!< number of aux buffers (0 by default) */ + + /*! which profile to use for the opengl verion */ + typedef RGFW_ENUM(u8, RGFW_GL_profile) { RGFW_GL_CORE = 0, RGFW_GL_COMPATIBILITY }; + /*! Set OpenGL version hint (core or compatibility profile)*/ + RGFWDEF void RGFW_setGLVersion(RGFW_GL_profile profile, i32 major, i32 minor); + RGFWDEF void RGFW_setDoubleBuffer(b8 useDoubleBuffer); + RGFWDEF void* RGFW_getProcAddress(const char* procname); /*!< get native opengl proc address */ + RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /*!< to be called by RGFW_window_makeCurrent */ #elif defined(RGFW_DIRECTX) typedef struct { IDXGIFactory* pFactory; @@ -896,13 +985,15 @@ RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); RGFWDEF RGFW_directXinfo* RGFW_getDirectXInfo(void); #endif -/*! Supporting functions */ -RGFWDEF void RGFW_window_checkFPS(RGFW_window* win); /*!< updates fps / sets fps to cap (ran by RGFW_window_checkEvent)*/ -RGFWDEF u64 RGFW_getTime(void); /* get time in seconds */ -RGFWDEF u64 RGFW_getTimeNS(void); /* get time in nanoseconds */ -RGFWDEF void RGFW_sleep(u64 milisecond); /* sleep for a set time */ +/** @} */ -/* +/** * @defgroup Supporting +* @{ */ +RGFWDEF u64 RGFW_getTime(void); /*!< get time in seconds */ +RGFWDEF u64 RGFW_getTimeNS(void); /*!< get time in nanoseconds */ +RGFWDEF void RGFW_sleep(u64 milisecond); /*!< sleep for a set time */ + +/*! key codes and mouse icon enums */ @@ -1019,6 +1110,7 @@ typedef RGFW_ENUM(u8, RGFW_Key) { final_key, }; + typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_MOUSE_NORMAL = 0, RGFW_MOUSE_ARROW, @@ -1033,6 +1125,8 @@ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_MOUSE_NOT_ALLOWED, }; +/** @} */ + #endif /* RGFW_HEADER */ /* @@ -1092,15 +1186,18 @@ int main() { */ #ifdef RGFW_X11 - #define RGFW_OS_BASED_VALUE(l, w, m, a) l + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) l #elif defined(RGFW_WINDOWS) - #define RGFW_OS_BASED_VALUE(l, w, m, a) w + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) w #elif defined(RGFW_MACOS) - #define RGFW_OS_BASED_VALUE(l, w, m, a) m + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) m #elif defined(RGFW_WEBASM) - #define RGFW_OS_BASED_VALUE(l, w, m, a) a + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) h +#elif defined(RGFW_WAYLAND) + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) ww #endif + #ifdef RGFW_IMPLEMENTATION #include @@ -1119,120 +1216,147 @@ This is the start of keycode data MacOS -> windows and linux already don't have keycodes as macros, so there's no point */ -u8 RGFW_keycodes[] = { - [RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE)] = RGFW_Backtick, - - [RGFW_OS_BASED_VALUE(19, 0x30, 29, DOM_VK_0)] = RGFW_0, - [RGFW_OS_BASED_VALUE(10, 0x31, 18, DOM_VK_1)] = RGFW_1, - [RGFW_OS_BASED_VALUE(11, 0x32, 19, DOM_VK_2)] = RGFW_2, - [RGFW_OS_BASED_VALUE(12, 0x33, 20, DOM_VK_3)] = RGFW_3, - [RGFW_OS_BASED_VALUE(13, 0x34, 21, DOM_VK_4)] = RGFW_4, - [RGFW_OS_BASED_VALUE(14, 0x35, 23, DOM_VK_5)] = RGFW_5, - [RGFW_OS_BASED_VALUE(15, 0x36, 22, DOM_VK_6)] = RGFW_6, - [RGFW_OS_BASED_VALUE(16, 0x37, 26, DOM_VK_7)] = RGFW_7, - [RGFW_OS_BASED_VALUE(17, 0x38, 28, DOM_VK_8)] = RGFW_8, - [RGFW_OS_BASED_VALUE(18, 0x39, 25, DOM_VK_9)] = RGFW_9, - - [RGFW_OS_BASED_VALUE(65, 0x20, 49, DOM_VK_SPACE)] = RGFW_Space, - - [RGFW_OS_BASED_VALUE(38, 0x41, 0, DOM_VK_A)] = RGFW_a, - [RGFW_OS_BASED_VALUE(56, 0x42, 11, DOM_VK_B)] = RGFW_b, - [RGFW_OS_BASED_VALUE(54, 0x43, 8, DOM_VK_C)] = RGFW_c, - [RGFW_OS_BASED_VALUE(40, 0x44, 2, DOM_VK_D)] = RGFW_d, - [RGFW_OS_BASED_VALUE(26, 0x45, 14, DOM_VK_E)] = RGFW_e, - [RGFW_OS_BASED_VALUE(41, 0x46, 3, DOM_VK_F)] = RGFW_f, - [RGFW_OS_BASED_VALUE(42, 0x47, 5, DOM_VK_G)] = RGFW_g, - [RGFW_OS_BASED_VALUE(43, 0x48, 4, DOM_VK_H)] = RGFW_h, - [RGFW_OS_BASED_VALUE(31, 0x49, 34, DOM_VK_I)] = RGFW_i, - [RGFW_OS_BASED_VALUE(44, 0x4A, 38, DOM_VK_J)] = RGFW_j, - [RGFW_OS_BASED_VALUE(45, 0x4B, 40, DOM_VK_K)] = RGFW_k, - [RGFW_OS_BASED_VALUE(46, 0x4C, 37, DOM_VK_L)] = RGFW_l, - [RGFW_OS_BASED_VALUE(58, 0x4D, 46, DOM_VK_M)] = RGFW_m, - [RGFW_OS_BASED_VALUE(57, 0x4E, 45, DOM_VK_N)] = RGFW_n, - [RGFW_OS_BASED_VALUE(32, 0x4F, 31, DOM_VK_O)] = RGFW_o, - [RGFW_OS_BASED_VALUE(33, 0x50, 35, DOM_VK_P)] = RGFW_p, - [RGFW_OS_BASED_VALUE(24, 0x51, 12, DOM_VK_Q)] = RGFW_q, - [RGFW_OS_BASED_VALUE(27, 0x52, 15, DOM_VK_R)] = RGFW_r, - [RGFW_OS_BASED_VALUE(39, 0x53, 1, DOM_VK_S)] = RGFW_s, - [RGFW_OS_BASED_VALUE(28, 0x54, 17, DOM_VK_T)] = RGFW_t, - [RGFW_OS_BASED_VALUE(30, 0x55, 32, DOM_VK_U)] = RGFW_u, - [RGFW_OS_BASED_VALUE(55, 0x56, 9, DOM_VK_V)] = RGFW_v, - [RGFW_OS_BASED_VALUE(25, 0x57, 13, DOM_VK_W)] = RGFW_w, - [RGFW_OS_BASED_VALUE(53, 0x58, 7, DOM_VK_X)] = RGFW_x, - [RGFW_OS_BASED_VALUE(29, 0x59, 16, DOM_VK_Y)] = RGFW_y, - [RGFW_OS_BASED_VALUE(52, 0x5A, 6, DOM_VK_Z)] = RGFW_z, - - [RGFW_OS_BASED_VALUE(60, 190, 47, DOM_VK_PERIOD)] = RGFW_Period, - [RGFW_OS_BASED_VALUE(59, 188, 43, DOM_VK_COMMA)] = RGFW_Comma, - [RGFW_OS_BASED_VALUE(61, 191, 44, DOM_VK_SLASH)] = RGFW_Slash, - [RGFW_OS_BASED_VALUE(34, 219, 33, DOM_VK_OPEN_BRACKET)] = RGFW_Bracket, - [RGFW_OS_BASED_VALUE(35, 221, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_CloseBracket, - [RGFW_OS_BASED_VALUE(47, 186, 41, DOM_VK_SEMICOLON)] = RGFW_Semicolon, - [RGFW_OS_BASED_VALUE(48, 222, 39, DOM_VK_QUOTE)] = RGFW_Quote, - [RGFW_OS_BASED_VALUE(51, 322, 42, DOM_VK_BACK_SLASH)] = RGFW_BackSlash, + + +/* + the c++ compiler doesn't support setting up an array like, + we'll have to do it during runtime using a function & this messy setup +*/ +#ifndef __cplusplus +#define RGFW_NEXT , +#define RGFW_MAP +#else +#define RGFW_NEXT ; +#define RGFW_MAP RGFW_keycodes +#endif + +#ifdef RGFW_WAYLAND +#include +#endif + +u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(136, 337, 128, DOM_VK_WIN_OEM_CLEAR + 1, 130)] = { +#ifdef __cplusplus + 0 +}; +void RGFW_init_keys(void) { +#endif + RGFW_MAP [RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE, KEY_GRAVE)] = RGFW_Backtick RGFW_NEXT + + RGFW_MAP [RGFW_OS_BASED_VALUE(19, 0x30, 29, DOM_VK_0, KEY_0)] = RGFW_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(10, 0x31, 18, DOM_VK_1, KEY_1)] = RGFW_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(11, 0x32, 19, DOM_VK_2, KEY_2)] = RGFW_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(12, 0x33, 20, DOM_VK_3, KEY_3)] = RGFW_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(13, 0x34, 21, DOM_VK_4, KEY_4)] = RGFW_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(14, 0x35, 23, DOM_VK_5, KEY_5)] = RGFW_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(15, 0x36, 22, DOM_VK_6, KEY_6)] = RGFW_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(16, 0x37, 26, DOM_VK_7, KEY_7)] = RGFW_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(17, 0x38, 28, DOM_VK_8, KEY_8)] = RGFW_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(18, 0x39, 25, DOM_VK_9, KEY_9)] = RGFW_9, + + RGFW_MAP [RGFW_OS_BASED_VALUE(65, 0x20, 49, DOM_VK_SPACE, KEY_SPACE)] = RGFW_Space, + + RGFW_MAP [RGFW_OS_BASED_VALUE(38, 0x41, 0, DOM_VK_A, KEY_A)] = RGFW_a RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(56, 0x42, 11, DOM_VK_B, KEY_B)] = RGFW_b RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(54, 0x43, 8, DOM_VK_C, KEY_C)] = RGFW_c RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(40, 0x44, 2, DOM_VK_D, KEY_D)] = RGFW_d RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(26, 0x45, 14, DOM_VK_E, KEY_E)] = RGFW_e RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(41, 0x46, 3, DOM_VK_F, KEY_F)] = RGFW_f RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(42, 0x47, 5, DOM_VK_G, KEY_G)] = RGFW_g RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(43, 0x48, 4, DOM_VK_H, KEY_H)] = RGFW_h RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(31, 0x49, 34, DOM_VK_I, KEY_I)] = RGFW_i RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(44, 0x4A, 38, DOM_VK_J, KEY_J)] = RGFW_j RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(45, 0x4B, 40, DOM_VK_K, KEY_K)] = RGFW_k RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(46, 0x4C, 37, DOM_VK_L, KEY_L)] = RGFW_l RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(58, 0x4D, 46, DOM_VK_M, KEY_M)] = RGFW_m RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(57, 0x4E, 45, DOM_VK_N, KEY_N)] = RGFW_n RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(32, 0x4F, 31, DOM_VK_O, KEY_O)] = RGFW_o RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(33, 0x50, 35, DOM_VK_P, KEY_P)] = RGFW_p RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(24, 0x51, 12, DOM_VK_Q, KEY_Q)] = RGFW_q RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(27, 0x52, 15, DOM_VK_R, KEY_R)] = RGFW_r RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(39, 0x53, 1, DOM_VK_S, KEY_S)] = RGFW_s RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(28, 0x54, 17, DOM_VK_T, KEY_T)] = RGFW_t RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(30, 0x55, 32, DOM_VK_U, KEY_U)] = RGFW_u RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(55, 0x56, 9, DOM_VK_V, KEY_V)] = RGFW_v RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(25, 0x57, 13, DOM_VK_W, KEY_W)] = RGFW_w RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(53, 0x58, 7, DOM_VK_X, KEY_X)] = RGFW_x RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(29, 0x59, 16, DOM_VK_Y, KEY_Y)] = RGFW_y RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(52, 0x5A, 6, DOM_VK_Z, KEY_Z)] = RGFW_z, + + RGFW_MAP [RGFW_OS_BASED_VALUE(60, 190, 47, DOM_VK_PERIOD, KEY_DOT)] = RGFW_Period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(59, 188, 43, DOM_VK_COMMA, KEY_COMMA)] = RGFW_Comma RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(61, 191, 44, DOM_VK_SLASH, KEY_SLASH)] = RGFW_Slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(34, 219, 33, DOM_VK_OPEN_BRACKET, KEY_LEFTBRACE)] = RGFW_Bracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(35, 221, 30, DOM_VK_CLOSE_BRACKET, KEY_RIGHTBRACE)] = RGFW_CloseBracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(47, 186, 41, DOM_VK_SEMICOLON, KEY_SEMICOLON)] = RGFW_Semicolon RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(48, 222, 39, DOM_VK_QUOTE, KEY_APOSTROPHE)] = RGFW_Quote RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(51, 322, 42, DOM_VK_BACK_SLASH, KEY_BACKSLASH)] = RGFW_BackSlash, - [RGFW_OS_BASED_VALUE(36, 0x0D, 36, DOM_VK_RETURN)] = RGFW_Return, - [RGFW_OS_BASED_VALUE(119, 0x2E, 118, DOM_VK_DELETE)] = RGFW_Delete, - [RGFW_OS_BASED_VALUE(77, 0x90, 72, DOM_VK_NUM_LOCK)] = RGFW_Numlock, - [RGFW_OS_BASED_VALUE(106, 0x6F, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash, - [RGFW_OS_BASED_VALUE(63, 0x6A, 76, DOM_VK_MULTIPLY)] = RGFW_Multiply, - [RGFW_OS_BASED_VALUE(82, 0x6D, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus, - [RGFW_OS_BASED_VALUE(87, 0x61, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1, - [RGFW_OS_BASED_VALUE(88, 0x62, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2, - [RGFW_OS_BASED_VALUE(89, 0x63, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3, - [RGFW_OS_BASED_VALUE(83, 0x64, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4, - [RGFW_OS_BASED_VALUE(84, 0x65, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5, - [RGFW_OS_BASED_VALUE(85, 0x66, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6, - [RGFW_OS_BASED_VALUE(79, 0x67, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7, - [RGFW_OS_BASED_VALUE(80, 0x68, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8, - [RGFW_OS_BASED_VALUE(81, 0x69, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9, - [RGFW_OS_BASED_VALUE(90, 0x60, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0, - [RGFW_OS_BASED_VALUE(91, 0x6E, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period, - [RGFW_OS_BASED_VALUE(104, 0x92, 77, 0)] = RGFW_KP_Return, + RGFW_MAP [RGFW_OS_BASED_VALUE(36, 0x0D, 36, DOM_VK_RETURN, KEY_ENTER)] = RGFW_Return RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(119, 0x2E, 118, DOM_VK_DELETE, KEY_DELETE)] = RGFW_Delete RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(77, 0x90, 72, DOM_VK_NUM_LOCK, KEY_NUMLOCK)] = RGFW_Numlock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(106, 0x6F, 82, DOM_VK_DIVIDE, KEY_KPSLASH)] = RGFW_KP_Slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(63, 0x6A, 76, DOM_VK_MULTIPLY, KEY_KPASTERISK)] = RGFW_Multiply RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(82, 0x6D, 67, DOM_VK_SUBTRACT, KEY_KPMINUS)] = RGFW_KP_Minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(87, 0x61, 84, DOM_VK_NUMPAD1, KEY_KP1)] = RGFW_KP_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(88, 0x62, 85, DOM_VK_NUMPAD2, KEY_KP2)] = RGFW_KP_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(89, 0x63, 86, DOM_VK_NUMPAD3, KEY_KP3)] = RGFW_KP_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(83, 0x64, 87, DOM_VK_NUMPAD4, KEY_KP4)] = RGFW_KP_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(84, 0x65, 88, DOM_VK_NUMPAD5, KEY_KP5)] = RGFW_KP_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(85, 0x66, 89, DOM_VK_NUMPAD6, KEY_KP6)] = RGFW_KP_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(79, 0x67, 90, DOM_VK_NUMPAD7, KEY_KP7)] = RGFW_KP_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(80, 0x68, 92, DOM_VK_NUMPAD8, KEY_KP8)] = RGFW_KP_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(81, 0x69, 93, DOM_VK_NUMPAD9, KEY_KP9)] = RGFW_KP_9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(90, 0x60, 83, DOM_VK_NUMPAD0, KEY_KP0)] = RGFW_KP_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(91, 0x6E, 65, DOM_VK_DECIMAL, KEY_KPDOT)] = RGFW_KP_Period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(104, 0x92, 77, 0, KEY_KPENTER)] = RGFW_KP_Return, - [RGFW_OS_BASED_VALUE(20, 189, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_Minus, - [RGFW_OS_BASED_VALUE(21, 187, 24, DOM_VK_EQUALS)] = RGFW_Equals, - [RGFW_OS_BASED_VALUE(22, 8, 51, DOM_VK_BACK_SPACE)] = RGFW_BackSpace, - [RGFW_OS_BASED_VALUE(23, 0x09, 48, DOM_VK_TAB)] = RGFW_Tab, - [RGFW_OS_BASED_VALUE(66, 20, 57, DOM_VK_CAPS_LOCK)] = RGFW_CapsLock, - [RGFW_OS_BASED_VALUE(50, 0xA0, 56, DOM_VK_SHIFT)] = RGFW_ShiftL, - [RGFW_OS_BASED_VALUE(37, 0x11, 59, DOM_VK_CONTROL)] = RGFW_ControlL, - [RGFW_OS_BASED_VALUE(64, 164, 58, DOM_VK_ALT)] = RGFW_AltL, - [RGFW_OS_BASED_VALUE(133, 0x5B, 55, DOM_VK_WIN)] = RGFW_SuperL, + RGFW_MAP [RGFW_OS_BASED_VALUE(20, 189, 27, DOM_VK_HYPHEN_MINUS, KEY_MINUS)] = RGFW_Minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(21, 187, 24, DOM_VK_EQUALS, KEY_EQUAL)] = RGFW_Equals RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(22, 8, 51, DOM_VK_BACK_SPACE, KEY_BACKSPACE)] = RGFW_BackSpace RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(23, 0x09, 48, DOM_VK_TAB, KEY_TAB)] = RGFW_Tab RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(66, 20, 57, DOM_VK_CAPS_LOCK, KEY_CAPSLOCK)] = RGFW_CapsLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(50, 0x10, 56, DOM_VK_SHIFT, KEY_LEFTSHIFT)] = RGFW_ShiftL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(37, 0x11, 59, DOM_VK_CONTROL, KEY_LEFTCTRL)] = RGFW_ControlL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(64,0x12, 58, DOM_VK_ALT, KEY_LEFTALT)] = RGFW_AltL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x5B, 55, DOM_VK_WIN, KEY_LEFTMETA)] = RGFW_SuperL, #if !defined(RGFW_WINDOWS) && !defined(RGFW_MACOS) && !defined(RGFW_WEBASM) - [RGFW_OS_BASED_VALUE(105, 0x11, 59, 0)] = RGFW_ControlR, - [RGFW_OS_BASED_VALUE(135, 0xA4, 55, 0)] = RGFW_SuperR, + RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11, 59, 0, KEY_RIGHTCTRL)] = RGFW_ControlR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(135, 0xA4, 55, 0, KEY_RIGHTMETA)] = RGFW_SuperR, + RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x5C, 56, 0, KEY_RIGHTSHIFT)] = RGFW_ShiftR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(108, 165, 58, 0, KEY_RIGHTALT)] = RGFW_AltR, #endif - #if !defined(RGFW_MACOS) && !defined(RGFW_WEBASM) - [RGFW_OS_BASED_VALUE(62, 0x5C, 56, 0)] = RGFW_ShiftR, - [RGFW_OS_BASED_VALUE(108, 165, 58, 0)] = RGFW_AltR, - #endif - - [RGFW_OS_BASED_VALUE(67, 0x70, 127, DOM_VK_F1)] = RGFW_F1, - [RGFW_OS_BASED_VALUE(68, 0x71, 121, DOM_VK_F2)] = RGFW_F2, - [RGFW_OS_BASED_VALUE(69, 0x72, 100, DOM_VK_F3)] = RGFW_F3, - [RGFW_OS_BASED_VALUE(70, 0x73, 119, DOM_VK_F4)] = RGFW_F4, - [RGFW_OS_BASED_VALUE(71, 0x74, 97, DOM_VK_F5)] = RGFW_F5, - [RGFW_OS_BASED_VALUE(72, 0x75, 98, DOM_VK_F6)] = RGFW_F6, - [RGFW_OS_BASED_VALUE(73, 0x76, 99, DOM_VK_F7)] = RGFW_F7, - [RGFW_OS_BASED_VALUE(74, 0x77, 101, DOM_VK_F8)] = RGFW_F8, - [RGFW_OS_BASED_VALUE(75, 0x78, 102, DOM_VK_F9)] = RGFW_F9, - [RGFW_OS_BASED_VALUE(76, 0x79, 110, DOM_VK_F10)] = RGFW_F10, - [RGFW_OS_BASED_VALUE(95, 0x7A, 104, DOM_VK_F11)] = RGFW_F11, - [RGFW_OS_BASED_VALUE(96, 0x7B, 112, DOM_VK_F12)] = RGFW_F12, - [RGFW_OS_BASED_VALUE(111, 0x26, 126, DOM_VK_UP)] = RGFW_Up, - [RGFW_OS_BASED_VALUE(116, 0x28, 125, DOM_VK_DOWN)] = RGFW_Down, - [RGFW_OS_BASED_VALUE(113, 0x25, 123, DOM_VK_LEFT)] = RGFW_Left, - [RGFW_OS_BASED_VALUE(114, 0x27, 124, DOM_VK_RIGHT)] = RGFW_Right, - [RGFW_OS_BASED_VALUE(118, 0x2D, 115, DOM_VK_INSERT)] = RGFW_Insert, - [RGFW_OS_BASED_VALUE(115, 0x23, 120, DOM_VK_END)] = RGFW_End, - [RGFW_OS_BASED_VALUE(112, 336, 117, DOM_VK_PAGE_UP)] = RGFW_PageUp, - [RGFW_OS_BASED_VALUE(117, 325, 122, DOM_VK_PAGE_DOWN)] = RGFW_PageDown, - [RGFW_OS_BASED_VALUE(9, 0x1B, 53, DOM_VK_ESCAPE)] = RGFW_Escape, - [RGFW_OS_BASED_VALUE(110, 0x24, 116, DOM_VK_HOME)] = RGFW_Home, + RGFW_MAP [RGFW_OS_BASED_VALUE(67, 0x70, 127, DOM_VK_F1, KEY_F1)] = RGFW_F1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(68, 0x71, 121, DOM_VK_F2, KEY_F2)] = RGFW_F2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(69, 0x72, 100, DOM_VK_F3, KEY_F3)] = RGFW_F3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(70, 0x73, 119, DOM_VK_F4, KEY_F4)] = RGFW_F4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(71, 0x74, 97, DOM_VK_F5, KEY_F5)] = RGFW_F5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(72, 0x75, 98, DOM_VK_F6, KEY_F6)] = RGFW_F6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(73, 0x76, 99, DOM_VK_F7, KEY_F7)] = RGFW_F7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(74, 0x77, 101, DOM_VK_F8, KEY_F8)] = RGFW_F8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x78, 102, DOM_VK_F9, KEY_F9)] = RGFW_F9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x79, 110, DOM_VK_F10, KEY_F10)] = RGFW_F10 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x7A, 104, DOM_VK_F11, KEY_F11)] = RGFW_F11 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x7B, 112, DOM_VK_F12, KEY_F12)] = RGFW_F12 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x26, 126, DOM_VK_UP, KEY_UP)] = RGFW_Up RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x28, 125, DOM_VK_DOWN, KEY_DOWN)] = RGFW_Down RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x25, 123, DOM_VK_LEFT, KEY_LEFT)] = RGFW_Left RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(114, 0x27, 124, DOM_VK_RIGHT, KEY_RIGHT)] = RGFW_Right RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(118, 0x2D, 115, DOM_VK_INSERT, KEY_INSERT)] = RGFW_Insert RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(115, 0x23, 120, DOM_VK_END, KEY_END)] = RGFW_End RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(112, 336, 117, DOM_VK_PAGE_UP, KEY_PAGEUP)] = RGFW_PageUp RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(117, 325, 122, DOM_VK_PAGE_DOWN, KEY_PAGEDOWN)] = RGFW_PageDown RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x1B, 53, DOM_VK_ESCAPE, KEY_ESC)] = RGFW_Escape RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x24, 116, DOM_VK_HOME, KEY_HOME)] = RGFW_Home RGFW_NEXT +#ifndef __cplusplus }; +#else +} +#endif + +#undef RGFW_NEXT +#undef RGFW_MAP typedef struct { b8 current : 1; @@ -1244,6 +1368,12 @@ RGFW_keyState RGFW_keyboard[final_key] = { {0, 0} }; RGFWDEF u32 RGFW_apiKeyCodeToRGFW(u32 keycode); u32 RGFW_apiKeyCodeToRGFW(u32 keycode) { + #ifdef __cplusplus + if (RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE, KEY_GRAVE) != RGFW_Backtick) { + RGFW_init_keys(); + } + #endif + /* make sure the key isn't out of bounds */ if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) return 0; @@ -1253,22 +1383,54 @@ u32 RGFW_apiKeyCodeToRGFW(u32 keycode) { RGFWDEF void RGFW_resetKey(void); void RGFW_resetKey(void) { - size_t len = final_key; /* last_key == length */ + size_t len = final_key; /*!< last_key == length */ - size_t i; /* reset each previous state */ + size_t i; /*!< reset each previous state */ for (i = 0; i < len; i++) RGFW_keyboard[i].prev = 0; } +b8 RGFW_shouldShift(u32 keycode, u8 lockState) { + #define RGFW_xor(x, y) (( (x) && (!(y)) ) || ((y) && (!(x)) )) + b8 caps4caps = (lockState & RGFW_CAPSLOCK) && ((keycode >= RGFW_a) && (keycode <= RGFW_z)); + b8 shouldShift = RGFW_xor((RGFW_isPressed(NULL, RGFW_ShiftL) || RGFW_isPressed(NULL, RGFW_ShiftR)), caps4caps); + #undef RGFW_xor + + return shouldShift; +} + +char RGFW_keyCodeToChar(u32 keycode, b8 shift) { + static const char map[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '`', '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', '-', '=', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '.', ',', '/', '[', ']', ';', '\n', '\'', '\\', + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '/', '*', '-', '1', '2', '3', '3', '5', '6', '7', '8', '9', '0', '\n' + }; + + static const char mapCaps[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '~', ')', '!', '@', '#', '$', '%', '^', '&', '*', + '(', '_', '+', 0, '0', 0, 0, 0, 0, 0, 0, 0, 0, 0, ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '>', '<', '?', '{', '}', ':', '\n', '"', '|', + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '?', '*', '-', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (shift == RGFW_FALSE) + return map[keycode]; + return mapCaps[keycode]; +} + +char RGFW_keyCodeToCharAuto(u32 keycode, u8 lockState) { return RGFW_keyCodeToChar(keycode, RGFW_shouldShift(keycode, lockState)); } + /* this is the end of keycode data */ /* joystick data */ -u8 RGFW_jsPressed[4][16]; /* if a key is currently pressed or not (per joystick) */ +u8 RGFW_jsPressed[4][16]; /*!< if a key is currently pressed or not (per joystick) */ -i32 RGFW_joysticks[4]; /* limit of 4 joysticks at a time */ -u16 RGFW_joystickCount; /* the actual amount of joysticks */ +i32 RGFW_joysticks[4]; /*!< limit of 4 joysticks at a time */ +u16 RGFW_joystickCount; /*!< the actual amount of joysticks */ /* event callback defines start here @@ -1325,19 +1487,73 @@ void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS) { #endif } -void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { RGFW_windowMoveCallback = func; } -void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { RGFW_windowResizeCallback = func; } -void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { RGFW_windowQuitCallback = func; } -void RGFW_setMousePosCallback(RGFW_mouseposfunc func) { RGFW_mousePosCallback = func; } -void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { RGFW_windowRefreshCallback = func; } -void RGFW_setFocusCallback(RGFW_focusfunc func) { RGFW_focusCallback = func; } -void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { RGFW_mouseNotifyCallBack = func; } -void RGFW_setDndCallback(RGFW_dndfunc func) { RGFW_dndCallback = func; } -void RGFW_setDndInitCallback(RGFW_dndInitfunc func) { RGFW_dndInitCallback = func; } -void RGFW_setKeyCallback(RGFW_keyfunc func) { RGFW_keyCallback = func; } -void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { RGFW_mouseButtonCallback = func; } -void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func) { RGFW_jsButtonCallback = func; } -void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func) { RGFW_jsAxisCallback = func; } +RGFW_windowmovefunc RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { + RGFW_windowmovefunc prev = (RGFW_windowMoveCallback == RGFW_windowmovefuncEMPTY) ? NULL : RGFW_windowMoveCallback; + RGFW_windowMoveCallback = func; + return prev; +} +RGFW_windowresizefunc RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { + RGFW_windowresizefunc prev = (RGFW_windowResizeCallback == RGFW_windowresizefuncEMPTY) ? NULL : RGFW_windowResizeCallback; + RGFW_windowResizeCallback = func; + return prev; +} +RGFW_windowquitfunc RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { + RGFW_windowquitfunc prev = (RGFW_windowQuitCallback == RGFW_windowquitfuncEMPTY) ? NULL : RGFW_windowQuitCallback; + RGFW_windowQuitCallback = func; + return prev; +} + +RGFW_mouseposfunc RGFW_setMousePosCallback(RGFW_mouseposfunc func) { + RGFW_mouseposfunc prev = (RGFW_mousePosCallback == RGFW_mouseposfuncEMPTY) ? NULL : RGFW_mousePosCallback; + RGFW_mousePosCallback = func; + return prev; +} +RGFW_windowrefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { + RGFW_windowrefreshfunc prev = (RGFW_windowRefreshCallback == RGFW_windowrefreshfuncEMPTY) ? NULL : RGFW_windowRefreshCallback; + RGFW_windowRefreshCallback = func; + return prev; +} +RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func) { + RGFW_focusfunc prev = (RGFW_focusCallback == RGFW_focusfuncEMPTY) ? NULL : RGFW_focusCallback; + RGFW_focusCallback = func; + return prev; +} + +RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { + RGFW_mouseNotifyfunc prev = (RGFW_mouseNotifyCallBack == RGFW_mouseNotifyfuncEMPTY) ? NULL : RGFW_mouseNotifyCallBack; + RGFW_mouseNotifyCallBack = func; + return prev; +} +RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func) { + RGFW_dndfunc prev = (RGFW_dndCallback == RGFW_dndfuncEMPTY) ? NULL : RGFW_dndCallback; + RGFW_dndCallback = func; + return prev; +} +RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func) { + RGFW_dndInitfunc prev = (RGFW_dndInitCallback == RGFW_dndInitfuncEMPTY) ? NULL : RGFW_dndInitCallback; + RGFW_dndInitCallback = func; + return prev; +} +RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func) { + RGFW_keyfunc prev = (RGFW_keyCallback == RGFW_keyfuncEMPTY) ? NULL : RGFW_keyCallback; + RGFW_keyCallback = func; + return prev; +} +RGFW_mousebuttonfunc RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { + RGFW_mousebuttonfunc prev = (RGFW_mouseButtonCallback == RGFW_mousebuttonfuncEMPTY) ? NULL : RGFW_mouseButtonCallback; + RGFW_mouseButtonCallback = func; + return prev; +} +RGFW_jsButtonfunc RGFW_setjsButtonCallback(RGFW_jsButtonfunc func) { + RGFW_jsButtonfunc prev = (RGFW_jsButtonCallback == RGFW_jsButtonfuncEMPTY) ? NULL : RGFW_jsButtonCallback; + RGFW_jsButtonCallback = func; + return prev; +} +RGFW_jsAxisfunc RGFW_setjsAxisCallback(RGFW_jsAxisfunc func) { + RGFW_jsAxisfunc prev = (RGFW_jsAxisCallback == RGFW_jsAxisfuncEMPTY) ? NULL : RGFW_jsAxisCallback; + RGFW_jsAxisCallback = func; + return prev; +} /* no more event call back defines */ @@ -1368,7 +1584,7 @@ RGFWDEF RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args); /* do a basic initialization for RGFW_window, this is to standard it for each OS */ RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { - RGFW_window* win = (RGFW_window*) RGFW_MALLOC(sizeof(RGFW_window)); /* make a new RGFW struct */ + RGFW_window* win = (RGFW_window*) RGFW_MALLOC(sizeof(RGFW_window)); /*!< make a new RGFW struct */ /* clear out dnd info */ #ifdef RGFW_ALLOC_DROPFILES @@ -1386,7 +1602,7 @@ RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { assert(win->src.display != NULL); Screen* scrn = DefaultScreenOfDisplay((Display*)win->src.display); - RGFW_area screenR = RGFW_AREA(scrn->width, scrn->height); + RGFW_area screenR = RGFW_AREA((u32)scrn->width, (u32)scrn->height); #endif /* rect based the requested args */ @@ -1398,7 +1614,6 @@ RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { /* set and init the new window's data */ win->r = rect; - win->fpsCap = 0; win->event.inFocus = 1; win->event.droppedFilesCount = 0; RGFW_joystickCount = 0; @@ -1424,7 +1639,7 @@ RGFW_window* RGFW_root = NULL; void RGFW_clipboardFree(char* str) { RGFW_FREE(str); } -RGFW_keyState RGFW_mouseButtons[5] = { 0 }; +RGFW_keyState RGFW_mouseButtons[5] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; b8 RGFW_isMousePressed(RGFW_window* win, u8 button) { assert(win != NULL); @@ -1461,20 +1676,28 @@ b8 RGFW_isReleased(RGFW_window* win, u8 key) { return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); } -void RGFW_window_makeCurrent(RGFW_window* win) { - assert(win != NULL); +#if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) /* defines for directX context*/ + RGFW_directXinfo RGFW_dxInfo; + RGFW_directXinfo* RGFW_getDirectXInfo(void) { return &RGFW_dxInfo; } +#endif +void RGFW_window_makeCurrent(RGFW_window* win) { #if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) - RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, &win->src.renderTargetView, NULL); + if (win == NULL) + RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, NULL, NULL); + else + RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, &win->src.renderTargetView, NULL); #elif defined(RGFW_OPENGL) RGFW_window_makeCurrent_OpenGL(win); +#else + RGFW_UNUSED(win) #endif } void RGFW_window_setGPURender(RGFW_window* win, i8 set) { if (!set && !(win->_winArgs & RGFW_NO_GPU_RENDER)) win->_winArgs |= RGFW_NO_GPU_RENDER; - + else if (set && win->_winArgs & RGFW_NO_GPU_RENDER) win->_winArgs ^= RGFW_NO_GPU_RENDER; } @@ -1510,6 +1733,7 @@ void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; #endif RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); +RGFWDEF void RGFW_releaseCursor(RGFW_window* win); void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { if ((win->_winArgs & RGFW_HOLD_MOUSE)) @@ -1521,25 +1745,26 @@ void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { win->_winArgs |= RGFW_HOLD_MOUSE; RGFW_captureCursor(win, win->r); - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (area.w), win->r.y + (area.h))); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); } void RGFW_window_mouseUnhold(RGFW_window* win) { if ((win->_winArgs & RGFW_HOLD_MOUSE)) { win->_winArgs ^= RGFW_HOLD_MOUSE; - RGFW_captureCursor(win, RGFW_RECT(0, 0, 0, 0)); + RGFW_releaseCursor(win); } } -void RGFW_window_checkFPS(RGFW_window* win) { +u32 RGFW_window_checkFPS(RGFW_window* win, u32 fpsCap) { u64 deltaTime = RGFW_getTimeNS() - win->event.frameTime; + u32 output_fps = 0; u64 fps = round(1e+9 / deltaTime); - win->event.fps = fps; + output_fps= fps; - if (win->fpsCap && fps > win->fpsCap) { - u64 frameTimeNS = 1e+9 / win->fpsCap; + if (fpsCap && fps > fpsCap) { + u64 frameTimeNS = 1e+9 / fpsCap; u64 sleepTimeMS = (frameTimeNS - deltaTime) / 1e6; if (sleepTimeMS > 0) { @@ -1550,12 +1775,14 @@ void RGFW_window_checkFPS(RGFW_window* win) { win->event.frameTime = RGFW_getTimeNS(); - if (win->fpsCap == 0) - return; + if (fpsCap == 0) + return (u32) output_fps; deltaTime = RGFW_getTimeNS() - win->event.frameTime2; - win->event.fps = round(1e+9 / deltaTime); + output_fps = round(1e+9 / deltaTime); win->event.frameTime2 = RGFW_getTimeNS(); + + return output_fps; } u32 RGFW_isPressedJS(RGFW_window* win, u16 c, u8 button) { @@ -1586,7 +1813,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { win->event.lockState ^= RGFW_NUMLOCK; } -#if defined(RGFW_X11) || defined(RGFW_MACOS) +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) || defined(RGFW_WAYLAND) struct timespec; int nanosleep(const struct timespec* duration, struct timespec* rem); @@ -1603,7 +1830,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif /* - graphics API spcific code (end of generic code) + graphics API specific code (end of generic code) starts here */ @@ -1615,12 +1842,13 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #if defined(RGFW_OPENGL) || defined(RGFW_EGL) || defined(RGFW_OSMESA) #ifdef RGFW_WINDOWS #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE #include #endif - #ifndef __APPLE__ + #if !defined(__APPLE__) && !defined(RGFW_NO_GL_HEADER) #include - #else + #elif defined(__APPLE__) #ifndef GL_SILENCE_DEPRECATION #define GL_SILENCE_DEPRECATION #endif @@ -1631,11 +1859,12 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { /* EGL, normal OpenGL only */ #if !defined(RGFW_OSMESA) i32 RGFW_majorVersion = 0, RGFW_minorVersion = 0; + b8 RGFW_profile = RGFW_GL_CORE; #ifndef RGFW_EGL - i32 RGFW_STENCIL = 8, RGFW_SAMPLES = 4, RGFW_STEREO = GL_FALSE, RGFW_AUX_BUFFERS = 0; + i32 RGFW_STENCIL = 8, RGFW_SAMPLES = 4, RGFW_STEREO = 0, RGFW_AUX_BUFFERS = 0, RGFW_DOUBLE_BUFFER = 1; #else - i32 RGFW_STENCIL = 0, RGFW_SAMPLES = 0, RGFW_STEREO = GL_FALSE, RGFW_AUX_BUFFERS = 0; + i32 RGFW_STENCIL = 0, RGFW_SAMPLES = 0, RGFW_STEREO = 0, RGFW_AUX_BUFFERS = 0, RGFW_DOUBLE_BUFFER = 1; #endif @@ -1643,55 +1872,44 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { void RGFW_setGLSamples(i32 samples) { RGFW_SAMPLES = samples; } void RGFW_setGLStereo(i32 stereo) { RGFW_STEREO = stereo; } void RGFW_setGLAuxBuffers(i32 auxBuffers) { RGFW_AUX_BUFFERS = auxBuffers; } + void RGFW_setDoubleBuffer(b8 useDoubleBuffer) { RGFW_DOUBLE_BUFFER = useDoubleBuffer; } - void RGFW_setGLVersion(i32 major, i32 minor) { + void RGFW_setGLVersion(b8 profile, i32 major, i32 minor) { + RGFW_profile = profile; RGFW_majorVersion = major; RGFW_minorVersion = minor; } - u8* RGFW_getMaxGLVersion(void) { - RGFW_window* dummy = RGFW_createWindow("dummy", RGFW_RECT(0, 0, 1, 1), 0); - - const char* versionStr = (const char*) glGetString(GL_VERSION); - - static u8 version[2]; - version[0] = versionStr[0] - '0', - version[1] = versionStr[2] - '0'; - - RGFW_window_close(dummy); - - return version; - } - /* OPENGL normal only (no EGL / OSMesa) */ #ifndef RGFW_EGL -#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0) - #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0) - #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0) - #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0) - #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0) - #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0) - #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0) - #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0) +#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0, 0) + #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0, 0) + #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0, 0) + #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0, 0) + #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0, 0) + #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0, 0) + #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0, 0) + #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0, 0) #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0) - #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) - #define RGFW_GL_USE_OPENGL RGFW_OS_BASED_VALUE(GLX_USE_GL, 0x2010, 0, 0) - #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) - #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) - #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) - #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) - #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) + #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0, 0) + #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0, 0) + #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0, 0) + #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0, 0) + #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0, 0) + #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0, 0) + #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0, 0) #endif #ifdef RGFW_WINDOWS + #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 #define WGL_SAMPLE_BUFFERS_ARB 0x2041 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 @@ -1700,8 +1918,12 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #define WGL_TRANSPARENT_ARB 0x200A #endif - - static u32* RGFW_initAttribs(u32 useSoftware) { + +/* The window'ing api needs to know how to render the data we (or opengl) give it + MacOS and Windows do this using a structure called a "pixel format" + X11 calls it a "Visual" + This function returns the attributes for the format we want */ + static u32* RGFW_initFormatAttribs(u32 useSoftware) { RGFW_UNUSED(useSoftware); static u32 attribs[] = { #if defined(RGFW_X11) || defined(RGFW_WINDOWS) @@ -1710,13 +1932,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif RGFW_GL_ALPHA_SIZE , 8, RGFW_GL_DEPTH_SIZE , 24, - RGFW_GL_DOUBLEBUFFER , - #ifndef RGFW_MACOS - 1, - #endif - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_USE_OPENGL, 1, RGFW_GL_DRAW, 1, RGFW_GL_RED_SIZE , 8, RGFW_GL_GREEN_SIZE , 8, @@ -1726,7 +1942,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #ifdef RGFW_X11 GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, - #endif + #endif #ifdef RGFW_MACOS 72, @@ -1734,8 +1950,8 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif #ifdef RGFW_WINDOWS + WGL_SUPPORT_OPENGL_ARB, 1, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_TRANSPARENT_ARB, TRUE, WGL_COLOR_BITS_ARB, 32, #endif @@ -1750,8 +1966,10 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { attribs[index + 1] = attVal;\ index += 2;\ } - - RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_STENCIL); + + RGFW_GL_ADD_ATTRIB(RGFW_GL_DOUBLEBUFFER, 1); + + RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_STENCIL); RGFW_GL_ADD_ATTRIB(RGFW_GL_STEREO, RGFW_STEREO); RGFW_GL_ADD_ATTRIB(RGFW_GL_AUX_BUFFERS, RGFW_AUX_BUFFERS); @@ -1769,13 +1987,15 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif #ifdef RGFW_MACOS + /* macOS has the surface attribs and the opengl attribs connected for some reason + maybe this is to give macOS more control to limit openGL/the opengl version? */ + attribs[index] = 99; attribs[index + 1] = 0x1000; if (RGFW_majorVersion >= 4 || RGFW_majorVersion >= 3) { attribs[index + 1] = (u32) ((RGFW_majorVersion >= 4) ? 0x4100 : 0x3200); } - #endif RGFW_GL_ADD_ATTRIB(0, 0); @@ -1894,18 +2114,29 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #else 2, #endif - EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE + EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE }; size_t index = 4; RGFW_GL_ADD_ATTRIB(EGL_STENCIL_SIZE, RGFW_STENCIL); RGFW_GL_ADD_ATTRIB(EGL_SAMPLES, RGFW_SAMPLES); + if (RGFW_DOUBLE_BUFFER) + RGFW_GL_ADD_ATTRIB(EGL_RENDER_BUFFER, EGL_BACK_BUFFER); + if (RGFW_majorVersion) { attribs[1] = RGFW_majorVersion; - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MAJOR_VERSION, RGFW_majorVersion); RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MINOR_VERSION, RGFW_minorVersion); + + if (RGFW_profile == RGFW_GL_CORE) { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } + else { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + } + } #if defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3) @@ -1913,13 +2144,20 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #else eglBindAPI(EGL_OPENGL_API); #endif - - win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); + + win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); + + if (win->src.EGL_context == NULL) + fprintf(stderr, "failed to create an EGL opengl context\n"); eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); } + void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); + } + #ifdef RGFW_APPLE void* RGFWnsglFramework = NULL; #elif defined(RGFW_WINDOWS) @@ -1949,8 +2187,6 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { eglSwapInterval(win->src.EGL_display, swapInterval); - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; - } #endif /* RGFW_EGL */ @@ -1990,6 +2226,62 @@ This is where OS specific stuff starts */ +#if defined(RGFW_WAYLAND) || defined(RGFW_X11) + int RGFW_eventWait_forceStop[] = {0, 0, 0}; /* for wait events */ + + #ifdef __linux__ + #include + #include + #include + + RGFW_Event* RGFW_linux_updateJoystick(RGFW_window* win) { + static int xAxis = 0, yAxis = 0; + u8 i; + for (i = 0; i < RGFW_joystickCount; i++) { + struct js_event e; + + + if (RGFW_joysticks[i] == 0) + continue; + + i32 flags = fcntl(RGFW_joysticks[i], F_GETFL, 0); + fcntl(RGFW_joysticks[i], F_SETFL, flags | O_NONBLOCK); + + ssize_t bytes; + while ((bytes = read(RGFW_joysticks[i], &e, sizeof(e))) > 0) { + switch (e.type) { + case JS_EVENT_BUTTON: + win->event.type = e.value ? RGFW_jsButtonPressed : RGFW_jsButtonReleased; + win->event.button = e.number; + RGFW_jsPressed[i][e.number] = e.value; + RGFW_jsButtonCallback(win, i, e.number, e.value); + return &win->event; + case JS_EVENT_AXIS: + ioctl(RGFW_joysticks[i], JSIOCGAXES, &win->event.axisesCount); + + if ((e.number == 0 || e.number % 2) && e.number != 1) + xAxis = e.value; + else + yAxis = e.value; + + win->event.axis[e.number / 2].x = xAxis; + win->event.axis[e.number / 2].y = yAxis; + win->event.type = RGFW_jsAxisMove; + win->event.joystick = i; + RGFW_jsAxisCallback(win, i, win->event.axis, win->event.axisesCount); + return &win->event; + + default: break; + } + } + } + + return NULL; + } + + #endif +#endif + /* @@ -2021,9 +2313,9 @@ Start of Linux / Unix defines #include #include /* for data limits (mainly used in drag and drop functions) */ -#include #include + #ifdef __linux__ #include #endif @@ -2075,7 +2367,7 @@ Start of Linux / Unix defines if (RGFW_bufferSize.w == 0 && RGFW_bufferSize.h == 0) RGFW_bufferSize = RGFW_getScreenSize(); - win->buffer = RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); + win->buffer = (u8*)RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); #ifdef RGFW_OSMESA win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); @@ -2092,7 +2384,7 @@ Start of Linux / Unix defines win->src.gc = XCreateGC(win->src.display, win->src.window, 0, NULL); #else - RGFW_UNUSED(win); /* if buffer rendering is not being used */ + RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ RGFW_UNUSED(vi) #endif } @@ -2117,30 +2409,30 @@ Start of Linux / Unix defines 32, PropModeReplace, (u8*)&hints, 5 ); } + + void RGFW_releaseCursor(RGFW_window* win) { + XUngrabPointer(win->src.display, CurrentTime); - void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + /* disable raw input */ + unsigned char mask[] = { 0 }; XIEventMask em; em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; - /* grab the cursor if the rect struct isn't zeroed out, else ungrab*/ - if (!r.x && !r.y && r.w && !r.h) { - XUngrabPointer(win->src.display, CurrentTime); - - /* disable raw input */ - unsigned char mask[] = { 0 }; - em.mask_len = sizeof(mask); - em.mask = mask; - XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); - return; - } - + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); + } + + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { /* enable raw input */ unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; + XISetMask(mask, XI_RawMotion); + XIEventMask em; + em.deviceid = XIAllMasterDevices; em.mask_len = sizeof(mask); em.mask = mask; - XISetMask(mask, XI_RawMotion); - + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); @@ -2179,24 +2471,24 @@ Start of Linux / Unix defines } #endif - XInitThreads(); /* init X11 threading*/ + XInitThreads(); /*!< init X11 threading*/ if (args & RGFW_OPENGL_SOFTWARE) setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); RGFW_window* win = RGFW_window_basic_init(rect, args); - u64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /* X11 events accepted*/ + u64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted*/ #ifdef RGFW_OPENGL - u32* visual_attribs = RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); + u32* visual_attribs = RGFW_initFormatAttribs(args & RGFW_OPENGL_SOFTWARE); i32 fbcount; GLXFBConfig* fbc = glXChooseFBConfig((Display*) win->src.display, DefaultScreen(win->src.display), (i32*) visual_attribs, &fbcount); i32 best_fbc = -1; if (fbcount == 0) { - printf("Failed to find any valid GLX configs\n"); + printf("Failed to find any valid GLX visual configs\n"); return NULL; } @@ -2230,7 +2522,7 @@ Start of Linux / Unix defines XFree(fbc); if (args & RGFW_TRANSPARENT_WINDOW) { - XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ + XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /*!< for RGBA backgrounds*/ } #else @@ -2241,7 +2533,7 @@ Start of Linux / Unix defines viNorm.depth = 0; XVisualInfo* vi = &viNorm; - XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ + XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /*!< for RGBA backgrounds*/ #endif /* make X window attrubutes*/ XSetWindowAttributes swa; @@ -2273,16 +2565,19 @@ Start of Linux / Unix defines // with your application - robrohan XClassHint *hint = XAllocClassHint(); assert(hint != NULL); - hint->res_class = "RGFW"; + hint->res_class = (char*)"RGFW"; hint->res_name = (char*)name; // just use the window name as the app name XSetClassHint((Display*) win->src.display, win->src.window, hint); XFree(hint); if ((args & RGFW_NO_INIT_API) == 0) { -#ifdef RGFW_OPENGL +#ifdef RGFW_OPENGL /* This is the second part of setting up opengl. This is where we ask OpenGL for a specific version. */ i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; - context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + if (RGFW_profile == RGFW_GL_CORE) + context_attribs[1] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; + else + context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; if (RGFW_majorVersion || RGFW_minorVersion) { context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; @@ -2328,7 +2623,7 @@ Start of Linux / Unix defines RGFW_window_setBorder(win, 0); } - XSelectInput((Display*) win->src.display, (Drawable) win->src.window, event_mask); /* tell X11 what events we want*/ + XSelectInput((Display*) win->src.display, (Drawable) win->src.window, event_mask); /*!< tell X11 what events we want*/ /* make it so the user can't close the window until the program does*/ if (wm_delete_window == 0) @@ -2343,10 +2638,10 @@ Start of Linux / Unix defines #endif /* set the background*/ - XStoreName((Display*) win->src.display, (Drawable) win->src.window, name); /* set the name*/ + XStoreName((Display*) win->src.display, (Drawable) win->src.window, name); /*!< set the name*/ XMapWindow((Display*) win->src.display, (Drawable) win->src.window); /* draw the window*/ - XMoveWindow((Display*) win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /* move the window to it's proper cords*/ + XMoveWindow((Display*) win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /*!< move the window to it's proper cords*/ if (args & RGFW_ALLOW_DND) { /* init drag and drop atoms and turn on drag and drop for this window */ win->_winArgs |= RGFW_ALLOW_DND; @@ -2373,7 +2668,7 @@ Start of Linux / Unix defines XChangeProperty((Display*) win->src.display, (Window) win->src.window, XdndAware, 4, 32, - PropModeReplace, (u8*) &version, 1); /* turns on drag and drop */ + PropModeReplace, (u8*) &version, 1); /*!< turns on drag and drop */ } #ifdef RGFW_EGL @@ -2421,88 +2716,19 @@ Start of Linux / Unix defines return RGFWMouse; } + typedef struct XDND { + long source, version; + i32 format; + } XDND; /*!< data structure for xdnd events */ + XDND xdnd; - int RGFW_eventWait_forceStop[] = {0, 0, 0}; + int xAxis = 0, yAxis = 0; - void RGFW_stopCheckEvents(void) { - RGFW_eventWait_forceStop[2] = 1; - while (1) { - const char byte = 0; - const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); - if (result == 1 || result == -1) - break; - } - } + RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { + assert(win != NULL); - void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { - if (waitMS == 0) - return; - - u8 i; - - if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { - if (pipe(RGFW_eventWait_forceStop) != -1) { - fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); - } - } - - struct pollfd fds[] = { - { ConnectionNumber(win->src.display), POLLIN, 0 }, - { RGFW_eventWait_forceStop[0], POLLIN, 0 }, - #ifdef __linux__ /* blank space for 4 joystick files*/ - { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} - #endif - }; - - u8 index = 2; - - #if defined(__linux__) - for (i = 0; i < RGFW_joystickCount; i++) { - if (RGFW_joysticks[i] == 0) - continue; - - fds[index].fd = RGFW_joysticks[i]; - index++; - } - #endif - - - u64 start = RGFW_getTimeNS(); - - while (XPending(win->src.display) == 0 && waitMS >= -1) { - if (poll(fds, index, waitMS) <= 0) - break; - - if (waitMS > 0) { - waitMS -= (RGFW_getTimeNS() - start) / 1e+6; - } - } - - /* drain any data in the stop request */ - if (RGFW_eventWait_forceStop[2]) { - char data[64]; - read(RGFW_eventWait_forceStop[0], data, sizeof(data)); - - RGFW_eventWait_forceStop[2] = 0; - } - } - - typedef struct XDND { - long source, version; - i32 format; - } XDND; /* data structure for xdnd events */ - XDND xdnd; - - int xAxis = 0, yAxis = 0; - - RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { - assert(win != NULL); - - if (win->event.type == 0) - RGFW_resetKey(); + if (win->event.type == 0) + RGFW_resetKey(); if (win->event.type == RGFW_quit) { return NULL; @@ -2511,52 +2737,14 @@ Start of Linux / Unix defines win->event.type = 0; #ifdef __linux__ - { - u8 i; - for (i = 0; i < RGFW_joystickCount; i++) { - struct js_event e; - - - if (RGFW_joysticks[i] == 0) - continue; - - i32 flags = fcntl(RGFW_joysticks[i], F_GETFL, 0); - fcntl(RGFW_joysticks[i], F_SETFL, flags | O_NONBLOCK); - - ssize_t bytes; - while ((bytes = read(RGFW_joysticks[i], &e, sizeof(e))) > 0) { - switch (e.type) { - case JS_EVENT_BUTTON: - win->event.type = e.value ? RGFW_jsButtonPressed : RGFW_jsButtonReleased; - win->event.button = e.number; - RGFW_jsPressed[i][e.number] = e.value; - RGFW_jsButtonCallback(win, i, e.number, e.value); - return &win->event; - case JS_EVENT_AXIS: - ioctl(RGFW_joysticks[i], JSIOCGAXES, &win->event.axisesCount); - - if ((e.number == 0 || e.number % 2) && e.number != 1) - xAxis = e.value; - else - yAxis = e.value; - - win->event.axis[e.number / 2].x = xAxis; - win->event.axis[e.number / 2].y = yAxis; - win->event.type = RGFW_jsAxisMove; - win->event.joystick = i; - RGFW_jsAxisCallback(win, i, win->event.axis, win->event.axisesCount); - return &win->event; - - default: break; - } - } - } - } + RGFW_Event* event = RGFW_linux_updateJoystick(win); + if (event != NULL) + return event; #endif XPending(win->src.display); - XEvent E; /* raw X11 event */ + XEvent E; /*!< raw X11 event */ /* if there is no unread qued events, get a new one */ if ((QLength(win->src.display) || XEventsQueued((Display*) win->src.display, QueuedAlready) + XEventsQueued((Display*) win->src.display, QueuedAfterReading)) @@ -2573,21 +2761,22 @@ Start of Linux / Unix defines switch (E.type) { case KeyPress: - case KeyRelease: + case KeyRelease: { + win->event.repeat = RGFW_FALSE; /* check if it's a real key release */ if (E.type == KeyRelease && XEventsQueued((Display*) win->src.display, QueuedAfterReading)) { /* get next event if there is one*/ XEvent NE; XPeekEvent((Display*) win->src.display, &NE); if (E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) /* check if the current and next are both the same*/ - break; + win->event.repeat = RGFW_TRUE; } /* set event key data */ - KeySym sym = XkbKeycodeToKeysym((Display*) win->src.display, E.xkey.keycode, 0, E.xkey.state & ShiftMask ? 1 : 0); + KeySym sym = (KeySym)XkbKeycodeToKeysym((Display*) win->src.display, E.xkey.keycode, 0, E.xkey.state & ShiftMask ? 1 : 0); win->event.keyCode = RGFW_apiKeyCodeToRGFW(E.xkey.keycode); - char* str = XKeysymToString(sym); + char* str = (char*)XKeysymToString(sym); if (str != NULL) strncpy(win->event.keyName, str, 16); @@ -2602,14 +2791,13 @@ Start of Linux / Unix defines XGetKeyboardControl((Display*) win->src.display, &keystate); RGFW_updateLockState(win, (keystate.led_mask & 1), (keystate.led_mask & 2)); - RGFW_keyboard[win->event.keyCode].current = (E.type == KeyPress); RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, (E.type == KeyPress)); break; - + } case ButtonPress: case ButtonRelease: - win->event.type = E.type; // the events match + win->event.type = RGFW_mouseButtonPressed + (E.type == ButtonRelease); // the events match switch(win->event.button) { case RGFW_mouseScrollUp: @@ -2623,19 +2811,23 @@ Start of Linux / Unix defines win->event.button = E.xbutton.button; RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + + if (win->event.repeat == RGFW_FALSE) + win->event.repeat = RGFW_isPressed(win, win->event.keyCode); + RGFW_mouseButtons[win->event.button].current = (E.type == ButtonPress); RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, (E.type == ButtonPress)); break; - case MotionNotify: + case MotionNotify: win->event.point.x = E.xmotion.x; win->event.point.y = E.xmotion.y; if ((win->_winArgs & RGFW_HOLD_MOUSE)) { - win->event.point.x = win->_lastMousePoint.x - win->event.point.x; - win->event.point.y = win->_lastMousePoint.y - win->event.point.y; + win->event.point.y = E.xmotion.y; - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + win->event.point.x = win->_lastMousePoint.x - abs(win->event.point.x); + win->event.point.y = win->_lastMousePoint.y - abs(win->event.point.y); } win->_lastMousePoint = RGFW_POINT(E.xmotion.x, E.xmotion.y); @@ -2668,7 +2860,9 @@ Start of Linux / Unix defines if (XIMaskIsSet(raw->valuators.mask, 1) != 0) deltaY += raw->raw_values[1]; - win->event.point = RGFW_POINT((u32)-deltaX, (u32)-deltaY); + win->event.point = RGFW_POINT((i32)deltaX, (i32)deltaY); + + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); win->event.type = RGFW_mousePosChanged; RGFW_mousePosCallback(win, win->event.point); @@ -2855,7 +3049,7 @@ Start of Linux / Unix defines RGFW_dndInitCallback(win, win->event.point); break; - case SelectionNotify: + case SelectionNotify: { /* this is only for checking for xdnd drops */ if (E.xselection.property != XdndSelection || !(win->_winArgs | RGFW_ALLOW_DND)) break; @@ -2878,7 +3072,7 @@ Start of Linux / Unix defines Copyright (c) 2006-2019 Camilla Löwy */ - const char* prefix = "file://"; + const char* prefix = (const char*)"file://"; char* line; @@ -2945,7 +3139,7 @@ Start of Linux / Unix defines RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); break; - + } case FocusIn: win->event.inFocus = 1; win->event.type = RGFW_focusIn; @@ -3222,7 +3416,7 @@ Start of Linux / Unix defines void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { assert(win != NULL); - + if (mouse > (sizeof(RGFW_mouseIconSrc) / sizeof(u8))) return; @@ -3286,7 +3480,7 @@ Start of Linux / Unix defines *size = sizeN; return s; - } + } /* almost all of this function is sourced from GLFW @@ -3309,27 +3503,25 @@ Start of Linux / Unix defines ATOM_PAIR = XInternAtom((Display*) RGFW_root->src.display, "ATOM_PAIR", False); CLIPBOARD_MANAGER = XInternAtom((Display*) RGFW_root->src.display, "CLIPBOARD_MANAGER", False); } - + XSetSelectionOwner((Display*) RGFW_root->src.display, CLIPBOARD, (Window) RGFW_root->src.window, CurrentTime); XConvertSelection((Display*) RGFW_root->src.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, (Window) RGFW_root->src.window, CurrentTime); - for (;;) { XEvent event; XNextEvent((Display*) RGFW_root->src.display, &event); - if (event.type != SelectionRequest) - return; + if (event.type != SelectionRequest) { + break; + } const XSelectionRequestEvent* request = &event.xselectionrequest; XEvent reply = { SelectionNotify }; + reply.xselection.property = 0; - char* selectionString = NULL; const Atom formats[] = { UTF8_STRING, XA_STRING }; const i32 formatCount = sizeof(formats) / sizeof(formats[0]); - - selectionString = (char*) text; if (request->target == TARGETS) { const Atom targets[] = { TARGETS, @@ -3350,11 +3542,11 @@ Start of Linux / Unix defines } if (request->target == MULTIPLE) { - Atom* targets; + Atom* targets = NULL; - Atom actualType; - i32 actualFormat; - unsigned long count, bytesAfter; + Atom actualType = 0; + int actualFormat = 0; + unsigned long count = 0, bytesAfter = 0; XGetWindowProperty((Display*) RGFW_root->src.display, request->requestor, request->property, 0, LONG_MAX, False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); @@ -3375,10 +3567,12 @@ Start of Linux / Unix defines targets[i], 8, PropModeReplace, - (u8*) selectionString, + (u8*) text, textLen); - } else + XFlush(RGFW_root->src.display); + } else { targets[i + 1] = None; + } } XChangeProperty((Display*) RGFW_root->src.display, @@ -3390,6 +3584,7 @@ Start of Linux / Unix defines (u8*) targets, count); + XFlush(RGFW_root->src.display); XFree(targets); reply.xselection.property = request->property; @@ -3402,49 +3597,10 @@ Start of Linux / Unix defines reply.xselection.time = request->time; XSendEvent((Display*) RGFW_root->src.display, request->requestor, False, 0, &reply); + XFlush(RGFW_root->src.display); } } - u16 RGFW_registerJoystick(RGFW_window* win, i32 jsNumber) { - assert(win != NULL); - -#ifdef __linux__ - char file[15]; - sprintf(file, "/dev/input/js%i", jsNumber); - - return RGFW_registerJoystickF(win, file); -#endif - } - - u16 RGFW_registerJoystickF(RGFW_window* win, char* file) { - assert(win != NULL); - -#ifdef __linux__ - - i32 js = open(file, O_RDONLY); - - if (js && RGFW_joystickCount < 4) { - RGFW_joystickCount++; - - RGFW_joysticks[RGFW_joystickCount - 1] = open(file, O_RDONLY); - - u8 i; - for (i = 0; i < 16; i++) - RGFW_jsPressed[RGFW_joystickCount - 1][i] = 0; - - } - - else { -#ifdef RGFW_PRINT_ERRORS - RGFW_error = 1; - fprintf(stderr, "Error RGFW_registerJoystickF : Cannot open file %s\n", file); -#endif - } - - return RGFW_joystickCount - 1; -#endif - } - u8 RGFW_window_isFullscreen(RGFW_window* win) { assert(win != NULL); @@ -3648,9 +3804,10 @@ Start of Linux / Unix defines #ifdef RGFW_OPENGL void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - assert(win != NULL); - - glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); + if (win == NULL) + glXMakeCurrent((Display*) NULL, (Drawable)NULL, (GLXContext) NULL); + else + glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); } #endif @@ -3658,8 +3815,6 @@ Start of Linux / Unix defines void RGFW_window_swapBuffers(RGFW_window* win) { assert(win != NULL); - RGFW_window_makeCurrent(win); - /* clear the window*/ if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) @@ -3693,8 +3848,6 @@ Start of Linux / Unix defines glXSwapBuffers((Display*) win->src.display, (Window) win->src.window); #endif } - - RGFW_window_checkFPS(win); } #if !defined(RGFW_EGL) @@ -3703,9 +3856,9 @@ Start of Linux / Unix defines #if defined(RGFW_OPENGL) ((PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT"))((Display*) win->src.display, (Window) win->src.window, swapInterval); + #else + RGFW_UNUSED(swapInterval); #endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; } #endif @@ -3736,9 +3889,9 @@ Start of Linux / Unix defines RGFW_root = NULL; if ((Drawable) win->src.window) - XDestroyWindow((Display*) win->src.display, (Drawable) win->src.window); /* close the window*/ + XDestroyWindow((Display*) win->src.display, (Drawable) win->src.window); /*!< close the window*/ - XCloseDisplay((Display*) win->src.display); /* kill the display*/ + XCloseDisplay((Display*) win->src.display); /*!< kill the display*/ } #ifdef RGFW_ALLOC_DROPFILES @@ -3757,61 +3910,1083 @@ Start of Linux / Unix defines if (X11Cursorhandle != NULL && RGFW_windowsOpen <= 0) { dlclose(X11Cursorhandle); - X11Cursorhandle = NULL; - } -#endif -#if !defined(RGFW_NO_X11_XI_PRELOAD) - if (X11Xihandle != NULL && RGFW_windowsOpen <= 0) { - dlclose(X11Xihandle); + X11Cursorhandle = NULL; + } +#endif +#if !defined(RGFW_NO_X11_XI_PRELOAD) + if (X11Xihandle != NULL && RGFW_windowsOpen <= 0) { + dlclose(X11Xihandle); + + X11Xihandle = NULL; + } +#endif + + if (RGFW_libxshape != NULL && RGFW_windowsOpen <= 0) { + dlclose(RGFW_libxshape); + RGFW_libxshape = NULL; + } + + if (RGFW_windowsOpen <= 0) { + if (RGFW_eventWait_forceStop[0] || RGFW_eventWait_forceStop[1]){ + close(RGFW_eventWait_forceStop[0]); + close(RGFW_eventWait_forceStop[1]); + } + + u8 i; + for (i = 0; i < RGFW_joystickCount; i++) + close(RGFW_joysticks[i]); + } + + /* set cleared display / window to NULL for error checking */ + win->src.display = (Display*) 0; + win->src.window = (Window) 0; + + RGFW_FREE(win); /*!< free collected window data */ + } + + +/* + End of X11 linux / unix defines +*/ + +#endif /* RGFW_X11 */ + + +/* wayland or X11 defines*/ +#if defined(RGFW_WAYLAND) || defined(RGFW_X11) +#include +#include +#include + u16 RGFW_registerJoystickF(RGFW_window* win, char* file) { + assert(win != NULL); + +#ifdef __linux__ + + i32 js = open(file, O_RDONLY); + + if (js && RGFW_joystickCount < 4) { + RGFW_joystickCount++; + + RGFW_joysticks[RGFW_joystickCount - 1] = open(file, O_RDONLY); + + u8 i; + for (i = 0; i < 16; i++) + RGFW_jsPressed[RGFW_joystickCount - 1][i] = 0; + + } + + else { +#ifdef RGFW_PRINT_ERRORS + RGFW_error = 1; + fprintf(stderr, "Error RGFW_registerJoystickF : Cannot open file %s\n", file); +#endif + } + + return RGFW_joystickCount - 1; +#endif + } + + u16 RGFW_registerJoystick(RGFW_window* win, i32 jsNumber) { + assert(win != NULL); + +#ifdef __linux__ + char file[15]; + sprintf(file, "/dev/input/js%i", jsNumber); + + return RGFW_registerJoystickF(win, file); +#endif + } + + void RGFW_stopCheckEvents(void) { + RGFW_eventWait_forceStop[2] = 1; + while (1) { + const char byte = 0; + const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); + if (result == 1 || result == -1) + break; + } + } + + void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + if (waitMS == 0) + return; + + u8 i; + + if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { + if (pipe(RGFW_eventWait_forceStop) != -1) { + fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); + } + } + + struct pollfd fds[] = { + #ifdef RGFW_WAYLAND + { wl_display_get_fd(win->src.display), POLLIN, 0 }, + #else + { ConnectionNumber(win->src.display), POLLIN, 0 }, + #endif + { RGFW_eventWait_forceStop[0], POLLIN, 0 }, + #ifdef __linux__ /* blank space for 4 joystick files*/ + { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} + #endif + }; + + u8 index = 2; + + #if defined(__linux__) + for (i = 0; i < RGFW_joystickCount; i++) { + if (RGFW_joysticks[i] == 0) + continue; + + fds[index].fd = RGFW_joysticks[i]; + index++; + } + #endif + + + u64 start = RGFW_getTimeNS(); + + #ifdef RGFW_WAYLAND + while (wl_display_dispatch(win->src.display) <= 0 && waitMS >= -1) { + #else + while (XPending(win->src.display) == 0 && waitMS >= -1) { + #endif + if (poll(fds, index, waitMS) <= 0) + break; + + if (waitMS > 0) { + waitMS -= (RGFW_getTimeNS() - start) / 1e+6; + } + } + + /* drain any data in the stop request */ + if (RGFW_eventWait_forceStop[2]) { + char data[64]; + (void)!read(RGFW_eventWait_forceStop[0], data, sizeof(data)); + + RGFW_eventWait_forceStop[2] = 0; + } + } + + u64 RGFW_getTimeNS(void) { + struct timespec ts = { 0 }; + clock_gettime(1, &ts); + unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + + return nanoSeconds; + } + + u64 RGFW_getTime(void) { + struct timespec ts = { 0 }; + clock_gettime(1, &ts); + unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + + return (double)(nanoSeconds) * 1e-9; + } +#endif /* end of wayland or X11 time defines*/ + + +/* + + Start of Wayland defines + + +*/ + +#ifdef RGFW_WAYLAND +/* +Wayland TODO: +- fix RGFW_keyPressed lock state + + RGFW_windowMoved, the window was moved (by the user) + RGFW_windowResized the window was resized (by the user), [on webASM this means the browser was resized] + RGFW_windowRefresh The window content needs to be refreshed + + RGFW_dnd a file has been dropped into the window + RGFW_dnd_init + +- window args: + #define RGFW_NO_RESIZE the window cannot be resized by the user + #define RGFW_ALLOW_DND the window supports drag and drop + #define RGFW_SCALE_TO_MONITOR scale the window to the screen + +- other missing functions functions ("TODO wayland") (~30 functions) +- fix buffer rendering weird behavior +*/ + #include + #include + #include + #include + #include + #include + #include + #include + +RGFW_window* RGFW_key_win = NULL; + +void RGFW_eventPipe_push(RGFW_window* win, RGFW_Event event) { + if (win == NULL) { + win = RGFW_key_win; + + if (win == NULL) return; + } + + if (win->src.eventLen >= (i32)(sizeof(win->src.events) / sizeof(win->src.events[0]))) + return; + + win->src.events[win->src.eventLen] = event; + win->src.eventLen += 1; +} + +RGFW_Event RGFW_eventPipe_pop(RGFW_window* win) { + RGFW_Event ev; + ev.type = 0; + + if (win->src.eventLen > -1) + win->src.eventLen -= 1; + + if (win->src.eventLen >= 0) + ev = win->src.events[win->src.eventLen]; + else { + printf("H2\n"); + } + + return ev; +} + +/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ +#include "xdg-shell.h" +#include "xdg-decoration-unstable-v1.h" + +struct xdg_wm_base *xdg_wm_base; +struct wl_compositor* RGFW_compositor = NULL; +struct wl_shm* shm = NULL; +struct wl_shell* RGFW_shell = NULL; +static struct wl_seat *seat = NULL; +static struct xkb_context *xkb_context; +static struct xkb_keymap *keymap = NULL; +static struct xkb_state *xkb_state = NULL; +enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, RGFW_current_mode; +static struct zxdg_decoration_manager_v1 *decoration_manager = NULL; + +struct wl_cursor_theme* RGFW_wl_cursor_theme = NULL; +struct wl_surface* RGFW_cursor_surface = NULL; +struct wl_cursor_image* RGFW_cursor_image = NULL; + +static void xdg_wm_base_ping_handler(void *data, + struct xdg_wm_base *wm_base, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping_handler, +}; + +b8 RGFW_wl_configured = 0; + +static void xdg_surface_configure_handler(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_surface_ack_configure(xdg_surface, serial); + #ifdef RGFW_DEBUG + printf("Surface configured\n"); + #endif + RGFW_wl_configured = 1; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure_handler, +}; + +static void xdg_toplevel_configure_handler(void *data, + struct xdg_toplevel *toplevel, int32_t width, int32_t height, + struct wl_array *states) +{ + RGFW_UNUSED(data); RGFW_UNUSED(toplevel); RGFW_UNUSED(states) + fprintf(stderr, "XDG toplevel configure: %dx%d\n", width, height); +} + +static void xdg_toplevel_close_handler(void *data, + struct xdg_toplevel *toplevel) +{ + RGFW_UNUSED(data); + RGFW_window* win = (RGFW_window*)xdg_toplevel_get_user_data(toplevel); + if (win == NULL) + win = RGFW_key_win; + + RGFW_Event ev; + ev.type = RGFW_quit; + + RGFW_eventPipe_push(win, ev); + + RGFW_windowQuitCallback(win); +} + +static void shm_format_handler(void *data, + struct wl_shm *shm, uint32_t format) +{ + RGFW_UNUSED(data); RGFW_UNUSED(shm); + fprintf(stderr, "Format %d\n", format); +} + +static const struct wl_shm_listener shm_listener = { + .format = shm_format_handler, +}; + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure_handler, + .close = xdg_toplevel_close_handler, +}; + +RGFW_window* RGFW_mouse_win = NULL; + +static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface_x); RGFW_UNUSED(surface_y); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW_mouse_win = win; + + RGFW_Event ev; + ev.type = RGFW_mouseEnter; + ev.point = win->event.point; + + RGFW_eventPipe_push(win, ev); + + RGFW_mouseNotifyCallBack(win, win->event.point, RGFW_TRUE); +} +static void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_mouse_win == win) + RGFW_mouse_win = NULL; + + RGFW_Event ev; + ev.type = RGFW_mouseLeave; + ev.point = win->event.point; + RGFW_eventPipe_push(win, ev); + + RGFW_mouseNotifyCallBack(win, win->event.point, RGFW_FALSE); +} +static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); + + assert(RGFW_mouse_win != NULL); + + RGFW_Event ev; + ev.type = RGFW_mousePosChanged; + ev.point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)); + RGFW_eventPipe_push(RGFW_mouse_win, ev); + + RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y))); +} +static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); + assert(RGFW_mouse_win != NULL); + + u32 b = (button - 0x110) + 1; + + /* flip right and middle button codes */ + if (b == 2) b = 3; + else if (b == 3) b = 2; + + RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; + RGFW_mouseButtons[b].current = state; + + RGFW_Event ev; + ev.type = RGFW_mouseButtonPressed + state; + ev.button = b; + RGFW_eventPipe_push(RGFW_mouse_win, ev); + + RGFW_mouseButtonCallback(RGFW_mouse_win, b, 0, state); +} +static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); + assert(RGFW_mouse_win != NULL); + + double scroll = wl_fixed_to_double(value); + + RGFW_Event ev; + ev.type = RGFW_mouseButtonPressed; + ev.button = RGFW_mouseScrollUp + (scroll < 0); + RGFW_eventPipe_push(RGFW_mouse_win, ev); + + RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); +} + +void RGFW_doNothing(void) { } +static struct wl_pointer_listener pointer_listener = (struct wl_pointer_listener){&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing}; + +static void keyboard_keymap (void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(format); + + char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref (keymap); + keymap = xkb_keymap_new_from_string (xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap (keymap_string, size); + close (fd); + xkb_state_unref (xkb_state); + xkb_state = xkb_state_new (keymap); +} +static void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(keys); + + RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); + + RGFW_Event ev; + ev.type = RGFW_focusIn; + ev.inFocus = RGFW_TRUE; + RGFW_key_win->event.inFocus = RGFW_TRUE; + + RGFW_eventPipe_push((RGFW_window*)RGFW_mouse_win, ev); + + RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); +} +static void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); + + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_key_win == win) + RGFW_key_win = NULL; + + RGFW_Event ev; + ev.type = RGFW_focusOut; + ev.inFocus = RGFW_FALSE; + win->event.inFocus = RGFW_FALSE; + RGFW_eventPipe_push(win, ev); + + RGFW_focusCallback(win, RGFW_FALSE); +} +static void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + + assert(RGFW_key_win != NULL); + + xkb_keysym_t keysym = xkb_state_key_get_one_sym (xkb_state, key+8); + char name[16]; + xkb_keysym_get_name(keysym, name, 16); + + u32 RGFW_key = RGFW_apiKeyCodeToRGFW(key); + RGFW_keyboard[RGFW_key].prev = RGFW_keyboard[RGFW_key].current; + RGFW_keyboard[RGFW_key].current = state; + RGFW_Event ev; + ev.type = RGFW_keyPressed + state; + ev.keyCode = RGFW_key; + strcpy(ev.keyName, name); + ev.repeat = RGFW_isHeld(RGFW_key_win, RGFW_key); + RGFW_eventPipe_push(RGFW_key_win, ev); + + RGFW_updateLockState(RGFW_key_win, xkb_keymap_mod_get_index(keymap, "Lock"), xkb_keymap_mod_get_index(keymap, "Mod2")); + + RGFW_keyCallback(RGFW_key_win, RGFW_key, name, RGFW_key_win->event.lockState, state); +} +static void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} +static struct wl_keyboard_listener keyboard_listener = {&keyboard_keymap, &keyboard_enter, &keyboard_leave, &keyboard_key, &keyboard_modifiers, (void*)&RGFW_doNothing}; + +static void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { + RGFW_UNUSED(data); + + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + struct wl_pointer *pointer = wl_seat_get_pointer (seat); + wl_pointer_add_listener (pointer, &pointer_listener, NULL); + } + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard (seat); + wl_keyboard_add_listener (keyboard, &keyboard_listener, NULL); + } +} +static struct wl_seat_listener seat_listener = {&seat_capabilities, (void*)&RGFW_doNothing}; + +static void wl_global_registry_handler(void *data, + struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version) +{ + RGFW_UNUSED(data); RGFW_UNUSED(version); + + if (strcmp(interface, "wl_compositor") == 0) { + RGFW_compositor = wl_registry_bind(registry, + id, &wl_compositor_interface, 4); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + xdg_wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(shm, &shm_listener, NULL); + } else if (strcmp(interface,"wl_seat") == 0) { + seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(seat, &seat_listener, NULL); + } + + else { + #ifdef RGFW_DEBUG + printf("did not register %s\n", interface); + return; + #endif + } + + #ifdef RGFW_DEBUG + printf("registered %s\n", interface); + #endif +} + +static void wl_global_registry_remove(void *data, struct wl_registry *registry, uint32_t name) { RGFW_UNUSED(data); RGFW_UNUSED(registry); RGFW_UNUSED(name); } +static const struct wl_registry_listener registry_listener = { + .global = wl_global_registry_handler, + .global_remove = wl_global_registry_remove, +}; + +static const char *get_mode_name(enum zxdg_toplevel_decoration_v1_mode mode) { + switch (mode) { + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: + return "client-side decorations"; + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: + return "server-side decorations"; + } + abort(); +} + + +static void decoration_handle_configure(void *data, + struct zxdg_toplevel_decoration_v1 *decoration, + enum zxdg_toplevel_decoration_v1_mode mode) { + RGFW_UNUSED(data); RGFW_UNUSED(decoration); + printf("Using %s\n", get_mode_name(mode)); + RGFW_current_mode = mode; +} + +static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { + .configure = decoration_handle_configure, +}; + +static void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int anonymous_shm_open(void) { + char name[] = "/RGFW-wayland-XXXXXX"; + int retries = 100; + + do { + randname(name + strlen(name) - 6); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { + #ifdef RGFW_BUFFER + RGFW_window* win = (RGFW_window*)data; + if ((win->_winArgs & RGFW_NO_CPU_RENDER)) + return; + + #ifndef RGFW_X11_DONT_CONVERT_BGR + u32 x, y; + for (y = 0; y < (u32)win->r.h; y++) { + for (x = 0; x < (u32)win->r.w; x++) { + u32 index = (y * 4 * win->r.w) + x * 4; + + u8 red = win->buffer[index]; + win->buffer[index] = win->buffer[index + 2]; + win->buffer[index + 2] = red; + + } + } + #endif + + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_damage_buffer(win->src.surface, 0, 0, win->r.w, win->r.h); + wl_surface_commit(win->src.surface); + #endif +} + +static const struct wl_callback_listener wl_surface_frame_listener = { + .done = wl_surface_frame_done, +}; + + + /* normal wayland RGFW stuff */ + + RGFW_area RGFW_getScreenSize(void) { + RGFW_area area = {}; + + if (RGFW_root != NULL) + /* this isn't right but it's here for buffers */ + area = RGFW_AREA(RGFW_root->r.w, RGFW_root->r.h); + + /* TODO wayland */ + return area; + } + + void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + } + + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win); RGFW_UNUSED(r); + + /* TODO wayland */ + } + + + RGFWDEF void RGFW_init_buffer(RGFW_window* win); + void RGFW_init_buffer(RGFW_window* win) { + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + size_t size = win->r.w * win->r.h * 4; + int fd = create_shm_file(size); + if (fd < 0) { + fprintf(stderr, "Failed to create a buffer. size: %ld\n", size); + exit(1); + } + + win->buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (win->buffer == MAP_FAILED) { + fprintf(stderr, "mmap failed!\n"); + close(fd); + exit(1); + } + + struct wl_shm_pool* pool = wl_shm_create_pool(shm, fd, size); + win->src.wl_buffer = wl_shm_pool_create_buffer(pool, 0, win->r.w, win->r.h, win->r.w * 4, + WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_commit(win->src.surface); + + u8 color[] = {0x00, 0x00, 0x00, 0xFF}; + + size_t i; + for (i = 0; i < size; i += 4) { + memcpy(&win->buffer[i], color, 4); + } + + #if defined(RGFW_OSMESA) + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + #endif + #else + RGFW_UNUSED(win); + #endif + } + + + RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { + RGFW_window* win = RGFW_window_basic_init(rect, args); + + fprintf(stderr, "Warning: RGFW Wayland support is experimental\n"); + + win->src.display = wl_display_connect(NULL); + if (win->src.display == NULL) { + #ifdef RGFW_DEBUG + fprintf(stderr, "Failed to load Wayland display\n"); + #endif + return NULL; + } + + struct wl_registry *registry = wl_display_get_registry(win->src.display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + + wl_display_dispatch(win->src.display); + wl_display_roundtrip(win->src.display); + + if (RGFW_compositor == NULL) { + #ifdef RGFW_DEBUG + fprintf(stderr, "Can't find compositor.\n"); + #endif + + return NULL; + } + + if (RGFW_wl_cursor_theme == NULL) { + RGFW_wl_cursor_theme = wl_cursor_theme_load(NULL, 24, shm); + RGFW_cursor_surface = wl_compositor_create_surface(RGFW_compositor); + + struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, "left_ptr"); + RGFW_cursor_image = cursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + if (RGFW_root == NULL) + xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); + + xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + win->src.surface = wl_compositor_create_surface(RGFW_compositor); + wl_surface_set_user_data(win->src.surface, win); + + win->src.xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, win->src.surface); + xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, NULL); + + xdg_wm_base_set_user_data(xdg_wm_base, win); + + win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); + xdg_toplevel_set_user_data(win->src.xdg_toplevel, win); + xdg_toplevel_set_title(win->src.xdg_toplevel, name); + xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, NULL); + + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); + + if (!(args & RGFW_NO_BORDER)) { + win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + decoration_manager, win->src.xdg_toplevel); + } + + + if (args & RGFW_OPENGL_SOFTWARE) + setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); + + wl_display_roundtrip(win->src.display); + + wl_surface_commit(win->src.surface); + + /* wait for the surface to be configured */ + while (wl_display_dispatch(win->src.display) != -1 && !RGFW_wl_configured) { } + + + #ifdef RGFW_OPENGL + if ((args & RGFW_NO_INIT_API) == 0) { + win->src.window = wl_egl_window_create(win->src.surface, win->r.w, win->r.h); + RGFW_createOpenGLContext(win); + } + #endif + + RGFW_init_buffer(win); + + struct wl_callback* callback = wl_surface_frame(win->src.surface); + wl_callback_add_listener(callback, &wl_surface_frame_listener, win); + wl_surface_commit(win->src.surface); + + if (args & RGFW_HIDE_MOUSE) { + RGFW_window_showMouse(win, 0); + } + + if (RGFW_root == NULL) { + RGFW_root = win; + } + + win->src.eventIndex = 0; + win->src.eventLen = 0; + + return win; + } + + RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { + if (win->_winArgs & RGFW_WINDOW_HIDE) + return NULL; + + if (win->src.eventIndex == 0) { + if (wl_display_roundtrip(win->src.display) == -1) { + return NULL; + } + RGFW_resetKey(); + } + + #ifdef __linux__ + RGFW_Event* event = RGFW_linux_updateJoystick(win); + if (event != NULL) + return event; + #endif + + if (win->src.eventLen == 0) { + return NULL; + } + + RGFW_Event ev = RGFW_eventPipe_pop(win); + + if (ev.type == 0 || win->event.type == RGFW_quit) { + return NULL; + } + + ev.frameTime = win->event.frameTime; + ev.frameTime2 = win->event.frameTime2; + ev.inFocus = win->event.inFocus; + win->event = ev; + + return &win->event; + } + + + void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); RGFW_UNUSED(a); + + /* TODO wayland */ + } + + void RGFW_window_move(RGFW_window* win, RGFW_point v) { + RGFW_UNUSED(win); RGFW_UNUSED(v); + + /* TODO wayland */ + } + + void RGFW_window_setIcon(RGFW_window* win, u8* src, RGFW_area a, i32 channels) { + RGFW_UNUSED(win); RGFW_UNUSED(src); RGFW_UNUSED(a); RGFW_UNUSED(channels) + /* TODO wayland */ + } + + void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { + RGFW_UNUSED(win); RGFW_UNUSED(v); + + /* TODO wayland */ + } + + void RGFW_window_showMouse(RGFW_window* win, i8 show) { + RGFW_UNUSED(win); + + if (show) { + + } + else { + + } + + /* TODO wayland */ + } + + b8 RGFW_window_isMaximized(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + b8 RGFW_window_isMinimized(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + b8 RGFW_window_isHidden(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + b8 RGFW_window_isFullscreen(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return RGFW_POINT(0, 0); + } + + RGFW_point RGFW_getGlobalMousePoint(void) { + /* TODO wayland */ + return RGFW_POINT(0, 0); + } + + void RGFW_window_show(RGFW_window* win) { + //wl_surface_attach(win->src.surface, win->rc., 0, 0); + wl_surface_commit(win->src.surface); + + if (win->_winArgs & RGFW_WINDOW_HIDE) + win->_winArgs ^= RGFW_WINDOW_HIDE; + } + + void RGFW_window_hide(RGFW_window* win) { + wl_surface_attach(win->src.surface, NULL, 0, 0); + wl_surface_commit(win->src.surface); + win->_winArgs |= RGFW_WINDOW_HIDE; + } + + void RGFW_window_setMouseDefault(RGFW_window* win) { + RGFW_UNUSED(win); + + RGFW_window_setMouseStandard(win, RGFW_MOUSE_NORMAL); + } + + void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_UNUSED(win); + + static const char* iconStrings[] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; + + struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); + RGFW_cursor_image = cursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 channels) { + RGFW_UNUSED(win); RGFW_UNUSED(image); RGFW_UNUSED(a); RGFW_UNUSED(channels) + //struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); + //RGFW_cursor_image = image; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + void RGFW_window_setName(RGFW_window* win, char* name) { + xdg_toplevel_set_title(win->src.xdg_toplevel, name); + } + + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { + RGFW_UNUSED(win); RGFW_UNUSED(passthrough); + + /* TODO wayland */ + } + + void RGFW_window_setBorder(RGFW_window* win, b8 border) { + RGFW_UNUSED(win); RGFW_UNUSED(border); + + /* TODO wayland */ + } + + void RGFW_window_restore(RGFW_window* win) { + RGFW_UNUSED(win); + + /* TODO wayland */ + } + + void RGFW_window_minimize(RGFW_window* win) { + RGFW_UNUSED(win); + + /* TODO wayland */ + } + + void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); RGFW_UNUSED(a); + + /* TODO wayland */ + } + + void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); RGFW_UNUSED(a); + + /* TODO wayland */ + } + + RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { + RGFW_monitor m = {}; + RGFW_UNUSED(win); + RGFW_UNUSED(m); + /* TODO wayland */ + + return m; + } + + + #ifndef RGFW_EGL + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } + #endif + + void RGFW_window_swapBuffers(RGFW_window* win) { + assert(win != NULL); - X11Xihandle = NULL; + /* clear the window*/ + #ifdef RGFW_BUFFER + wl_surface_frame_done(win, NULL, 0); + if (!(win->_winArgs & RGFW_NO_GPU_RENDER)) + #endif + { + #ifdef RGFW_OPENGL + eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); + #endif } -#endif + + wl_display_flush(win->src.display); + } - if (RGFW_libxshape != NULL && RGFW_windowsOpen <= 0) { - dlclose(RGFW_libxshape); - RGFW_libxshape = NULL; + void RGFW_window_close(RGFW_window* win) { + #ifdef RGFW_EGL + RGFW_closeEGL(win); + #endif + + if (RGFW_root == win) { + RGFW_root = NULL; } - if (RGFW_windowsOpen <= 0) { - if (RGFW_eventWait_forceStop[0] || RGFW_eventWait_forceStop[1]){ - close(RGFW_eventWait_forceStop[0]); - close(RGFW_eventWait_forceStop[1]); - } + xdg_toplevel_destroy(win->src.xdg_toplevel); + xdg_surface_destroy(win->src.xdg_surface); + wl_surface_destroy(win->src.surface); - u8 i; - for (i = 0; i < RGFW_joystickCount; i++) - close(RGFW_joysticks[i]); - } + #ifdef RGFW_BUFFER + wl_buffer_destroy(win->src.wl_buffer); + #endif + + wl_display_disconnect(win->src.display); + RGFW_FREE(win); + } - /* set cleared display / window to NULL for error checking */ - win->src.display = (Display*) 0; - win->src.window = (Window) 0; + RGFW_monitor RGFW_getPrimaryMonitor(void) { + /* TODO wayland */ - RGFW_FREE(win); /* free collected window data */ + return (RGFW_monitor){}; } + + RGFW_monitor* RGFW_getMonitors(void) { + /* TODO wayland */ - u64 RGFW_getTimeNS(void) { - struct timespec ts = { 0 }; - clock_gettime(1, &ts); - unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + return NULL; + } - return nanoSeconds; + void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(text); RGFW_UNUSED(textLen); + + /* TODO wayland */ } - u64 RGFW_getTime(void) { - struct timespec ts = { 0 }; - clock_gettime(1, &ts); - unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + char* RGFW_readClipboard(size_t* size) { + RGFW_UNUSED(size); - return (double)(nanoSeconds) * 1e-9; + /* TODO wayland */ + + return NULL; } +#endif /* RGFW_WAYLAND */ + /* - End of linux / unix defines + End of Wayland defines */ -#endif /* RGFW_X11 */ - /* @@ -3822,6 +4997,8 @@ Start of Linux / Unix defines #ifdef RGFW_WINDOWS #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE + #include #include #include @@ -3829,8 +5006,6 @@ Start of Linux / Unix defines #include #include #include - #include - #include #ifndef RGFW_NO_XINPUT typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); @@ -3873,13 +5048,6 @@ Start of Linux / Unix defines #define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc - -#if defined(RGFW_DIRECTX) - RGFW_directXinfo RGFW_dxInfo; - - RGFW_directXinfo* RGFW_getDirectXInfo(void) { return &RGFW_dxInfo; } -#endif - void* RGFWjoystickApi = NULL; /* these two wgl functions need to be preloaded */ @@ -3893,7 +5061,6 @@ Start of Linux / Unix defines #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_NO_ACCELERATION_ARB 0x2025 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 @@ -3990,7 +5157,7 @@ static HMODULE wglinstance = NULL; #endif __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); - + #ifndef RGFW_NO_XINPUT void RGFW_loadXInput(void) { u32 i; @@ -4048,7 +5215,7 @@ static HMODULE wglinstance = NULL; OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else -RGFW_UNUSED(win); /* if buffer rendering is not being used */ +RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ #endif } @@ -4056,16 +5223,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ DragAcceptFiles(win->src.window, allow); } + void RGFW_releaseCursor(RGFW_window* win) { + ClipCursor(NULL); + const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + RegisterRawInputDevices(&id, 1, sizeof(id)); + } + void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { RGFW_UNUSED(win) - - if (!rect.x && !rect.y && rect.w && !rect.h) { - ClipCursor(NULL); - const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; - RegisterRawInputDevices(&id, 1, sizeof(id)); - - return; - } RECT clipRect; GetClientRect(win->src.window, &clipRect); @@ -4101,9 +5266,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ wglGetCurrentContextSRC = (PFN_wglGetCurrentContext) GetProcAddress(wglinstance, "wglGetCurrentContext"); #endif } - - timeBeginPeriod(1); - + if (name[0] == 0) name = (char*) " "; RGFW_eventWindow.r = RGFW_RECT(-1, -1, -1, -1); @@ -4117,7 +5280,12 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ HINSTANCE inh = GetModuleHandleA(NULL); - WNDCLASSA Class = { 0 }; /* Setup the Window class. */ + #ifndef __cplusplus + WNDCLASSA Class = { 0 }; /*!< Setup the Window class. */ + #else + WNDCLASSA Class = { }; + #endif + Class.lpszClassName = name; Class.hInstance = inh; Class.hCursor = LoadCursor(NULL, IDC_ARROW); @@ -4130,7 +5298,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RECT windowRect, clientRect; if (!(args & RGFW_NO_BORDER)) { - window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_VISIBLE | WS_MINIMIZEBOX; + window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; if (!(args & RGFW_NO_RESIZE)) window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; @@ -4215,17 +5383,26 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL HDC dummy_dc = GetDC(dummyWin); - - PIXELFORMATDESCRIPTOR pfd = { - .nSize = sizeof(pfd), - .nVersion = 1, - .iPixelType = PFD_TYPE_RGBA, - .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - .cColorBits = 24, - .cAlphaBits = 8, - .iLayerType = PFD_MAIN_PLANE, - .cDepthBits = 32, - .cStencilBits = 8, + + u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + + //if (RGFW_DOUBLE_BUFFER) + pfd_flags |= PFD_DOUBLEBUFFER; + + PIXELFORMATDESCRIPTOR pfd = { + sizeof(pfd), + 1, /* version */ + pfd_flags, + PFD_TYPE_RGBA, /* ipixel type */ + 24, /* color bits */ + 0, 0, 0, 0, 0, 0, + 8, /* alpha bits */ + 0, 0, 0, 0, 0, 0, + 32, /* depth bits */ + 8, /* stencil bits */ + 0, + PFD_MAIN_PLANE, /* Layer type */ + 0, 0, 0, 0 }; int pixel_format = ChoosePixelFormat(dummy_dc, &pfd); @@ -4242,41 +5419,41 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ wglMakeCurrent(dummy_dc, 0); wglDeleteContext(dummy_context); ReleaseDC(dummyWin, dummy_dc); - + + /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ if (wglCreateContextAttribsARB != NULL) { - PIXELFORMATDESCRIPTOR pfd = (PIXELFORMATDESCRIPTOR){ sizeof(pfd), 1, PFD_TYPE_RGBA, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, 32, 8, PFD_MAIN_PLANE, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; if (args & RGFW_OPENGL_SOFTWARE) pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; if (wglChoosePixelFormatARB != NULL) { - i32* pixel_format_attribs = (i32*)RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); + i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(args & RGFW_OPENGL_SOFTWARE); int pixel_format; UINT num_formats; wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &pixel_format, &num_formats); if (!num_formats) { - printf("Failed to set the OpenGL 3.3 pixel format.\n"); + printf("Failed to create a pixel format for WGL.\n"); } DescribePixelFormat(win->src.hdc, pixel_format, sizeof(pfd), &pfd); if (!SetPixelFormat(win->src.hdc, pixel_format, &pfd)) { - printf("Failed to set the OpenGL 3.3 pixel format.\n"); + printf("Failed to set the WGL pixel format.\n"); } } - + + /* create opengl/WGL context for the specified version */ u32 index = 0; i32 attribs[40]; - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); - - if (RGFW_majorVersion || RGFW_minorVersion) { - SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_majorVersion); - SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_minorVersion); + if (RGFW_profile == RGFW_GL_CORE) { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB); } - - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); - + else { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); + } + if (RGFW_majorVersion || RGFW_minorVersion) { SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_majorVersion); SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_minorVersion); @@ -4285,7 +5462,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ SET_ATTRIB(0, 0); win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); - } else { + } else { /* fall back to a default context (probably opengl 2 or something) */ fprintf(stderr, "Failed to create an accelerated OpenGL Context\n"); int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); @@ -4659,7 +5836,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ CharLowerBuffA(keyName, 16); } } - + RGFW_updateLockState(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001)); strncpy(win->event.keyName, keyName, 16); @@ -4670,6 +5847,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } win->event.type = RGFW_keyPressed; + win->event.repeat = RGFW_isPressed(win, win->event.keyCode); RGFW_keyboard[win->event.keyCode].current = 1; RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 1); break; @@ -4706,8 +5884,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ break; win->event.type = RGFW_mousePosChanged; - win->event.point.x = -raw->data.mouse.lLastX; - win->event.point.y = -raw->data.mouse.lLastY; + win->event.point.x = raw->data.mouse.lLastX; + win->event.point.y = raw->data.mouse.lLastY; break; } @@ -4830,7 +6008,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ u8 RGFW_window_isFullscreen(RGFW_window* win) { assert(win != NULL); + #ifndef __cplusplus WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMAXIMIZED; } @@ -4844,7 +6026,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ u8 RGFW_window_isMinimized(RGFW_window* win) { assert(win != NULL); + #ifndef __cplusplus WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMINIMIZED; } @@ -4852,7 +6038,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ u8 RGFW_window_isMaximized(RGFW_window* win) { assert(win != NULL); + #ifndef __cplusplus WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMAXIMIZED; } @@ -4892,7 +6082,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ for (deviceIndex = 0; EnumDisplayDevicesA(0, (DWORD) deviceIndex, &dd, 0); deviceIndex++) { char* deviceName = dd.DeviceName; if (EnumDisplayDevicesA(deviceName, info.iIndex, &dd, 0)) { - strncpy(monitor.name, dd.DeviceString, 128); /* copy the monitor's name */ + strncpy(monitor.name, dd.DeviceString, 128); /*!< copy the monitor's name */ break; } } @@ -4904,6 +6094,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ monitor.rect.h = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; #ifndef RGFW_NO_DPI + #ifndef USER_DEFAULT_SCREEN_DPI + #define USER_DEFAULT_SCREEN_DPI 96 + #endif + if (GetDpiForMonitor != NULL) { u32 x, y; GetDpiForMonitor(src, MDT_ANGULAR_DPI, &x, &y); @@ -4945,8 +6139,12 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } RGFW_monitor RGFW_getPrimaryMonitor(void) { + #ifdef __cplusplus + return win32CreateMonitor(MonitorFromPoint({ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + #else return win32CreateMonitor(MonitorFromPoint((POINT) { 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); - } + #endif + } RGFW_monitor* RGFW_getMonitors(void) { RGFW_mInfo info; @@ -5102,10 +6300,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #endif #ifdef RGFW_OPENGL - wglDeleteContext((HGLRC) win->src.ctx); /* delete opengl context */ + wglDeleteContext((HGLRC) win->src.ctx); /*!< delete opengl context */ #endif - DeleteDC(win->src.hdc); /* delete device context */ - DestroyWindow(win->src.window); /* delete window */ + DeleteDC(win->src.hdc); /*!< delete device context */ + DestroyWindow(win->src.window); /*!< delete window */ #if defined(RGFW_OSMESA) if (win->buffer != NULL) @@ -5292,8 +6490,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - assert(win != NULL); - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); + if (win == NULL) + wglMakeCurrent(NULL, NULL); + else + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); } #endif @@ -5308,7 +6508,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (loadSwapFunc == NULL) { fprintf(stderr, "wglSwapIntervalEXT not supported\n"); - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; return; } @@ -5319,18 +6518,15 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (wglSwapIntervalEXT(swapInterval) == FALSE) fprintf(stderr, "Failed to set swap interval\n"); - #endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + #else + RGFW_UNUSED(swapInterval); + #endif } #endif void RGFW_window_swapBuffers(RGFW_window* win) { - assert(win != NULL); - - RGFW_window_makeCurrent(win); - + //assert(win != NULL); /* clear the window*/ if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { @@ -5356,8 +6552,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->src.swapchain->lpVtbl->Present(win->src.swapchain, 0, 0); #endif } - - RGFW_window_checkFPS(win); } char* createUTF8FromWideStringWin32(const WCHAR* source) { @@ -5378,20 +6572,28 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return target; } + + static inline LARGE_INTEGER RGFW_win32_initTimer(void) { + static LARGE_INTEGER frequency = {{0, 0}}; + if (frequency.QuadPart == 0) { + timeBeginPeriod(1); + QueryPerformanceFrequency(&frequency); + } + + return frequency; + } u64 RGFW_getTimeNS(void) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); + LARGE_INTEGER frequency = RGFW_win32_initTimer(); LARGE_INTEGER counter; QueryPerformanceCounter(&counter); - return (u64) (counter.QuadPart * 1e9 / frequency.QuadPart); + return (u64) ((counter.QuadPart * 1e9) / frequency.QuadPart); } u64 RGFW_getTime(void) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); + LARGE_INTEGER frequency = RGFW_win32_initTimer(); LARGE_INTEGER counter; QueryPerformanceCounter(&counter); @@ -5720,11 +6922,9 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSWindowStyleMaskHUDWindow = 1 << 13 }; - typedef const char* NSPasteboardType; NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text"; // Replaces NSStringPboardType - typedef NS_ENUM(i32, NSDragOperation) { NSDragOperationNone = 0, NSDragOperationCopy = 1, @@ -6002,7 +7202,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else - RGFW_UNUSED(win); /* if buffer rendering is not being used */ + RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ #endif } @@ -6063,20 +7263,22 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL if ((args & RGFW_NO_INIT_API) == 0) { - void* attrs = RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); + void* attrs = RGFW_initFormatAttribs(args & RGFW_OPENGL_SOFTWARE); void* format = NSOpenGLPixelFormat_initWithAttributes(attrs); if (format == NULL) { - printf("Failed to load pixel format "); + printf("Failed to load pixel format for OpenGL\n"); - void* attrs = RGFW_initAttribs(1); + void* attrs = RGFW_initFormatAttribs(1); format = NSOpenGLPixelFormat_initWithAttributes(attrs); if (format == NULL) printf("and loading software rendering OpenGL failed\n"); else printf("Switching to software rendering\n"); } - + + /* the pixel format can be passed directly to opengl context creation to create a context + this is because the format also includes information about the opengl version (which may be a bad thing) */ win->src.view = NSOpenGLView_initWithFrame((NSRect){{0, 0}, {win->r.w, win->r.h}}, format); objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); @@ -6164,6 +7366,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } // Show the window + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); ((id(*)(id, SEL, SEL))objc_msgSend)(win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), true); @@ -6216,7 +7419,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ CGPoint point = CGEventGetLocation(e); CFRelease(e); - return RGFW_POINT((u32) point.x, (u32) point.y); /* the point is loaded during event checks */ + return RGFW_POINT((u32) point.x, (u32) point.y); /*!< the point is loaded during event checks */ } RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { @@ -6340,7 +7543,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); - void* date = (void*) ((id(*)(id, SEL, double))objc_msgSend) + void* date = (void*) ((id(*)(Class, SEL, double))objc_msgSend) (objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), waitMS); NSEvent* e = (NSEvent*) ((id(*)(id, SEL, NSEventMask, void*, NSString*, bool))objc_msgSend) @@ -6411,7 +7614,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.type = RGFW_mouseEnter; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + win->event.point = RGFW_POINT((i32) p.x, (i32) (win->r.h - p.y)); RGFW_mouseNotifyCallBack(win, win->event.point, 1); break; } @@ -6429,6 +7632,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.type = RGFW_keyPressed; char* str = (char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("characters"))); strncpy(win->event.keyName, str, 16); + win->event.repeat = RGFW_isPressed(win, win->event.keyCode); RGFW_keyboard[win->event.keyCode].current = 1; RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 1); @@ -6501,7 +7705,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - win->event.point = RGFW_POINT((u32) p.x, (u32) (p.y)); + win->event.point = RGFW_POINT((i32)p.x, (i32)p.y); } RGFW_mousePosCallback(win, win->event.point); @@ -6727,12 +7931,16 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ CGDisplayShowCursor(kCGDirectMainDisplay); objc_msgSend_void(mouse, sel_registerName("set")); } + + void RGFW_releaseCursor(RGFW_window* win) { + CGAssociateMouseAndMouseCursorPosition(1); + } void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win) CGWarpMouseCursorPosition(CGPointMake(r.x + (r.w / 2), r.y + (r.h / 2))); - CGAssociateMouseAndMouseCursorPosition((!r.x && !r.y && r.w && !r.h)); + CGAssociateMouseAndMouseCursorPosition(0); } void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { @@ -6881,8 +8089,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSOpenGLContext_setValues(win->src.ctx, &swapInterval, 222); #endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; } #endif @@ -6910,9 +8116,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void RGFW_window_swapBuffers(RGFW_window* win) { assert(win != NULL); - - RGFW_window_makeCurrent(win); - /* clear the window*/ if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { @@ -6953,8 +8156,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); #endif } - - RGFW_window_checkFPS(win); } void RGFW_window_close(RGFW_window* win) { @@ -7005,23 +8206,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ */ /* - Start of Web ASM defines + WEBASM defines */ #ifdef RGFW_WEBASM - - -#define RGFW_jsButtonPressed 7 /*!< a joystick button was pressed */ -#define RGFW_jsButtonReleased 8 /*!< a joystick button was released */ -#define RGFW_jsAxisMove 9 /*!< an axis of a joystick was moved*/ - -#define RGFW_mouseEnter 14 /* mouse entered the window */ -#define RGFW_mouseLeave 15 /* mouse left the window */ - RGFW_Event RGFW_events[20]; size_t RGFW_eventLen = 0; -EM_BOOL on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { +EM_BOOL Emscripten_on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_keyPressed; @@ -7032,12 +8224,12 @@ EM_BOOL on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userDa RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].prev = RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current; RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current = 1; - RGFW_keyCallback(RGFW_root, e->keyCode, RGFW_events[RGFW_eventLen].keyName, 0, 1); - + RGFW_keyCallback(RGFW_root, RGFW_apiKeyCodeToRGFW(e->keyCode), RGFW_events[RGFW_eventLen].keyName, 0, 1); + return EM_TRUE; } -EM_BOOL on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { +EM_BOOL Emscripten_on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_keyReleased; @@ -7049,12 +8241,12 @@ EM_BOOL on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].prev = RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current; RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current = 0; - RGFW_keyCallback(RGFW_root, e->keyCode, RGFW_events[RGFW_eventLen].keyName, 0, 0); + RGFW_keyCallback(RGFW_root, RGFW_apiKeyCodeToRGFW(e->keyCode), RGFW_events[RGFW_eventLen].keyName, 0, 0); return EM_TRUE; } -EM_BOOL on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { +EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_windowResized; @@ -7064,7 +8256,7 @@ EM_BOOL on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { return EM_TRUE; } -EM_BOOL on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { +EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_windowResized; @@ -7075,7 +8267,7 @@ EM_BOOL on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent return EM_TRUE; } -EM_BOOL on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { +EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); RGFW_events[RGFW_eventLen].type = RGFW_focusIn; @@ -7086,7 +8278,7 @@ EM_BOOL on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) return EM_TRUE; } -EM_BOOL on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { +EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); RGFW_events[RGFW_eventLen].type = RGFW_focusOut; @@ -7097,13 +8289,13 @@ EM_BOOL on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData return EM_TRUE; } -EM_BOOL on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; if ((RGFW_root->_winArgs & RGFW_HOLD_MOUSE)) { - RGFW_point p = RGFW_POINT(-e->movementX, -e->movementY); + RGFW_point p = RGFW_POINT(e->movementX, e->movementY); RGFW_events[RGFW_eventLen].point = p; } else @@ -7114,7 +8306,7 @@ EM_BOOL on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userDat return EM_TRUE; } -EM_BOOL on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; @@ -7131,7 +8323,7 @@ EM_BOOL on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userDat return EM_TRUE; } -EM_BOOL on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; @@ -7147,7 +8339,7 @@ EM_BOOL on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) return EM_TRUE; } -EM_BOOL on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { +EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; @@ -7164,54 +8356,82 @@ EM_BOOL on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { return EM_TRUE; } -EM_BOOL on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; - RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); - RGFW_events[RGFW_eventLen].button = 1; - RGFW_events[RGFW_eventLen].scroll = 0; + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_events[RGFW_eventLen].button = 1; + RGFW_events[RGFW_eventLen].scroll = 0; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; - RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); - RGFW_eventLen++; + RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); + RGFW_eventLen++; + } return EM_TRUE; } -EM_BOOL on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + + RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); + RGFW_eventLen++; + } + return EM_TRUE; +} - RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; - RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); - - RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); - RGFW_eventLen++; - +EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_events[RGFW_eventLen].button = 1; + RGFW_events[RGFW_eventLen].scroll = 0; + + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 0; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 0); + RGFW_eventLen++; + } return EM_TRUE; } -EM_BOOL on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } + +EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; - RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); - RGFW_events[RGFW_eventLen].button = 1; - RGFW_events[RGFW_eventLen].scroll = 0; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 0; + if (gamepadEvent->index >= 4) + return 0; - RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 0); - RGFW_eventLen++; + RGFW_joysticks[gamepadEvent->index] = gamepadEvent->connected; - return EM_TRUE; + return 1; // The event was consumed by the callback handler } -EM_BOOL on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } +void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { + if (!(RGFW_root->_winArgs & RGFW_ALLOW_DND)) + return; + RGFW_events[RGFW_eventLen].droppedFilesCount = count; + RGFW_dndCallback(RGFW_root, RGFW_events[RGFW_eventLen].droppedFiles, count); + RGFW_eventLen++; +} b8 RGFW_stopCheckEvents_bool = RGFW_FALSE; void RGFW_stopCheckEvents(void) { @@ -7221,6 +8441,9 @@ void RGFW_stopCheckEvents(void) { void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { RGFW_UNUSED(win); + if (waitMS == 0) + return; + u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); while ((RGFW_eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && @@ -7244,25 +8467,57 @@ void RGFW_init_buffer(RGFW_window* win) { OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else - RGFW_UNUSED(win); /* if buffer rendering is not being used */ + RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ #endif } +void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { + /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ + /* TODO: find a better way to do this, + strcpy doesn't seem to work, maybe because of asyncio + */ + + RGFW_events[RGFW_eventLen].type = RGFW_dnd; + char** arr = (char**)&RGFW_events[RGFW_eventLen].droppedFiles[index]; + *arr = file; +} + +#include +#include +#include + +void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } + +void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { + FILE* file = fopen(path, "w+"); + if (file == NULL) + return; + + fwrite(data, sizeof(char), len, file); + fclose(file); +} + RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { RGFW_UNUSED(name) - RGFW_UNUSED(RGFW_initAttribs); - + RGFW_UNUSED(RGFW_initFormatAttribs); + RGFW_window* win = RGFW_window_basic_init(rect, args); EmscriptenWebGLContextAttributes attrs; attrs.alpha = EM_TRUE; attrs.depth = EM_TRUE; + attrs.alpha = EM_TRUE; attrs.stencil = RGFW_STENCIL; attrs.antialias = RGFW_SAMPLES; attrs.premultipliedAlpha = EM_TRUE; attrs.preserveDrawingBuffer = EM_FALSE; - attrs.renderViaOffscreenBackBuffer = RGFW_AUX_BUFFERS; + + if (RGFW_DOUBLE_BUFFER == 0) + attrs.renderViaOffscreenBackBuffer = 0; + else + attrs.renderViaOffscreenBackBuffer = RGFW_AUX_BUFFERS; + attrs.failIfMajorPerformanceCaveat = EM_FALSE; attrs.majorVersion = (RGFW_majorVersion == 0) ? 1 : RGFW_majorVersion; attrs.minorVersion = RGFW_minorVersion; @@ -7279,22 +8534,79 @@ RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { #endif emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); + emscripten_set_window_title(name); /* load callbacks */ - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_keydown); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_keyup); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_resize); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, on_fullscreenchange); - emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, on_mousemove); - emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, on_touchstart); - emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, on_touchend); - emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, on_touchmove); - emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, on_touchcancel); - emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, on_mousedown); - emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, on_mouseup); - emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, on_wheel); - emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_focusin); - emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_focusout); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_keydown); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_keyup); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); + emscripten_set_gamepadconnected_callback(NULL, 1, Emscripten_on_gamepad); + emscripten_set_gamepaddisconnected_callback(NULL, 1, Emscripten_on_gamepad); + + if (args & RGFW_ALLOW_DND) { + win->_winArgs |= RGFW_ALLOW_DND; + } + + EM_ASM({ + var canvas = document.getElementById('canvas'); + canvas.addEventListener('drop', function(e) { + e.preventDefault(); + if (e.dataTransfer.file < 0) + return; + + var filenamesArray = []; + var count = e.dataTransfer.files.length; + + /* Read and save the files to emscripten's files */ + var drop_dir = '.rgfw_dropped_files'; + Module._RGFW_mkdir(drop_dir); + + for (var i = 0; i < count; i++) { + var file = e.dataTransfer.files[i]; + + var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); + var reader = new FileReader(); + + reader.onloadend = (e) => { + if (reader.readyState != 2) { + out('failed to read dropped file: '+file.name+': '+reader.error); + } + else { + var data = e.target.result; + + _RGFW_writeFile(path, new Uint8Array(data), file.size); + } + }; + + reader.readAsArrayBuffer(file); + // This works weird on modern opengl + var filename = stringToNewUTF8(path); + + filenamesArray.push(filename); + + Module._RGFW_makeSetValue(i, filename); + } + + Module._Emscripten_onDrop(count); + + for (var i = 0; i < count; ++i) { + _free(filenamesArray[i]); + } + }, true); + + canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); + }); RGFW_init_buffer(win); glViewport(0, 0, rect.w, rect.h); @@ -7314,14 +8626,59 @@ RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { static u8 index = 0; - + if (index == 0) RGFW_resetKey(); + /* check gamepads */ + for (int i = 0; (i < emscripten_get_num_gamepads()) && (i < 4); i++) { + if (RGFW_joysticks[i] == 0) + continue;; + + EmscriptenGamepadEvent gamepadState; + + if (emscripten_get_gamepad_status(i, &gamepadState) != EMSCRIPTEN_RESULT_SUCCESS) + break; + + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < 16); j++) { + u32 map[] = { + RGFW_JS_A, RGFW_JS_X, RGFW_JS_B, RGFW_JS_Y, + RGFW_JS_L1, RGFW_JS_R1, RGFW_JS_L2, RGFW_JS_R2, + RGFW_JS_SELECT, RGFW_JS_START, + 0, 0, + RGFW_JS_UP, RGFW_JS_DOWN, RGFW_JS_LEFT, RGFW_JS_RIGHT + }; + + u32 button = map[j]; + if (RGFW_jsPressed[i][button] != gamepadState.digitalButton[j]) { + win->event.type = RGFW_jsButtonPressed; + win->event.joystick = i; + win->event.button = map[j]; + return &win->event; + } + + RGFW_jsPressed[i][button] = gamepadState.digitalButton[j]; + } + + for (int j = 0; (j < gamepadState.numAxes) && (j < 4); j += 2) { + win->event.axisesCount = gamepadState.numAxes; + if (win->event.axis[j].x != gamepadState.axis[j] || + win->event.axis[j].y != gamepadState.axis[j + 1] + ) { + win->event.axis[j].x = gamepadState.axis[j]; + win->event.axis[j].y = gamepadState.axis[j + 1]; + win->event.type = RGFW_jsAxisMove; + win->event.joystick = i; + return &win->event; + } + } + } + + /* check queued events */ if (RGFW_eventLen == 0) return NULL; - RGFW_events[index].fps = win->event.fps; RGFW_events[index].frameTime = win->event.frameTime; RGFW_events[index].frameTime2 = win->event.frameTime2; RGFW_events[index].inFocus = win->event.inFocus; @@ -7389,6 +8746,14 @@ RGFW_point RGFW_getGlobalMousePoint(void) { return point; } +RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_UNUSED(win); + + EmscriptenMouseEvent mouseEvent; + emscripten_get_mouse_status(&mouseEvent); + return RGFW_POINT( mouseEvent.targetX, mouseEvent.targetY); +} + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { RGFW_UNUSED(win); @@ -7417,13 +8782,15 @@ char* RGFW_readClipboard(size_t* size) { if (size != NULL) *size = 0; - char* str = malloc(1); + char* str = (char*)malloc(1); str[0] = '\0'; return str; } void RGFW_window_swapBuffers(RGFW_window* win) { + RGFW_UNUSED(win); + #ifdef RGFW_BUFFER if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { glEnable(GL_TEXTURE_2D); @@ -7431,9 +8798,6 @@ void RGFW_window_swapBuffers(RGFW_window* win) { GLuint texture; glGenTextures(1,&texture); - //glPixelStorei( GL_PACK_ROW_LENGTH, RGFW_bufferSize.w); - //glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, RGFW_bufferSize.h); - glBindTexture(GL_TEXTURE_2D,texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -7464,23 +8828,19 @@ void RGFW_window_swapBuffers(RGFW_window* win) { #endif emscripten_webgl_commit_frame(); - - if (win->fpsCap == 0 || win->fpsCap < 100) { - emscripten_sleep(0); - } - - RGFW_window_checkFPS(win); + emscripten_sleep(0); } void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - emscripten_webgl_make_context_current(win->src.ctx); + if (win == NULL) + emscripten_webgl_make_context_current(0); + else + emscripten_webgl_make_context_current(win->src.ctx); } #ifndef RGFW_EGL -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; -} +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } #endif void RGFW_window_close(RGFW_window* win) { @@ -7512,16 +8872,22 @@ u64 RGFW_getTime(void) { return emscripten_get_now() * 1000; } +void RGFW_releaseCursor(RGFW_window* win) { + emscripten_exit_pointerlock(); +} + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win) - if (!r.x && !r.y && !r.w && !r.h) { - emscripten_exit_pointerlock(); - return; - } emscripten_request_pointerlock("#canvas", 1); } + +void RGFW_window_setName(RGFW_window* win, char* name) { + RGFW_UNUSED(win); + emscripten_set_window_title(name); +} + /* unsupported functions */ RGFW_monitor* RGFW_getMonitors(void) { return NULL; } RGFW_monitor RGFW_getPrimaryMonitor(void) { return (RGFW_monitor){}; } @@ -7531,8 +8897,6 @@ void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win) RG void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win)} void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win) } void RGFW_window_setBorder(RGFW_window* win, b8 border) { RGFW_UNUSED(win) RGFW_UNUSED(border) } -void RGFW_window_setDND(RGFW_window* win, b8 allow) { RGFW_UNUSED(win) RGFW_UNUSED(allow) } -void RGFW_window_setName(RGFW_window* win, char* name) { RGFW_UNUSED(win) RGFW_UNUSED(name) } void RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(win) RGFW_UNUSED(icon) RGFW_UNUSED(a) RGFW_UNUSED(channels) } void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win) } void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win) } @@ -7546,7 +8910,7 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return /* end of web asm defines */ /* unix (macOS, linux, web asm) only stuff */ -#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) || defined(RGFW_WAYLAND) /* unix threading */ #ifndef RGFW_NO_THREADS #include @@ -7561,7 +8925,7 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return void RGFW_cancelThread(RGFW_thread thread) { pthread_cancel((pthread_t) thread); } void RGFW_joinThread(RGFW_thread thread) { pthread_join((pthread_t) thread, NULL); } #ifdef __linux__ - void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio(thread, priority); } + void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio((pthread_t)thread, priority); } #endif #endif @@ -7579,6 +8943,6 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return #endif /* end of unix / mac stuff*/ #endif /*RGFW_IMPLEMENTATION*/ -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) } #endif diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index d98c6391db71..afea0c2e6f6a 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -1239,15 +1239,15 @@ int InitPlatform(void) // Check selection OpenGL version if (rlGetVersion() == RL_OPENGL_21) { - RGFW_setGLVersion(2, 1); + RGFW_setGLVersion(RGFW_GL_CORE, 2, 1); } else if (rlGetVersion() == RL_OPENGL_33) { - RGFW_setGLVersion(3, 3); + RGFW_setGLVersion(RGFW_GL_CORE, 3, 3); } else if (rlGetVersion() == RL_OPENGL_43) { - RGFW_setGLVersion(4, 1); + RGFW_setGLVersion(RGFW_GL_CORE, 4, 1); } if (CORE.Window.flags & FLAG_MSAA_4X_HINT) From 7fab03c0b4f079a6450f03d82ca17f2e18be3477 Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:41:20 +0300 Subject: [PATCH 036/107] Fix warnings (#4264) Fix following gcc warnings when SVG enabled: rtextures.c: In function 'LoadImageSvg': rtextures.c:374:52: warning: pointer targets in passing argument 1 of 'nsvgParse' differ in signedness [-Wpointer-sign] 374 | struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); | ^~~~~~~~ | | | unsigned char * In file included from rtextures.c:230: external/nanosvg.h:2952:28: note: expected 'char *' but argument is of type 'unsigned char *' 2952 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ~~~~~~^~~~~ rtextures.c:407:43: warning: comparison of distinct pointer types lacks a cast [-Wcompare-distinct-pointer-types] 407 | if (isSvgStringValid && (fileData != fileNameOrString)) UnloadFileData(fileData); | ^~ rtextures.c: In function 'LoadImageFromMemory': rtextures.c:614:52: warning: passing argument 1 of 'nsvgParse' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] 614 | struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); | ^~~~~~~~ external/nanosvg.h:2952:28: note: expected 'char *' but argument is of type 'const unsigned char *' 2952 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ~~~~~~^~~~~ --- src/rtextures.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rtextures.c b/src/rtextures.c index 5fa01c9dff2d..f2faa18503a0 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -371,7 +371,7 @@ Image LoadImageSvg(const char *fileNameOrString, int width, int height) if (isSvgStringValid) { - struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); + struct NSVGimage *svgImage = nsvgParse((char *)fileData, "px", 96.0f); unsigned char *img = RL_MALLOC(width*height*4); @@ -404,7 +404,7 @@ Image LoadImageSvg(const char *fileNameOrString, int width, int height) nsvgDeleteRasterizer(rast); } - if (isSvgStringValid && (fileData != fileNameOrString)) UnloadFileData(fileData); + if (isSvgStringValid && (fileData != (unsigned char *)fileNameOrString)) UnloadFileData(fileData); } #else TRACELOG(LOG_WARNING, "SVG image support not enabled, image can not be loaded"); @@ -611,7 +611,7 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i (fileData[2] == 'v') && (fileData[3] == 'g')) { - struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); + struct NSVGimage *svgImage = nsvgParse((char *)fileData, "px", 96.0f); unsigned char *img = RL_MALLOC(svgImage->width*svgImage->height*4); // Rasterize From 039df36f4b73cfd685c143f47e71abf93270e709 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 20 Aug 2024 19:13:18 +0200 Subject: [PATCH 037/107] REVIEWED: Automation events mouse wheel #4263 --- examples/core/core_automation_events.c | 70 +++++++++++++------------- src/rcore.c | 7 +-- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/examples/core/core_automation_events.c b/examples/core/core_automation_events.c index 319b013bad05..64ca51fda784 100644 --- a/examples/core/core_automation_events.c +++ b/examples/core/core_automation_events.c @@ -88,7 +88,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- float deltaTime = 0.015f;//GetFrameTime(); - + // Dropped files logic //---------------------------------------------------------------------------------- if (IsFileDropped()) @@ -157,11 +157,6 @@ int main(void) } else player.canJump = true; - camera.zoom += ((float)GetMouseWheelMove()*0.05f); - - if (camera.zoom > 3.0f) camera.zoom = 3.0f; - else if (camera.zoom < 0.25f) camera.zoom = 0.25f; - if (IsKeyPressed(KEY_R)) { // Reset game state @@ -176,12 +171,44 @@ int main(void) } //---------------------------------------------------------------------------------- + // Events playing + // NOTE: Logic must be before Camera update because it depends on mouse-wheel value, + // that can be set by the played event... but some other inputs could be affected + //---------------------------------------------------------------------------------- + if (eventPlaying) + { + // NOTE: Multiple events could be executed in a single frame + while (playFrameCounter == aelist.events[currentPlayFrame].frame) + { + PlayAutomationEvent(aelist.events[currentPlayFrame]); + currentPlayFrame++; + + if (currentPlayFrame == aelist.count) + { + eventPlaying = false; + currentPlayFrame = 0; + playFrameCounter = 0; + + TraceLog(LOG_INFO, "FINISH PLAYING!"); + break; + } + } + + playFrameCounter++; + } + //---------------------------------------------------------------------------------- + // Update camera //---------------------------------------------------------------------------------- camera.target = player.position; camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f }; float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000; + // WARNING: On event replay, mouse-wheel internal value is set + camera.zoom += ((float)GetMouseWheelMove()*0.05f); + if (camera.zoom > 3.0f) camera.zoom = 3.0f; + else if (camera.zoom < 0.25f) camera.zoom = 0.25f; + for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++) { EnvElement *element = &envElements[i]; @@ -200,8 +227,8 @@ int main(void) if (min.y > 0) camera.offset.y = screenHeight/2 - min.y; //---------------------------------------------------------------------------------- - // Toggle events recording - if (IsKeyPressed(KEY_S)) + // Events management + if (IsKeyPressed(KEY_S)) // Toggle events recording { if (!eventPlaying) { @@ -222,7 +249,7 @@ int main(void) } } } - else if (IsKeyPressed(KEY_A)) + else if (IsKeyPressed(KEY_A)) // Toggle events playing (WARNING: Starts next frame) { if (!eventRecording && (aelist.count > 0)) { @@ -241,32 +268,7 @@ int main(void) camera.zoom = 1.0f; } } - - if (eventPlaying) - { - // NOTE: Multiple events could be executed in a single frame - while (playFrameCounter == aelist.events[currentPlayFrame].frame) - { - TraceLog(LOG_INFO, "PLAYING: PlayFrameCount: %i | currentPlayFrame: %i | Event Frame: %i, param: %i", - playFrameCounter, currentPlayFrame, aelist.events[currentPlayFrame].frame, aelist.events[currentPlayFrame].params[0]); - - PlayAutomationEvent(aelist.events[currentPlayFrame]); - currentPlayFrame++; - if (currentPlayFrame == aelist.count) - { - eventPlaying = false; - currentPlayFrame = 0; - playFrameCounter = 0; - - TraceLog(LOG_INFO, "FINISH PLAYING!"); - break; - } - } - - playFrameCounter++; - } - if (eventRecording || eventPlaying) frameCounter++; else frameCounter = 0; //---------------------------------------------------------------------------------- diff --git a/src/rcore.c b/src/rcore.c index 5485ce8ce032..e8041ea4198c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -669,7 +669,6 @@ void InitWindow(int width, int height, const char *title) #endif #endif - CORE.Time.frameCounter = 0; CORE.Window.shouldClose = false; @@ -2707,8 +2706,8 @@ void PlayAutomationEvent(AutomationEvent event) } break; case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta { - CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; break; - CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; break; + CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; + CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; } break; case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[event.params[0]] = false; break; // param[0]: id case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[event.params[0]] = true; break; // param[0]: id @@ -2745,6 +2744,8 @@ void PlayAutomationEvent(AutomationEvent event) case ACTION_SETTARGETFPS: SetTargetFPS(event.params[0]); break; default: break; } + + TRACELOG(LOG_INFO, "AUTOMATION PLAY: Frame: %i | Event type: %i | Event parameters: %i, %i, %i", event.frame, event.type, event.params[0], event.params[1], event.params[2]); } #endif } From fa3f73d88174fb66d9b275e2970e4d201e6a9ecb Mon Sep 17 00:00:00 2001 From: Paperdomo101 <38697390+Paperdomo101@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:07:52 +0800 Subject: [PATCH 038/107] [rshapes] Review DrawGradient color parameter names (#4270) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2); void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); Have been changed to: void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); --- src/raylib.h | 8 ++++---- src/rshapes.c | 30 +++++++++++++----------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 1cf34f004470..0759c259f18e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1238,7 +1238,7 @@ RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color c RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline -RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) @@ -1250,9 +1250,9 @@ RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color) RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters -RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle -RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges diff --git a/src/rshapes.c b/src/rshapes.c index 676b2521a06b..66dfbf3b606a 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -430,17 +430,16 @@ void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float } // Draw a gradient-filled circle -// NOTE: Gradient goes from center (color1) to border (color2) -void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) +void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer) { rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) { - rlColor4ub(color1.r, color1.g, color1.b, color1.a); + rlColor4ub(inner.r, inner.g, inner.b, inner.a); rlVertex2f((float)centerX, (float)centerY); - rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); rlVertex2f((float)centerX + cosf(DEG2RAD*(i + 10))*radius, (float)centerY + sinf(DEG2RAD*(i + 10))*radius); - rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); rlVertex2f((float)centerX + cosf(DEG2RAD*i)*radius, (float)centerY + sinf(DEG2RAD*i)*radius); } rlEnd(); @@ -761,22 +760,19 @@ void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color } // Draw a vertical-gradient-filled rectangle -// NOTE: Gradient goes from bottom (color1) to top (color2) -void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom) { - DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color2, color2, color1); + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, top, bottom, bottom, top); } // Draw a horizontal-gradient-filled rectangle -// NOTE: Gradient goes from bottom (color1) to top (color2) -void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2) +void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right) { - DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color1, color2, color2); + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, left, left, right, right); } // Draw a gradient-filled rectangle -// NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise -void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) +void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight) { rlSetTexture(GetShapesTexture().id); Rectangle shapeRect = GetShapesTextureRectangle(); @@ -785,19 +781,19 @@ void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, rlNormal3f(0.0f, 0.0f, 1.0f); // NOTE: Default raylib font character 95 is a white square - rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlColor4ub(topLeft.r, topLeft.g, topLeft.b, topLeft.a); rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x, rec.y); - rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlColor4ub(bottomLeft.r, bottomLeft.g, bottomLeft.b, bottomLeft.a); rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x, rec.y + rec.height); - rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlColor4ub(topRight.r, topRight.g, topRight.b, topRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y + rec.height); - rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlColor4ub(bottomRight.r, bottomRight.g, bottomRight.b, bottomRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); From bae0af241ed8ecf0b8107d3d3c43fef297e038d3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Aug 2024 12:08:09 +0000 Subject: [PATCH 039/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 20 ++++++++++---------- parser/output/raylib_api.lua | 20 ++++++++++---------- parser/output/raylib_api.txt | 20 ++++++++++---------- parser/output/raylib_api.xml | 20 ++++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index f149d628f0f1..c4ce91e87f91 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -5485,11 +5485,11 @@ }, { "type": "Color", - "name": "color1" + "name": "inner" }, { "type": "Color", - "name": "color2" + "name": "outer" } ] }, @@ -5785,11 +5785,11 @@ }, { "type": "Color", - "name": "color1" + "name": "top" }, { "type": "Color", - "name": "color2" + "name": "bottom" } ] }, @@ -5816,11 +5816,11 @@ }, { "type": "Color", - "name": "color1" + "name": "left" }, { "type": "Color", - "name": "color2" + "name": "right" } ] }, @@ -5835,19 +5835,19 @@ }, { "type": "Color", - "name": "col1" + "name": "topLeft" }, { "type": "Color", - "name": "col2" + "name": "bottomLeft" }, { "type": "Color", - "name": "col3" + "name": "topRight" }, { "type": "Color", - "name": "col4" + "name": "bottomRight" } ] }, diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 7c00fff33f29..3a9e2f04f076 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4697,8 +4697,8 @@ return { {type = "int", name = "centerX"}, {type = "int", name = "centerY"}, {type = "float", name = "radius"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "inner"}, + {type = "Color", name = "outer"} } }, { @@ -4835,8 +4835,8 @@ return { {type = "int", name = "posY"}, {type = "int", name = "width"}, {type = "int", name = "height"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "top"}, + {type = "Color", name = "bottom"} } }, { @@ -4848,8 +4848,8 @@ return { {type = "int", name = "posY"}, {type = "int", name = "width"}, {type = "int", name = "height"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "left"}, + {type = "Color", name = "right"} } }, { @@ -4858,10 +4858,10 @@ return { returnType = "void", params = { {type = "Rectangle", name = "rec"}, - {type = "Color", name = "col1"}, - {type = "Color", name = "col2"}, - {type = "Color", name = "col3"}, - {type = "Color", name = "col4"} + {type = "Color", name = "topLeft"}, + {type = "Color", name = "bottomLeft"}, + {type = "Color", name = "topRight"}, + {type = "Color", name = "bottomRight"} } }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 73170c860a46..afa9b525dba0 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -2176,8 +2176,8 @@ Function 217: DrawCircleGradient() (5 input parameters) Param[1]: centerX (type: int) Param[2]: centerY (type: int) Param[3]: radius (type: float) - Param[4]: color1 (type: Color) - Param[5]: color2 (type: Color) + Param[4]: inner (type: Color) + Param[5]: outer (type: Color) Function 218: DrawCircleV() (3 input parameters) Name: DrawCircleV Return type: void @@ -2278,8 +2278,8 @@ Function 229: DrawRectangleGradientV() (6 input parameters) Param[2]: posY (type: int) Param[3]: width (type: int) Param[4]: height (type: int) - Param[5]: color1 (type: Color) - Param[6]: color2 (type: Color) + Param[5]: top (type: Color) + Param[6]: bottom (type: Color) Function 230: DrawRectangleGradientH() (6 input parameters) Name: DrawRectangleGradientH Return type: void @@ -2288,17 +2288,17 @@ Function 230: DrawRectangleGradientH() (6 input parameters) Param[2]: posY (type: int) Param[3]: width (type: int) Param[4]: height (type: int) - Param[5]: color1 (type: Color) - Param[6]: color2 (type: Color) + Param[5]: left (type: Color) + Param[6]: right (type: Color) Function 231: DrawRectangleGradientEx() (5 input parameters) Name: DrawRectangleGradientEx Return type: void Description: Draw a gradient-filled rectangle with custom vertex colors Param[1]: rec (type: Rectangle) - Param[2]: col1 (type: Color) - Param[3]: col2 (type: Color) - Param[4]: col3 (type: Color) - Param[5]: col4 (type: Color) + Param[2]: topLeft (type: Color) + Param[3]: bottomLeft (type: Color) + Param[4]: topRight (type: Color) + Param[5]: bottomRight (type: Color) Function 232: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 474bc0473713..8a6560f489bc 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1353,8 +1353,8 @@ - - + + @@ -1431,23 +1431,23 @@ - - + + - - + + - - - - + + + + From c8bee7c439d3a49d63ab0e67441fffb56ea4bf2a Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 21 Aug 2024 08:11:59 -0700 Subject: [PATCH 040/107] [rmodels] Add a warning when loading an OBJ with multiple materials. (#4271) * Update raylib_api.* by CI * Add a temp warning about material assignments during OBJ loading if the file has more than one material. To be replaced when the OBJ translation code is fixed. --------- Co-authored-by: github-actions[bot] --- src/rmodels.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rmodels.c b/src/rmodels.c index 36a7e254d1f3..8afa3df574c5 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4092,6 +4092,12 @@ static Model LoadOBJ(const char *fileName) model.materialCount = 1; TRACELOG(LOG_INFO, "MODEL: No materials provided, setting one default material for all meshes"); } + else if (model.materialCount > 1 && model.meshCount > 1) + { + // TEMP warning about multiple materials, to be removed when proper splitting code is implemented + // any obj with multiple materials will need to have it's materials assigned by the user in code to work at this time + TRACELOG(LOG_INFO, "MODEL: OBJ has multiple materials, manual material assignment will be required."); + } // Init model meshes and materials model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); From cc88e0b780a7cc265e60a2e0842e587dd9b1c293 Mon Sep 17 00:00:00 2001 From: listeria <56203103+ListeriaM@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:45:14 -0300 Subject: [PATCH 041/107] rtext: always multiply by sign in TextToFloat() (#4273) Co-authored-by: Listeria monocytogenes --- src/rtext.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 755b15efdefd..8c1dc3c5e53d 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1467,8 +1467,7 @@ float TextToFloat(const char *text) int i = 0; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); - if (text[i++] != '.') value *= sign; - else + if (text[i++] == '.') { float divisor = 10.0f; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) @@ -1478,7 +1477,7 @@ float TextToFloat(const char *text) } } - return value; + return value*sign; } #if defined(SUPPORT_TEXT_MANIPULATION) From 74350fa7cf89f7e04e532a4cb89ff036f98fe59c Mon Sep 17 00:00:00 2001 From: Peter0x44 Date: Fri, 23 Aug 2024 21:21:22 +0100 Subject: [PATCH 042/107] [build] CMake: Delete BuildOptions.cmake (#4277) This file seems to not do anything useful. From what I can tell the OSX_FATLIB option sets CMAKE_OSX_ARCHITECTURES to "x86_64;i386". This doesn't account for the arm that apple now has, as well as 32 bit support being completely removed, and I think it's entirely reasonable to expect users to pass the necessary architectures they want themselves. It's possible this could break some users who rely on this, but I sincerely doubt anyone does. The solution is trivial either way (put -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" on the command line yourself) The second part of BuildOptions.cmake claims to set PLATFORM to "Web" if the emscripten toolchain file is used (if (EMSCRIPTEN)), but it does not work correctly anyway. Currently, glfw searches for wayland and x11 libraries and fails likeso: CMake Error at /usr/share/cmake/Modules/FindPkgConfig.cmake:645 (message): The following required packages were not found: - wayland-client>=0.2.7 - wayland-cursor>=0.2.7 - wayland-egl>=0.2.7 - xkbcommon>=0.5.0 Call Stack (most recent call first): /usr/share/cmake/Modules/FindPkgConfig.cmake:873 (_pkg_check_modules_internal) src/external/glfw/src/CMakeLists.txt:163 (pkg_check_modules) Considering this code doesn't work as described, it's okay to delete it. I think a better check should be implemented, but that is for a different PR. --- CMakeLists.txt | 3 --- CMakeOptions.txt | 1 - cmake/BuildOptions.cmake | 18 ------------------ 3 files changed, 22 deletions(-) delete mode 100644 cmake/BuildOptions.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a5e3408c8f09..678d8e372453 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,9 +36,6 @@ include(CompilerFlags) # Registers build options that are exposed to cmake include(CMakeOptions.txt) -# Enforces a few environment and compiler configurations -include(BuildOptions) - if (UNIX AND NOT APPLE) if (NOT GLFW_BUILD_WAYLAND AND NOT GLFW_BUILD_X11) MESSAGE(FATAL_ERROR "Cannot disable both Wayland and X11") diff --git a/CMakeOptions.txt b/CMakeOptions.txt index b063f02a1516..ce9141e22a3e 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -16,7 +16,6 @@ option(ENABLE_MSAN "Enable MemorySanitizer (MSan) for debugging (not recommended # Shared library is always PIC. Static library should be PIC too if linked into a shared library option(WITH_PIC "Compile static library as position-independent code" OFF) option(BUILD_SHARED_LIBS "Build raylib as a shared library" OFF) -option(MACOS_FATLIB "Build fat library for both i386 and x86_64 on macOS" OFF) cmake_dependent_option(USE_AUDIO "Build raylib with audio module" ON CUSTOMIZE_BUILD ON) enum_option(USE_EXTERNAL_GLFW "OFF;IF_POSSIBLE;ON" "Link raylib against system GLFW instead of embedded one") diff --git a/cmake/BuildOptions.cmake b/cmake/BuildOptions.cmake deleted file mode 100644 index 0fce642922f0..000000000000 --- a/cmake/BuildOptions.cmake +++ /dev/null @@ -1,18 +0,0 @@ -if(${PLATFORM} MATCHES "Desktop" AND APPLE) - if(MACOS_FATLIB) - if (CMAKE_OSX_ARCHITECTURES) - message(FATAL_ERROR "User supplied -DCMAKE_OSX_ARCHITECTURES overrides -DMACOS_FATLIB=ON") - else() - set(CMAKE_OSX_ARCHITECTURES "x86_64;i386") - endif() - endif() -endif() - -# This helps support the case where emsdk toolchain file is used -# either by setting it with -DCMAKE_TOOLCHAIN_FILE=/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -# or by using "emcmake cmake -B build -S ." as described in https://emscripten.org/docs/compiling/Building-Projects.html -if(EMSCRIPTEN) - SET(PLATFORM Web CACHE STRING "Forcing PLATFORM_WEB because EMSCRIPTEN was detected") -endif() - -# vim: ft=cmake From 77172e34db5d27bb5413f9232215321b8cceac67 Mon Sep 17 00:00:00 2001 From: Bugsia <74007012+Bugsia@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:22:36 +0200 Subject: [PATCH 043/107] Fixing GenImagePerlinNoise() being stretched, if Image is not rectangular (#4276) --- src/rtextures.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rtextures.c b/src/rtextures.c index f2faa18503a0..867add38e3c9 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1112,6 +1112,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float { Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float aspectRatio = (float)width / (float)height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) @@ -1119,6 +1120,10 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float float nx = (float)(x + offsetX)*(scale/(float)width); float ny = (float)(y + offsetY)*(scale/(float)height); + // Apply aspect ratio compensation to wider side + if (width > height) nx *= aspectRatio; + else ny /= aspectRatio; + // Basic perlin noise implementation (not used) //float p = (stb_perlin_noise3(nx, ny, 0.0f, 0, 0, 0); From ecf863969c2ca0cf903acb5bef0d403f611943b4 Mon Sep 17 00:00:00 2001 From: Peter0x44 Date: Fri, 23 Aug 2024 21:29:40 +0100 Subject: [PATCH 044/107] [build] CMake: Fix warnings in projects/CMake/CMakeLists.txt (#4278) Currently, when building, the cmake example in projects/CMake gives this warning, with CMake 3.30.2 CMake Warning (dev) at /usr/share/cmake/Modules/FetchContent.cmake:1953 (message): Calling FetchContent_Populate(raylib) is deprecated, call FetchContent_MakeAvailable(raylib) instead. Policy CMP0169 can be set to OLD to allow FetchContent_Populate(raylib) to be called directly for now, but the ability to call it with declared details will be removed completely in a future version. Call Stack (most recent call first): CMakeLists.txt:20 (FetchContent_Populate) This warning is for project developers. Use -Wno-dev to suppress it. Changing FetchContent_Populate to FetchContent_MakeAvailable didn't cause any issues I could observe when building. I'm not sure why it wasn't like that to begin with. --- projects/CMake/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/CMake/CMakeLists.txt b/projects/CMake/CMakeLists.txt index 3b22964c594a..a95d68a4f368 100644 --- a/projects/CMake/CMakeLists.txt +++ b/projects/CMake/CMakeLists.txt @@ -17,9 +17,8 @@ if (NOT raylib_FOUND) # If there's none, fetch and build raylib FetchContent_GetProperties(raylib) if (NOT raylib_POPULATED) # Have we downloaded raylib yet? set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(raylib) + FetchContent_MakeAvailable(raylib) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples - add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) endif() endif() From 3079c69725b3e4f07f6984dc2f67262bba48b153 Mon Sep 17 00:00:00 2001 From: Menno van der Graaf Date: Fri, 23 Aug 2024 22:32:20 +0200 Subject: [PATCH 045/107] Replace deprecated Android function ALooper_pollAll with ALooper_pollOnce (#4275) --- src/platforms/rcore_android.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 68ae979ee64b..7a2162025e90 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -281,14 +281,15 @@ void android_main(struct android_app *app) // Request to end the native activity ANativeActivity_finish(app->activity); - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; // Waiting for application events before complete finishing while (!app->destroyRequested) { - while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void **)&platform.source)) >= 0) + // Poll all events until we reach return value TIMEOUT, meaning no events left to process + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void **)&platform.source)) > ALOOPER_POLL_TIMEOUT) { if (platform.source != NULL) platform.source->process(app, platform.source); } @@ -682,13 +683,13 @@ void PollInputEvents(void) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; } - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; - // Poll Events (registered events) + // Poll Events (registered events) until we reach TIMEOUT which indicates there are no events left to poll // NOTE: Activity is paused if not enabled (platform.appEnabled) - while ((pollResult = ALooper_pollAll(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) >= 0) + while ((pollResult = ALooper_pollOnce(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); @@ -767,15 +768,15 @@ int InitPlatform(void) TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully"); - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; // Wait for window to be initialized (display and context) while (!CORE.Window.ready) { - // Process events loop - while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&platform.source)) >= 0) + // Process events until we reach TIMEOUT, which indicates no more events queued. + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); From b0c3013b51c5dbb03e584aad04fe00d89e121946 Mon Sep 17 00:00:00 2001 From: konstruktor227 Date: Fri, 23 Aug 2024 22:51:29 +0200 Subject: [PATCH 046/107] [raudio] Support 24-bit FLACs in `LoadMusicStreamFromMemory` (#4279) Force conversion to 16-bit, same as how it is done in `LoadMusicStream`. This fixes the problem where 24-bit FLACs play silence or broken sound. --- src/raudio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/raudio.c b/src/raudio.c index 21d1adae3821..79d53b56583d 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1626,7 +1626,9 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, { music.ctxType = MUSIC_AUDIO_FLAC; music.ctxData = ctxFlac; - music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + int sampleSize = ctxFlac->bitsPerSample; + if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels); music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; From 7bde76ca2c649213546f412c28b7b05260338ad4 Mon Sep 17 00:00:00 2001 From: Reese Gallagher Date: Sat, 24 Aug 2024 12:42:38 -0400 Subject: [PATCH 047/107] [rmodels] More performant point cloud rendering with `DrawModelPoints()` (#4203) * Added the ability to draw a model as a point cloud * Added example to demonstrate drawing a model as a point cloud * polished the demo a bit * picture for example * adhere to conventions for example * update png to match aspect ratio * minor changes * address code convention comments * added point rendering to makefiles * added point rendering to readme and renumbered examples * comment formatting --------- Co-authored-by: Reese Gallagher Co-authored-by: Ray --- examples/Makefile | 1 + examples/Makefile.Web | 1 + examples/README.md | 50 +++--- examples/models/models_point_rendering.c | 171 +++++++++++++++++++++ examples/models/models_point_rendering.png | Bin 0 -> 130183 bytes src/raylib.h | 2 + src/rmodels.c | 24 +++ 7 files changed, 224 insertions(+), 25 deletions(-) create mode 100644 examples/models/models_point_rendering.c create mode 100644 examples/models/models_point_rendering.png diff --git a/examples/Makefile b/examples/Makefile index 93b05a0c7caa..904917f7c806 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -582,6 +582,7 @@ MODELS = \ models/models_mesh_generation \ models/models_mesh_picking \ models/models_orthographic_projection \ + models/models_point_rendering \ models/models_rlgl_solar_system \ models/models_skybox \ models/models_waving_cubes \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 46a8a98ea077..7057da1c03d9 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -447,6 +447,7 @@ MODELS = \ models/models_mesh_generation \ models/models_mesh_picking \ models/models_orthographic_projection \ + models/models_point_rendering \ models/models_rlgl_solar_system \ models/models_skybox \ models/models_waving_cubes \ diff --git a/examples/README.md b/examples/README.md index 317f43c899a3..d573ff23ef54 100644 --- a/examples/README.md +++ b/examples/README.md @@ -147,11 +147,12 @@ Examples using raylib models functionality, including models loading/generation | 91 | [models_loading_vox](models/models_loading_vox.c) | models_loading_vox | ⭐️☆☆☆ | **4.0** | **4.0** | [Johann Nadalutti](https://github.com/procfxgen) | | 92 | [models_loading_m3d](models/models_loading_m3d.c) | models_loading_m3d | ⭐️☆☆☆ | **4.2** | **4.2** | [bzt](https://bztsrc.gitlab.io/model3d) | | 93 | [models_orthographic_projection](models/models_orthographic_projection.c) | models_orthographic_projection | ⭐️☆☆☆ | 2.0 | 3.7 | [Max Danielsson](https://github.com/autious) | -| 94 | [models_rlgl_solar_system](models/models_rlgl_solar_system.c) | models_rlgl_solar_system | ⭐️⭐️⭐️⭐️ | 2.5 | **4.0** | [Ray](https://github.com/raysan5) | -| 95 | [models_yaw_pitch_roll](models/models_yaw_pitch_roll.c) | models_yaw_pitch_roll | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Berni](https://github.com/Berni8k) | -| 96 | [models_waving_cubes](models/models_waving_cubes.c) | models_waving_cubes | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [codecat](https://github.com/codecat) | -| 97 | [models_heightmap](models/models_heightmap.c) | models_heightmap | ⭐️☆☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | -| 98 | [models_skybox](models/models_skybox.c) | models_skybox | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Ray](https://github.com/raysan5) | +| 94 | [models_point_rendering](models/models_point_rendering.c) | models_point_rendering | ⭐️⭐️☆☆ | 5.0 | 5.0 | [Reese Gallagher](https://github.com/satchelfrost) | +| 95 | [models_rlgl_solar_system](models/models_rlgl_solar_system.c) | models_rlgl_solar_system | ⭐️⭐️⭐️⭐️ | 2.5 | **4.0** | [Ray](https://github.com/raysan5) | +| 96 | [models_yaw_pitch_roll](models/models_yaw_pitch_roll.c) | models_yaw_pitch_roll | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Berni](https://github.com/Berni8k) | +| 97 | [models_waving_cubes](models/models_waving_cubes.c) | models_waving_cubes | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [codecat](https://github.com/codecat) | +| 98 | [models_heightmap](models/models_heightmap.c) | models_heightmap | ⭐️☆☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | +| 99 | [models_skybox](models/models_skybox.c) | models_skybox | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Ray](https://github.com/raysan5) | ### category: shaders @@ -159,26 +160,25 @@ Examples using raylib shaders functionality, including shaders loading, paramete | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------| -| 99 | [shaders_basic_lighting](shaders/shaders_basic_lighting.c) | shaders_basic_lighting | ⭐️⭐️⭐️⭐️ | 3.0 | **4.2** | [Chris Camacho](https://github.com/codifies) | -| 100 | [shaders_model_shader](shaders/shaders_model_shader.c) | shaders_model_shader | ⭐️⭐️☆☆ | 1.3 | 3.7 | [Ray](https://github.com/raysan5) | -| 101 | [shaders_shapes_textures](shaders/shaders_shapes_textures.c) | shaders_shapes_textures | ⭐️⭐️☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | -| 102 | [shaders_custom_uniform](shaders/shaders_custom_uniform.c) | shaders_custom_uniform | ⭐️⭐️☆☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | -| 103 | [shaders_postprocessing](shaders/shaders_postprocessing.c) | shaders_postprocessing | ⭐️⭐️⭐️☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | -| 104 | [shaders_palette_switch](shaders/shaders_palette_switch.c) | shaders_palette_switch | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Marco Lizza](https://github.com/MarcoLizza) | -| 105 | [shaders_raymarching](shaders/shaders_raymarching.c) | shaders_raymarching | ⭐️⭐️⭐️⭐️ | 2.0 | **4.2** | [Ray](https://github.com/raysan5) | -| 106 | [shaders_texture_drawing](shaders/shaders_texture_drawing.c) | shaders_texture_drawing | ⭐️⭐️☆☆ | 2.0 | 3.7 | [Michał Ciesielski](https://github.com/) | -| 107 | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐️⭐️⭐️☆ | **4.0** | **4.0** | [Samuel Skiff](https://github.com/GoldenThumbs) | -| 108 | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | -| 109 | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [eggmund](https://github.com/eggmund) | -| 110 | [shaders_eratosthenes](shaders/shaders_eratosthenes.c) | shaders_eratosthenes | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [ProfJski](https://github.com/ProfJski) | -| 111 | [shaders_fog](shaders/shaders_fog.c) | shaders_fog | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | -| 112 | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | -| 113 | [shaders_hot_reloading](shaders/shaders_hot_reloading.c) | shaders_hot_reloading | ⭐️⭐️⭐️☆ | 3.0 | 3.5 | [Ray](https://github.com/raysan5) | -| 114 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | shaders_mesh_instancing | ⭐️⭐️⭐️⭐️ | 3.7 | **4.2** | [seanpringle](https://github.com/seanpringle) | -| 115 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 116 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | -| 117 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | -| 118 | [shaders_vertex_displacement](shaders/shaders_vertex_displacement.c) | shaders_deferred_render | ⭐️☆☆☆ | 1.5 | 1.5 | [Alex ZH](https://github.com/ZzzhHe) | +| 100 | [shaders_basic_lighting](shaders/shaders_basic_lighting.c) | shaders_basic_lighting | ⭐️⭐️⭐️⭐️ | 3.0 | **4.2** | [Chris Camacho](https://github.com/codifies) | +| 101 | [shaders_model_shader](shaders/shaders_model_shader.c) | shaders_model_shader | ⭐️⭐️☆☆ | 1.3 | 3.7 | [Ray](https://github.com/raysan5) | +| 102 | [shaders_shapes_textures](shaders/shaders_shapes_textures.c) | shaders_shapes_textures | ⭐️⭐️☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | +| 103 | [shaders_custom_uniform](shaders/shaders_custom_uniform.c) | shaders_custom_uniform | ⭐️⭐️☆☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | +| 104 | [shaders_postprocessing](shaders/shaders_postprocessing.c) | shaders_postprocessing | ⭐️⭐️⭐️☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | +| 105 | [shaders_palette_switch](shaders/shaders_palette_switch.c) | shaders_palette_switch | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Marco Lizza](https://github.com/MarcoLizza) | +| 106 | [shaders_raymarching](shaders/shaders_raymarching.c) | shaders_raymarching | ⭐️⭐️⭐️⭐️ | 2.0 | **4.2** | [Ray](https://github.com/raysan5) | +| 107 | [shaders_texture_drawing](shaders/shaders_texture_drawing.c) | shaders_texture_drawing | ⭐️⭐️☆☆ | 2.0 | 3.7 | [Michał Ciesielski](https://github.com/) | +| 108 | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐️⭐️⭐️☆ | **4.0** | **4.0** | [Samuel Skiff](https://github.com/GoldenThumbs) | +| 109 | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | +| 110 | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [eggmund](https://github.com/eggmund) | +| 111 | [shaders_eratosthenes](shaders/shaders_eratosthenes.c) | shaders_eratosthenes | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [ProfJski](https://github.com/ProfJski) | +| 112 | [shaders_fog](shaders/shaders_fog.c) | shaders_fog | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | +| 113 | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | +| 114 | [shaders_hot_reloading](shaders/shaders_hot_reloading.c) | shaders_hot_reloading | ⭐️⭐️⭐️☆ | 3.0 | 3.5 | [Ray](https://github.com/raysan5) | +| 115 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | shaders_mesh_instancing | ⭐️⭐️⭐️⭐️ | 3.7 | **4.2** | [seanpringle](https://github.com/seanpringle) | +| 116 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 117 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | +| 118 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | ### category: audio diff --git a/examples/models/models_point_rendering.c b/examples/models/models_point_rendering.c new file mode 100644 index 000000000000..128a70f57ee1 --- /dev/null +++ b/examples/models/models_point_rendering.c @@ -0,0 +1,171 @@ +/******************************************************************************************* +* +* raylib example - point rendering +* +* Example originally created with raylib 5.0, last time updated with raylib 5.0 +* +* Example contributed by Reese Gallagher (@satchelfrost) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2024 Reese Gallagher (@satchelfrost) +* +********************************************************************************************/ + +#include "raylib.h" +#include // Required for: rand() +#include // Required for: cos(), sin() + +#define MAX_POINTS 10000000 // 10 million +#define MIN_POINTS 1000 // 1 thousand + +static float RandFloat(); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + InitWindow(screenWidth, screenHeight, "raylib [models] example - point rendering"); + SetTargetFPS(60); + + Camera camera = { + .position = {3.0f, 3.0f, 3.0f}, + .target = {0.0f, 0.0f, 0.0f}, + .up = {0.0f, 1.0f, 0.0f}, + .fovy = 45.0f, + .projection = CAMERA_PERSPECTIVE, + }; + + Vector3 position = {0.0f, 0.0f, 0.0f}; + bool useDrawModelPoints = true; + bool numPointsChanged = false; + int numPoints = 1000; + Mesh mesh = GenPoints(numPoints); + Model model = LoadModelFromMesh(mesh); + //-------------------------------------------------------------------------------------- + + // Main game loop + while(!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_ORBITAL); + + if (IsKeyPressed(KEY_SPACE)) useDrawModelPoints = !useDrawModelPoints; + if (IsKeyPressed(KEY_UP)) + { + numPoints = (numPoints * 10 > MAX_POINTS) ? MAX_POINTS : numPoints * 10; + numPointsChanged = true; + TraceLog(LOG_INFO, "num points %d", numPoints); + } + if (IsKeyPressed(KEY_DOWN)) + { + numPoints = (numPoints / 10 < MIN_POINTS) ? MIN_POINTS : numPoints / 10; + numPointsChanged = true; + TraceLog(LOG_INFO, "num points %d", numPoints); + } + + // upload a different point cloud size + if (numPointsChanged) + { + UnloadModel(model); + mesh = GenPoints(numPoints); + model = LoadModelFromMesh(mesh); + numPointsChanged = false; + } + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(BLACK); + BeginMode3D(camera); + + // The new method only uploads the points once to the GPU + if (useDrawModelPoints) + { + DrawModelPoints(model, position, 1.0f, WHITE); + } + // The old method must continually draw the "points" (lines) + else + { + for (int i = 0; i < numPoints; i++) + { + Vector3 pos = { + .x = mesh.vertices[i * 3 + 0], + .y = mesh.vertices[i * 3 + 1], + .z = mesh.vertices[i * 3 + 2], + }; + Color color = { + .r = mesh.colors[i * 4 + 0], + .g = mesh.colors[i * 4 + 1], + .b = mesh.colors[i * 4 + 2], + .a = mesh.colors[i * 4 + 3], + }; + DrawPoint3D(pos, color); + } + } + + // Draw a unit sphere for reference + DrawSphereWires(position, 1.0f, 10, 10, YELLOW); + EndMode3D(); + + // Text formatting + Color color = WHITE; + int fps = GetFPS(); + if ((fps < 30) && (fps >= 15)) color = ORANGE; + else if (fps < 15) color = RED; + DrawText(TextFormat("%2i FPS", fps), 20, 20, 40, color); + DrawText(TextFormat("Point Count: %d", numPoints), 20, screenHeight - 50, 40, WHITE); + DrawText("Up - increase points", 20, 70, 20, WHITE); + DrawText("Down - decrease points", 20, 100, 20, WHITE); + DrawText("Space - drawing function", 20, 130, 20, WHITE); + if (useDrawModelPoints) DrawText("DrawModelPoints()", 20, 160, 20, GREEN); + else DrawText("DrawPoint3D()", 20, 160, 20, RED); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(model); + CloseWindow(); + //-------------------------------------------------------------------------------------- + return 0; +} + +// Generate a spherical point cloud +Mesh GenPoints(int numPoints) +{ + Mesh mesh = { + .triangleCount = 1, + .vertexCount = numPoints, + .vertices = (float *)MemAlloc(numPoints * 3 * sizeof(float)), + .colors = (unsigned char*)MemAlloc(numPoints * 4 * sizeof(unsigned char)), + }; + + // https://en.wikipedia.org/wiki/Spherical_coordinate_system + for (int i = 0; i < numPoints; i++) + { + float theta = PI * rand() / RAND_MAX; + float phi = 2.0f * PI * rand() / RAND_MAX; + float r = 10.0f * rand() / RAND_MAX; + mesh.vertices[i * 3 + 0] = r * sin(theta) * cos(phi); + mesh.vertices[i * 3 + 1] = r * sin(theta) * sin(phi); + mesh.vertices[i * 3 + 2] = r * cos(theta); + Color color = ColorFromHSV(r * 360.0f, 1.0f, 1.0f); + mesh.colors[i * 4 + 0] = color.r; + mesh.colors[i * 4 + 1] = color.g; + mesh.colors[i * 4 + 2] = color.b; + mesh.colors[i * 4 + 3] = color.a; + } + + // Upload mesh data from CPU (RAM) to GPU (VRAM) memory + UploadMesh(&mesh, false); + return mesh; +} diff --git a/examples/models/models_point_rendering.png b/examples/models/models_point_rendering.png new file mode 100644 index 0000000000000000000000000000000000000000..a1fc718e4dd508ba6768abb6f7a0c02a1fabaf9f GIT binary patch literal 130183 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 UrvOg;fdBvi07*qoM6N<$g6mpWr2qf` literal 0 HcmV?d00001 diff --git a/src/raylib.h b/src/raylib.h index 0759c259f18e..f51a444e1f07 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1545,6 +1545,8 @@ RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points +RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source diff --git a/src/rmodels.c b/src/rmodels.c index 8afa3df574c5..966a8665c622 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3631,6 +3631,30 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rlDisableWireMode(); } +// Draw a model points +void DrawModelPoints(Model model, Vector3 position, float scale, Color tint) +{ + rlEnablePointMode(); + rlDisableBackfaceCulling(); + + DrawModel(model, position, scale, tint); + + rlEnableBackfaceCulling(); + rlDisableWireMode(); +} + +// Draw a model points +void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + rlEnablePointMode(); + rlDisableBackfaceCulling(); + + DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint); + + rlEnableBackfaceCulling(); + rlDisableWireMode(); +} + // Draw a billboard void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint) { From dfd8055270691429076283f407d4176929f56e80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 24 Aug 2024 16:42:52 +0000 Subject: [PATCH 048/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 54 ++++++++ parser/output/raylib_api.lua | 24 ++++ parser/output/raylib_api.txt | 238 ++++++++++++++++++---------------- parser/output/raylib_api.xml | 16 ++- 4 files changed, 221 insertions(+), 111 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index c4ce91e87f91..dfe37e2fec08 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -10366,6 +10366,60 @@ } ] }, + { + "name": "DrawModelPoints", + "description": "Draw a model as points", + "returnType": "void", + "params": [ + { + "type": "Model", + "name": "model" + }, + { + "type": "Vector3", + "name": "position" + }, + { + "type": "float", + "name": "scale" + }, + { + "type": "Color", + "name": "tint" + } + ] + }, + { + "name": "DrawModelPointsEx", + "description": "Draw a model as points with extended parameters", + "returnType": "void", + "params": [ + { + "type": "Model", + "name": "model" + }, + { + "type": "Vector3", + "name": "position" + }, + { + "type": "Vector3", + "name": "rotationAxis" + }, + { + "type": "float", + "name": "rotationAngle" + }, + { + "type": "Vector3", + "name": "scale" + }, + { + "type": "Color", + "name": "tint" + } + ] + }, { "name": "DrawBoundingBox", "description": "Draw bounding box (wires)", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 3a9e2f04f076..933b2c704e0d 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -7213,6 +7213,30 @@ return { {type = "Color", name = "tint"} } }, + { + name = "DrawModelPoints", + description = "Draw a model as points", + returnType = "void", + params = { + {type = "Model", name = "model"}, + {type = "Vector3", name = "position"}, + {type = "float", name = "scale"}, + {type = "Color", name = "tint"} + } + }, + { + name = "DrawModelPointsEx", + description = "Draw a model as points with extended parameters", + returnType = "void", + params = { + {type = "Model", name = "model"}, + {type = "Vector3", name = "position"}, + {type = "Vector3", name = "rotationAxis"}, + {type = "float", name = "rotationAngle"}, + {type = "Vector3", name = "scale"}, + {type = "Color", name = "tint"} + } + }, { name = "DrawBoundingBox", description = "Draw bounding box (wires)", diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index afa9b525dba0..94562d66aa8f 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -984,7 +984,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 573 +Functions found: 575 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -3955,13 +3955,31 @@ Function 464: DrawModelWiresEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 465: DrawBoundingBox() (2 input parameters) +Function 465: DrawModelPoints() (4 input parameters) + Name: DrawModelPoints + Return type: void + Description: Draw a model as points + Param[1]: model (type: Model) + Param[2]: position (type: Vector3) + Param[3]: scale (type: float) + Param[4]: tint (type: Color) +Function 466: DrawModelPointsEx() (6 input parameters) + Name: DrawModelPointsEx + Return type: void + Description: Draw a model as points with extended parameters + Param[1]: model (type: Model) + Param[2]: position (type: Vector3) + Param[3]: rotationAxis (type: Vector3) + Param[4]: rotationAngle (type: float) + Param[5]: scale (type: Vector3) + Param[6]: tint (type: Color) +Function 467: DrawBoundingBox() (2 input parameters) Name: DrawBoundingBox Return type: void Description: Draw bounding box (wires) Param[1]: box (type: BoundingBox) Param[2]: color (type: Color) -Function 466: DrawBillboard() (5 input parameters) +Function 468: DrawBillboard() (5 input parameters) Name: DrawBillboard Return type: void Description: Draw a billboard texture @@ -3970,7 +3988,7 @@ Function 466: DrawBillboard() (5 input parameters) Param[3]: position (type: Vector3) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 467: DrawBillboardRec() (6 input parameters) +Function 469: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec Return type: void Description: Draw a billboard texture defined by source @@ -3980,7 +3998,7 @@ Function 467: DrawBillboardRec() (6 input parameters) Param[4]: position (type: Vector3) Param[5]: size (type: Vector2) Param[6]: tint (type: Color) -Function 468: DrawBillboardPro() (9 input parameters) +Function 470: DrawBillboardPro() (9 input parameters) Name: DrawBillboardPro Return type: void Description: Draw a billboard texture defined by source and rotation @@ -3993,13 +4011,13 @@ Function 468: DrawBillboardPro() (9 input parameters) Param[7]: origin (type: Vector2) Param[8]: rotation (type: float) Param[9]: tint (type: Color) -Function 469: UploadMesh() (2 input parameters) +Function 471: UploadMesh() (2 input parameters) Name: UploadMesh Return type: void Description: Upload mesh vertex data in GPU and provide VAO/VBO ids Param[1]: mesh (type: Mesh *) Param[2]: dynamic (type: bool) -Function 470: UpdateMeshBuffer() (5 input parameters) +Function 472: UpdateMeshBuffer() (5 input parameters) Name: UpdateMeshBuffer Return type: void Description: Update mesh vertex data in GPU for a specific buffer index @@ -4008,19 +4026,19 @@ Function 470: UpdateMeshBuffer() (5 input parameters) Param[3]: data (type: const void *) Param[4]: dataSize (type: int) Param[5]: offset (type: int) -Function 471: UnloadMesh() (1 input parameters) +Function 473: UnloadMesh() (1 input parameters) Name: UnloadMesh Return type: void Description: Unload mesh data from CPU and GPU Param[1]: mesh (type: Mesh) -Function 472: DrawMesh() (3 input parameters) +Function 474: DrawMesh() (3 input parameters) Name: DrawMesh Return type: void Description: Draw a 3d mesh with material and transform Param[1]: mesh (type: Mesh) Param[2]: material (type: Material) Param[3]: transform (type: Matrix) -Function 473: DrawMeshInstanced() (4 input parameters) +Function 475: DrawMeshInstanced() (4 input parameters) Name: DrawMeshInstanced Return type: void Description: Draw multiple mesh instances with material and different transforms @@ -4028,35 +4046,35 @@ Function 473: DrawMeshInstanced() (4 input parameters) Param[2]: material (type: Material) Param[3]: transforms (type: const Matrix *) Param[4]: instances (type: int) -Function 474: GetMeshBoundingBox() (1 input parameters) +Function 476: GetMeshBoundingBox() (1 input parameters) Name: GetMeshBoundingBox Return type: BoundingBox Description: Compute mesh bounding box limits Param[1]: mesh (type: Mesh) -Function 475: GenMeshTangents() (1 input parameters) +Function 477: GenMeshTangents() (1 input parameters) Name: GenMeshTangents Return type: void Description: Compute mesh tangents Param[1]: mesh (type: Mesh *) -Function 476: ExportMesh() (2 input parameters) +Function 478: ExportMesh() (2 input parameters) Name: ExportMesh Return type: bool Description: Export mesh data to file, returns true on success Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 477: ExportMeshAsCode() (2 input parameters) +Function 479: ExportMeshAsCode() (2 input parameters) Name: ExportMeshAsCode Return type: bool Description: Export mesh as code file (.h) defining multiple arrays of vertex attributes Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 478: GenMeshPoly() (2 input parameters) +Function 480: GenMeshPoly() (2 input parameters) Name: GenMeshPoly Return type: Mesh Description: Generate polygonal mesh Param[1]: sides (type: int) Param[2]: radius (type: float) -Function 479: GenMeshPlane() (4 input parameters) +Function 481: GenMeshPlane() (4 input parameters) Name: GenMeshPlane Return type: Mesh Description: Generate plane mesh (with subdivisions) @@ -4064,42 +4082,42 @@ Function 479: GenMeshPlane() (4 input parameters) Param[2]: length (type: float) Param[3]: resX (type: int) Param[4]: resZ (type: int) -Function 480: GenMeshCube() (3 input parameters) +Function 482: GenMeshCube() (3 input parameters) Name: GenMeshCube Return type: Mesh Description: Generate cuboid mesh Param[1]: width (type: float) Param[2]: height (type: float) Param[3]: length (type: float) -Function 481: GenMeshSphere() (3 input parameters) +Function 483: GenMeshSphere() (3 input parameters) Name: GenMeshSphere Return type: Mesh Description: Generate sphere mesh (standard sphere) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 482: GenMeshHemiSphere() (3 input parameters) +Function 484: GenMeshHemiSphere() (3 input parameters) Name: GenMeshHemiSphere Return type: Mesh Description: Generate half-sphere mesh (no bottom cap) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 483: GenMeshCylinder() (3 input parameters) +Function 485: GenMeshCylinder() (3 input parameters) Name: GenMeshCylinder Return type: Mesh Description: Generate cylinder mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 484: GenMeshCone() (3 input parameters) +Function 486: GenMeshCone() (3 input parameters) Name: GenMeshCone Return type: Mesh Description: Generate cone/pyramid mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 485: GenMeshTorus() (4 input parameters) +Function 487: GenMeshTorus() (4 input parameters) Name: GenMeshTorus Return type: Mesh Description: Generate torus mesh @@ -4107,7 +4125,7 @@ Function 485: GenMeshTorus() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 486: GenMeshKnot() (4 input parameters) +Function 488: GenMeshKnot() (4 input parameters) Name: GenMeshKnot Return type: Mesh Description: Generate trefoil knot mesh @@ -4115,84 +4133,84 @@ Function 486: GenMeshKnot() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 487: GenMeshHeightmap() (2 input parameters) +Function 489: GenMeshHeightmap() (2 input parameters) Name: GenMeshHeightmap Return type: Mesh Description: Generate heightmap mesh from image data Param[1]: heightmap (type: Image) Param[2]: size (type: Vector3) -Function 488: GenMeshCubicmap() (2 input parameters) +Function 490: GenMeshCubicmap() (2 input parameters) Name: GenMeshCubicmap Return type: Mesh Description: Generate cubes-based map mesh from image data Param[1]: cubicmap (type: Image) Param[2]: cubeSize (type: Vector3) -Function 489: LoadMaterials() (2 input parameters) +Function 491: LoadMaterials() (2 input parameters) Name: LoadMaterials Return type: Material * Description: Load materials from model file Param[1]: fileName (type: const char *) Param[2]: materialCount (type: int *) -Function 490: LoadMaterialDefault() (0 input parameters) +Function 492: LoadMaterialDefault() (0 input parameters) Name: LoadMaterialDefault Return type: Material Description: Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) No input parameters -Function 491: IsMaterialReady() (1 input parameters) +Function 493: IsMaterialReady() (1 input parameters) Name: IsMaterialReady Return type: bool Description: Check if a material is ready Param[1]: material (type: Material) -Function 492: UnloadMaterial() (1 input parameters) +Function 494: UnloadMaterial() (1 input parameters) Name: UnloadMaterial Return type: void Description: Unload material from GPU memory (VRAM) Param[1]: material (type: Material) -Function 493: SetMaterialTexture() (3 input parameters) +Function 495: SetMaterialTexture() (3 input parameters) Name: SetMaterialTexture Return type: void Description: Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) Param[1]: material (type: Material *) Param[2]: mapType (type: int) Param[3]: texture (type: Texture2D) -Function 494: SetModelMeshMaterial() (3 input parameters) +Function 496: SetModelMeshMaterial() (3 input parameters) Name: SetModelMeshMaterial Return type: void Description: Set material for a mesh Param[1]: model (type: Model *) Param[2]: meshId (type: int) Param[3]: materialId (type: int) -Function 495: LoadModelAnimations() (2 input parameters) +Function 497: LoadModelAnimations() (2 input parameters) Name: LoadModelAnimations Return type: ModelAnimation * Description: Load model animations from file Param[1]: fileName (type: const char *) Param[2]: animCount (type: int *) -Function 496: UpdateModelAnimation() (3 input parameters) +Function 498: UpdateModelAnimation() (3 input parameters) Name: UpdateModelAnimation Return type: void Description: Update model animation pose Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 497: UnloadModelAnimation() (1 input parameters) +Function 499: UnloadModelAnimation() (1 input parameters) Name: UnloadModelAnimation Return type: void Description: Unload animation data Param[1]: anim (type: ModelAnimation) -Function 498: UnloadModelAnimations() (2 input parameters) +Function 500: UnloadModelAnimations() (2 input parameters) Name: UnloadModelAnimations Return type: void Description: Unload animation array data Param[1]: animations (type: ModelAnimation *) Param[2]: animCount (type: int) -Function 499: IsModelAnimationValid() (2 input parameters) +Function 501: IsModelAnimationValid() (2 input parameters) Name: IsModelAnimationValid Return type: bool Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 500: CheckCollisionSpheres() (4 input parameters) +Function 502: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4200,40 +4218,40 @@ Function 500: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 501: CheckCollisionBoxes() (2 input parameters) +Function 503: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 502: CheckCollisionBoxSphere() (3 input parameters) +Function 504: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 503: GetRayCollisionSphere() (3 input parameters) +Function 505: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 504: GetRayCollisionBox() (2 input parameters) +Function 506: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 505: GetRayCollisionMesh() (3 input parameters) +Function 507: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 506: GetRayCollisionTriangle() (4 input parameters) +Function 508: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4241,7 +4259,7 @@ Function 506: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 507: GetRayCollisionQuad() (5 input parameters) +Function 509: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4250,158 +4268,158 @@ Function 507: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 508: InitAudioDevice() (0 input parameters) +Function 510: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 509: CloseAudioDevice() (0 input parameters) +Function 511: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 510: IsAudioDeviceReady() (0 input parameters) +Function 512: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 511: SetMasterVolume() (1 input parameters) +Function 513: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 512: GetMasterVolume() (0 input parameters) +Function 514: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 513: LoadWave() (1 input parameters) +Function 515: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 514: LoadWaveFromMemory() (3 input parameters) +Function 516: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 515: IsWaveReady() (1 input parameters) +Function 517: IsWaveReady() (1 input parameters) Name: IsWaveReady Return type: bool Description: Checks if wave data is ready Param[1]: wave (type: Wave) -Function 516: LoadSound() (1 input parameters) +Function 518: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 517: LoadSoundFromWave() (1 input parameters) +Function 519: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 518: LoadSoundAlias() (1 input parameters) +Function 520: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 519: IsSoundReady() (1 input parameters) +Function 521: IsSoundReady() (1 input parameters) Name: IsSoundReady Return type: bool Description: Checks if a sound is ready Param[1]: sound (type: Sound) -Function 520: UpdateSound() (3 input parameters) +Function 522: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 521: UnloadWave() (1 input parameters) +Function 523: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 522: UnloadSound() (1 input parameters) +Function 524: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 523: UnloadSoundAlias() (1 input parameters) +Function 525: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 524: ExportWave() (2 input parameters) +Function 526: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 525: ExportWaveAsCode() (2 input parameters) +Function 527: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 526: PlaySound() (1 input parameters) +Function 528: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 527: StopSound() (1 input parameters) +Function 529: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 528: PauseSound() (1 input parameters) +Function 530: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 529: ResumeSound() (1 input parameters) +Function 531: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 530: IsSoundPlaying() (1 input parameters) +Function 532: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 531: SetSoundVolume() (2 input parameters) +Function 533: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 532: SetSoundPitch() (2 input parameters) +Function 534: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 533: SetSoundPan() (2 input parameters) +Function 535: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (0.5 is center) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 534: WaveCopy() (1 input parameters) +Function 536: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 535: WaveCrop() (3 input parameters) +Function 537: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 536: WaveFormat() (4 input parameters) +Function 538: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4409,203 +4427,203 @@ Function 536: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 537: LoadWaveSamples() (1 input parameters) +Function 539: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 538: UnloadWaveSamples() (1 input parameters) +Function 540: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 539: LoadMusicStream() (1 input parameters) +Function 541: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 540: LoadMusicStreamFromMemory() (3 input parameters) +Function 542: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 541: IsMusicReady() (1 input parameters) +Function 543: IsMusicReady() (1 input parameters) Name: IsMusicReady Return type: bool Description: Checks if a music stream is ready Param[1]: music (type: Music) -Function 542: UnloadMusicStream() (1 input parameters) +Function 544: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 543: PlayMusicStream() (1 input parameters) +Function 545: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 544: IsMusicStreamPlaying() (1 input parameters) +Function 546: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 545: UpdateMusicStream() (1 input parameters) +Function 547: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 546: StopMusicStream() (1 input parameters) +Function 548: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 547: PauseMusicStream() (1 input parameters) +Function 549: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 548: ResumeMusicStream() (1 input parameters) +Function 550: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 549: SeekMusicStream() (2 input parameters) +Function 551: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 550: SetMusicVolume() (2 input parameters) +Function 552: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 551: SetMusicPitch() (2 input parameters) +Function 553: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 552: SetMusicPan() (2 input parameters) +Function 554: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (0.5 is center) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 553: GetMusicTimeLength() (1 input parameters) +Function 555: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 554: GetMusicTimePlayed() (1 input parameters) +Function 556: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 555: LoadAudioStream() (3 input parameters) +Function 557: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 556: IsAudioStreamReady() (1 input parameters) +Function 558: IsAudioStreamReady() (1 input parameters) Name: IsAudioStreamReady Return type: bool Description: Checks if an audio stream is ready Param[1]: stream (type: AudioStream) -Function 557: UnloadAudioStream() (1 input parameters) +Function 559: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 558: UpdateAudioStream() (3 input parameters) +Function 560: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 559: IsAudioStreamProcessed() (1 input parameters) +Function 561: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 560: PlayAudioStream() (1 input parameters) +Function 562: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 561: PauseAudioStream() (1 input parameters) +Function 563: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 562: ResumeAudioStream() (1 input parameters) +Function 564: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 563: IsAudioStreamPlaying() (1 input parameters) +Function 565: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 564: StopAudioStream() (1 input parameters) +Function 566: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 565: SetAudioStreamVolume() (2 input parameters) +Function 567: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 566: SetAudioStreamPitch() (2 input parameters) +Function 568: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 567: SetAudioStreamPan() (2 input parameters) +Function 569: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 568: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 570: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 569: SetAudioStreamCallback() (2 input parameters) +Function 571: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 570: AttachAudioStreamProcessor() (2 input parameters) +Function 572: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives the samples as 'float' Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 571: DetachAudioStreamProcessor() (2 input parameters) +Function 573: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 572: AttachAudioMixedProcessor() (1 input parameters) +Function 574: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' Param[1]: processor (type: AudioCallback) -Function 573: DetachAudioMixedProcessor() (1 input parameters) +Function 575: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 8a6560f489bc..02cfd02d0bc7 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -670,7 +670,7 @@ - + @@ -2637,6 +2637,20 @@ + + + + + + + + + + + + + + From bc0bd98763bba496836c5cfc1418949256bc6f4d Mon Sep 17 00:00:00 2001 From: Colleague Riley Date: Sat, 24 Aug 2024 12:43:46 -0400 Subject: [PATCH 049/107] (rcore_desktop_rgfw.c) fix errors when compiling with mingw (#4282) * (rcore_desktop_rgfw.c) fix errors when compiling with mingw * define WideCharToMultiByte --- src/external/RGFW.h | 4 +++- src/platforms/rcore_desktop_rgfw.c | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 367a46fa608e..35e782d4ae2b 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -4999,7 +4999,9 @@ static const struct wl_callback_listener wl_surface_frame_listener = { #define WIN32_LEAN_AND_MEAN #define OEMRESOURCE #include - + + __declspec(dllimport) int __stdcall WideCharToMultiByte( UINT CodePage, DWORD dwFlags, const WCHAR* lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); + #include #include #include diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index afea0c2e6f6a..64ab432e6c3e 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -76,9 +76,7 @@ void CloseWindow(void); #define Size NSSIZE #endif -#if defined(_MSC_VER) __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); -#endif #include "../external/RGFW.h" @@ -538,10 +536,8 @@ void *GetWindowHandle(void) { #ifdef RGFW_WEBASM return (void*)platform.window->src.ctx; -#elif !defined(RGFW_WINDOWS) - return (void *)platform.window->src.window; #else - return platform.window->src.hwnd; + return (void*)platform.window->src.window; #endif } From 414133dbe7b063566a338d9c4ef1c9eb0651dec4 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 24 Aug 2024 18:56:06 +0200 Subject: [PATCH 050/107] Update models_point_rendering.c --- examples/models/models_point_rendering.c | 107 +++++++++++++---------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/examples/models/models_point_rendering.c b/examples/models/models_point_rendering.c index 128a70f57ee1..68d498453e41 100644 --- a/examples/models/models_point_rendering.c +++ b/examples/models/models_point_rendering.c @@ -14,13 +14,15 @@ ********************************************************************************************/ #include "raylib.h" -#include // Required for: rand() -#include // Required for: cos(), sin() -#define MAX_POINTS 10000000 // 10 million -#define MIN_POINTS 1000 // 1 thousand +#include // Required for: rand() +#include // Required for: cos(), sin() -static float RandFloat(); +#define MAX_POINTS 10000000 // 10 million +#define MIN_POINTS 1000 // 1 thousand + +// Generate mesh using points +Mesh GenMeshPoints(int numPoints); //------------------------------------------------------------------------------------ // Program main entry point @@ -31,23 +33,26 @@ int main() //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; + InitWindow(screenWidth, screenHeight, "raylib [models] example - point rendering"); - SetTargetFPS(60); Camera camera = { - .position = {3.0f, 3.0f, 3.0f}, - .target = {0.0f, 0.0f, 0.0f}, - .up = {0.0f, 1.0f, 0.0f}, + .position = { 3.0f, 3.0f, 3.0f }, + .target = { 0.0f, 0.0f, 0.0f }, + .up = { 0.0f, 1.0f, 0.0f }, .fovy = 45.0f, - .projection = CAMERA_PERSPECTIVE, + .projection = CAMERA_PERSPECTIVE }; - Vector3 position = {0.0f, 0.0f, 0.0f}; + Vector3 position = { 0.0f, 0.0f, 0.0f }; bool useDrawModelPoints = true; bool numPointsChanged = false; int numPoints = 1000; - Mesh mesh = GenPoints(numPoints); + + Mesh mesh = GenMeshPoints(numPoints); Model model = LoadModelFromMesh(mesh); + + //SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop @@ -60,30 +65,30 @@ int main() if (IsKeyPressed(KEY_SPACE)) useDrawModelPoints = !useDrawModelPoints; if (IsKeyPressed(KEY_UP)) { - numPoints = (numPoints * 10 > MAX_POINTS) ? MAX_POINTS : numPoints * 10; + numPoints = (numPoints*10 > MAX_POINTS)? MAX_POINTS : numPoints*10; numPointsChanged = true; - TraceLog(LOG_INFO, "num points %d", numPoints); } if (IsKeyPressed(KEY_DOWN)) { - numPoints = (numPoints / 10 < MIN_POINTS) ? MIN_POINTS : numPoints / 10; + numPoints = (numPoints/10 < MIN_POINTS)? MIN_POINTS : numPoints/10; numPointsChanged = true; - TraceLog(LOG_INFO, "num points %d", numPoints); } - // upload a different point cloud size + // Upload a different point cloud size if (numPointsChanged) { UnloadModel(model); - mesh = GenPoints(numPoints); + mesh = GenMeshPoints(numPoints); model = LoadModelFromMesh(mesh); numPointsChanged = false; } + //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); ClearBackground(BLACK); + BeginMode3D(camera); // The new method only uploads the points once to the GPU @@ -91,42 +96,43 @@ int main() { DrawModelPoints(model, position, 1.0f, WHITE); } - // The old method must continually draw the "points" (lines) else { + // The old method must continually draw the "points" (lines) for (int i = 0; i < numPoints; i++) { Vector3 pos = { - .x = mesh.vertices[i * 3 + 0], - .y = mesh.vertices[i * 3 + 1], - .z = mesh.vertices[i * 3 + 2], + .x = mesh.vertices[i*3 + 0], + .y = mesh.vertices[i*3 + 1], + .z = mesh.vertices[i*3 + 2], }; Color color = { - .r = mesh.colors[i * 4 + 0], - .g = mesh.colors[i * 4 + 1], - .b = mesh.colors[i * 4 + 2], - .a = mesh.colors[i * 4 + 3], + .r = mesh.colors[i*4 + 0], + .g = mesh.colors[i*4 + 1], + .b = mesh.colors[i*4 + 2], + .a = mesh.colors[i*4 + 3], }; + DrawPoint3D(pos, color); } } // Draw a unit sphere for reference DrawSphereWires(position, 1.0f, 10, 10, YELLOW); + EndMode3D(); - // Text formatting - Color color = WHITE; - int fps = GetFPS(); - if ((fps < 30) && (fps >= 15)) color = ORANGE; - else if (fps < 15) color = RED; - DrawText(TextFormat("%2i FPS", fps), 20, 20, 40, color); + // Draw UI text DrawText(TextFormat("Point Count: %d", numPoints), 20, screenHeight - 50, 40, WHITE); DrawText("Up - increase points", 20, 70, 20, WHITE); DrawText("Down - decrease points", 20, 100, 20, WHITE); DrawText("Space - drawing function", 20, 130, 20, WHITE); - if (useDrawModelPoints) DrawText("DrawModelPoints()", 20, 160, 20, GREEN); - else DrawText("DrawPoint3D()", 20, 160, 20, RED); + + if (useDrawModelPoints) DrawText("Using: DrawModelPoints()", 20, 160, 20, GREEN); + else DrawText("Using: DrawPoint3D()", 20, 160, 20, RED); + + DrawFPS(10, 10); + EndDrawing(); //---------------------------------------------------------------------------------- } @@ -134,38 +140,43 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- UnloadModel(model); + CloseWindow(); //-------------------------------------------------------------------------------------- return 0; } // Generate a spherical point cloud -Mesh GenPoints(int numPoints) +Mesh GenMeshPoints(int numPoints) { Mesh mesh = { .triangleCount = 1, .vertexCount = numPoints, - .vertices = (float *)MemAlloc(numPoints * 3 * sizeof(float)), - .colors = (unsigned char*)MemAlloc(numPoints * 4 * sizeof(unsigned char)), + .vertices = (float *)MemAlloc(numPoints*3*sizeof(float)), + .colors = (unsigned char*)MemAlloc(numPoints*4*sizeof(unsigned char)), }; // https://en.wikipedia.org/wiki/Spherical_coordinate_system for (int i = 0; i < numPoints; i++) { - float theta = PI * rand() / RAND_MAX; - float phi = 2.0f * PI * rand() / RAND_MAX; - float r = 10.0f * rand() / RAND_MAX; - mesh.vertices[i * 3 + 0] = r * sin(theta) * cos(phi); - mesh.vertices[i * 3 + 1] = r * sin(theta) * sin(phi); - mesh.vertices[i * 3 + 2] = r * cos(theta); - Color color = ColorFromHSV(r * 360.0f, 1.0f, 1.0f); - mesh.colors[i * 4 + 0] = color.r; - mesh.colors[i * 4 + 1] = color.g; - mesh.colors[i * 4 + 2] = color.b; - mesh.colors[i * 4 + 3] = color.a; + float theta = PI*rand()/RAND_MAX; + float phi = 2.0f*PI*rand()/RAND_MAX; + float r = 10.0f*rand()/RAND_MAX; + + mesh.vertices[i*3 + 0] = r*sin(theta)*cos(phi); + mesh.vertices[i*3 + 1] = r*sin(theta)*sin(phi); + mesh.vertices[i*3 + 2] = r*cos(theta); + + Color color = ColorFromHSV(r*360.0f, 1.0f, 1.0f); + + mesh.colors[i*4 + 0] = color.r; + mesh.colors[i*4 + 1] = color.g; + mesh.colors[i*4 + 2] = color.b; + mesh.colors[i*4 + 3] = color.a; } // Upload mesh data from CPU (RAM) to GPU (VRAM) memory UploadMesh(&mesh, false); + return mesh; } From 78e86b6ea5824699f9fdaf708f532e5215ea9e7f Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 24 Aug 2024 18:58:43 +0200 Subject: [PATCH 051/107] Update rlgl.h --- src/rlgl.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 5fb9497cd20a..2f8515ee2a65 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -665,7 +665,7 @@ RLAPI void rlDisableScissorTest(void); // Disable scissor test RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test RLAPI void rlEnableWireMode(void); // Enable wire mode RLAPI void rlEnablePointMode(void); // Enable point mode -RLAPI void rlDisableWireMode(void); // Disable wire mode ( and point ) maybe rename +RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode RLAPI void rlSetLineWidth(float width); // Set the line drawing width RLAPI float rlGetLineWidth(void); // Get the line drawing width RLAPI void rlEnableSmoothLines(void); // Enable line aliasing @@ -1948,6 +1948,7 @@ void rlEnableWireMode(void) #endif } +// Enable point mode void rlEnablePointMode(void) { #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) @@ -1956,6 +1957,7 @@ void rlEnablePointMode(void) glEnable(GL_PROGRAM_POINT_SIZE); #endif } + // Disable wire mode void rlDisableWireMode(void) { @@ -4236,6 +4238,8 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() } #endif } From 4c9282b09025677a90e5e54ffe142824560163b6 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 24 Aug 2024 18:59:24 +0200 Subject: [PATCH 052/107] ADDED: `isGpuReady` flag, allow font loading with no GPU acceleration --- src/rcore.c | 48 ++++++++++++++++++++++++++++-------------------- src/rtext.c | 32 ++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index e8041ea4198c..972d5860074c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -360,10 +360,15 @@ typedef struct CoreData { //---------------------------------------------------------------------------------- RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings -CoreData CORE = { 0 }; // Global CORE state context +CoreData CORE = { 0 }; // Global CORE state context + +// Flag to note GPU acceleration is available, +// referenced from other modules to support GPU data loading +// NOTE: Useful to allow Texture, RenderTexture, Font.texture, Mesh.vaoId/vboId, Shader loading +bool isGpuReady = false; #if defined(SUPPORT_SCREEN_CAPTURE) -static int screenshotCounter = 0; // Screenshots counter +static int screenshotCounter = 0; // Screenshots counter #endif #if defined(SUPPORT_GIF_RECORDING) @@ -637,28 +642,31 @@ void InitWindow(int width, int height, const char *title) // Initialize rlgl default data (buffers and shaders) // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); + isGpuReady = true; // Flag to note GPU has been initialized successfully // Setup default viewport SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); -#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) - // Load default font - // WARNING: External function: Module required: rtext - LoadFontDefault(); - #if defined(SUPPORT_MODULE_RSHAPES) - // Set font white rectangle for shapes drawing, so shapes and text can be batched together - // WARNING: rshapes module is required, if not available, default internal white rectangle is used - Rectangle rec = GetFontDefault().recs[95]; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) - { - // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering - SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); - } - else - { - // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding - SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); - } +#if defined(SUPPORT_MODULE_RTEXT) + #if defined(SUPPORT_DEFAULT_FONT) + // Load default font + // WARNING: External function: Module required: rtext + LoadFontDefault(); + #if defined(SUPPORT_MODULE_RSHAPES) + // Set font white rectangle for shapes drawing, so shapes and text can be batched together + // WARNING: rshapes module is required, if not available, default internal white rectangle is used + Rectangle rec = GetFontDefault().recs[95]; + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); + } + else + { + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + } + #endif #endif #else #if defined(SUPPORT_MODULE_RSHAPES) diff --git a/src/rtext.c b/src/rtext.c index 8c1dc3c5e53d..4f1be2cd7819 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -124,6 +124,7 @@ //---------------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------------- +extern bool isGpuReady; #if defined(SUPPORT_DEFAULT_FONT) // Default font provided by raylib // NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core] @@ -252,7 +253,7 @@ extern void LoadFontDefault(void) counter++; } - defaultFont.texture = LoadTextureFromImage(imFont); + if (isGpuReady) defaultFont.texture = LoadTextureFromImage(imFont); // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, glyphCount //------------------------------------------------------------------------------ @@ -308,7 +309,7 @@ extern void LoadFontDefault(void) extern void UnloadFontDefault(void) { for (int i = 0; i < defaultFont.glyphCount; i++) UnloadImage(defaultFont.glyphs[i].image); - UnloadTexture(defaultFont.texture); + if (isGpuReady) UnloadTexture(defaultFont.texture); RL_FREE(defaultFont.glyphs); RL_FREE(defaultFont.recs); } @@ -362,11 +363,14 @@ Font LoadFont(const char *fileName) UnloadImage(image); } - if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); - else + if (isGpuReady) { - SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance) - TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", FONT_TTF_DEFAULT_SIZE, FONT_TTF_DEFAULT_NUMCHARS); + if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); + else + { + SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance) + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", FONT_TTF_DEFAULT_SIZE, FONT_TTF_DEFAULT_NUMCHARS); + } } return font; @@ -487,7 +491,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) }; // Set font with all data parsed from image - font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture + if (isGpuReady) font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture font.glyphCount = index; font.glyphPadding = 0; @@ -556,7 +560,7 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING; Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0); - font.texture = LoadTextureFromImage(atlas); + if (isGpuReady) font.texture = LoadTextureFromImage(atlas); // Update glyphs[i].image to use alpha, required to be used on ImageDrawText() for (int i = 0; i < font.glyphCount; i++) @@ -580,7 +584,7 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int // Check if a font is ready bool IsFontReady(Font font) { - return ((font.texture.id > 0) && // Validate OpenGL id fot font texture atlas + return ((font.texture.id > 0) && // Validate OpenGL id for font texture atlas (font.baseSize > 0) && // Validate font size (font.glyphCount > 0) && // Validate font contains some glyph (font.recs != NULL) && // Validate font recs defining glyphs on texture atlas @@ -946,7 +950,7 @@ void UnloadFont(Font font) if (font.texture.id != GetFontDefault().texture.id) { UnloadFontData(font.glyphs, font.glyphCount); - UnloadTexture(font.texture); + if (isGpuReady) UnloadTexture(font.texture); RL_FREE(font.recs); TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); @@ -1066,7 +1070,7 @@ bool ExportFontAsCode(Font font, const char *fileName) byteCount += sprintf(txtData + byteCount, " Image imFont = { fontImageData_%s, %i, %i, 1, %i };\n\n", styleName, image.width, image.height, image.format); #endif byteCount += sprintf(txtData + byteCount, " // Load texture from image\n"); - byteCount += sprintf(txtData + byteCount, " font.texture = LoadTextureFromImage(imFont);\n"); + byteCount += sprintf(txtData + byteCount, " if (isGpuReady) font.texture = LoadTextureFromImage(imFont);\n"); #if defined(SUPPORT_COMPRESSED_FONT_ATLAS) byteCount += sprintf(txtData + byteCount, " UnloadImage(imFont); // Uncompressed data can be unloaded from memory\n\n"); #endif @@ -1277,7 +1281,7 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing { Vector2 textSize = { 0 }; - if ((font.texture.id == 0) || (text == NULL)) return textSize; // Security check + if ((isGpuReady && (font.texture.id == 0)) || (text == NULL)) return textSize; // Security check int size = TextLength(text); // Get size in bytes of text int tempByteCounter = 0; // Used to count longer text line num chars @@ -2257,7 +2261,7 @@ static Font LoadBMFont(const char *fileName) RL_FREE(imFonts); - font.texture = LoadTextureFromImage(fullFont); + if (isGpuReady) font.texture = LoadTextureFromImage(fullFont); // Fill font characters info data font.baseSize = fontSize; @@ -2299,7 +2303,7 @@ static Font LoadBMFont(const char *fileName) UnloadImage(fullFont); UnloadFileText(fileText); - if (font.texture.id == 0) + if (isGpuReady && (font.texture.id == 0)) { UnloadFont(font); font = GetFontDefault(); From 0aba21f71c30f3375fa898f8c733089528617639 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 24 Aug 2024 11:31:28 -0700 Subject: [PATCH 053/107] [RCORE] Update comments on fullscreen and boderless window to describe what they do (#4280) * Update raylib_api.* by CI * update fullscreen and borderless comments to better describe what they do. * Update raylib_api.* by CI --------- Co-authored-by: github-actions[bot] --- parser/output/raylib_api.json | 4 ++-- parser/output/raylib_api.lua | 4 ++-- parser/output/raylib_api.txt | 4 ++-- parser/output/raylib_api.xml | 4 ++-- src/raylib.h | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index dfe37e2fec08..03b19325a35a 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -3215,12 +3215,12 @@ }, { "name": "ToggleFullscreen", - "description": "Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP)", + "description": "Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP)", "returnType": "void" }, { "name": "ToggleBorderlessWindowed", - "description": "Toggle window state: borderless windowed (only PLATFORM_DESKTOP)", + "description": "Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP)", "returnType": "void" }, { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 933b2c704e0d..7d74c9a41361 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -3158,12 +3158,12 @@ return { }, { name = "ToggleFullscreen", - description = "Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP)", + description = "Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP)", returnType = "void" }, { name = "ToggleBorderlessWindowed", - description = "Toggle window state: borderless windowed (only PLATFORM_DESKTOP)", + description = "Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP)", returnType = "void" }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 94562d66aa8f..fb30ebe5f228 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -1056,12 +1056,12 @@ Function 013: ClearWindowState() (1 input parameters) Function 014: ToggleFullscreen() (0 input parameters) Name: ToggleFullscreen Return type: void - Description: Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) + Description: Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP) No input parameters Function 015: ToggleBorderlessWindowed() (0 input parameters) Name: ToggleBorderlessWindowed Return type: void - Description: Toggle window state: borderless windowed (only PLATFORM_DESKTOP) + Description: Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP) No input parameters Function 016: MaximizeWindow() (0 input parameters) Name: MaximizeWindow diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 02cfd02d0bc7..532b9e3b9292 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -703,9 +703,9 @@ - + - + diff --git a/src/raylib.h b/src/raylib.h index f51a444e1f07..7e9af7ada222 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -968,8 +968,8 @@ RLAPI bool IsWindowResized(void); // Check if wi RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags (only PLATFORM_DESKTOP) RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags -RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) -RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed (only PLATFORM_DESKTOP) +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP) +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP) RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) From 0c06a08e0738a5777d150b7b4562cd8595680271 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:33:21 +0200 Subject: [PATCH 054/107] [rcore][desktop_glfw] Keeping CORE.Window.position properly in sync with glfw window position (#4190) * WindowPosCallback added. CORE.Window.position is now properly kept in sync with the glfw window position. * Update rcore_desktop_glfw.c Comments updated. * Setting CORE.Window.position correctly in InitPlatform() as well. This also fixes not centering the window correctly when the high dpi flag was enabled. * Fixes centering the window in the SetWindowMonitor() function. Here the render size has to be used again in case the high dpi flag is enabled. * Update Window Position Update Window Position right away in ToggleFullscreen() & ToggleBorderlessWindowed() functions --- src/platforms/rcore_desktop_glfw.c | 47 ++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index d64bf60db888..c6685846cc83 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -109,6 +109,7 @@ static void ErrorCallback(int error, const char *description); // Window callbacks events static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +static void WindowPosCallback(GLFWwindow* window, int x, int y); // GLFW3 WindowPos Callback, runs when window is moved static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus @@ -147,8 +148,8 @@ void ToggleFullscreen(void) if (!CORE.Window.fullscreen) { // Store previous window position (in case we exit fullscreen) - glfwGetWindowPos(platform.handle, &CORE.Window.position.x, &CORE.Window.position.y); - + CORE.Window.previousPosition = CORE.Window.position; + int monitorCount = 0; int monitorIndex = GetCurrentMonitor(); GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); @@ -179,7 +180,11 @@ void ToggleFullscreen(void) CORE.Window.fullscreen = false; CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; - glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + + // we update the window position right away + CORE.Window.position.x = CORE.Window.previousPosition.x; + CORE.Window.position.y = CORE.Window.previousPosition.y; } // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) @@ -190,11 +195,11 @@ void ToggleFullscreen(void) // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - // Leave fullscreen before attempting to set borderless windowed mode and get screen position from it + // Leave fullscreen before attempting to set borderless windowed mode bool wasOnFullscreen = false; if (CORE.Window.fullscreen) { - CORE.Window.previousPosition = CORE.Window.position; + // fullscreen already saves the previous position so it does not need to be set here again ToggleFullscreen(); wasOnFullscreen = true; } @@ -213,7 +218,7 @@ void ToggleBorderlessWindowed(void) { // Store screen position and size // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here - if (!wasOnFullscreen) glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y); + if (!wasOnFullscreen) CORE.Window.previousPosition = CORE.Window.position; CORE.Window.previousScreen = CORE.Window.screen; // Set undecorated and topmost modes and flags @@ -255,6 +260,9 @@ void ToggleBorderlessWindowed(void) glfwFocusWindow(platform.handle); CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + + CORE.Window.position.x = CORE.Window.previousPosition.x; + CORE.Window.position.y = CORE.Window.previousPosition.y; } } else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); @@ -592,6 +600,9 @@ void SetWindowTitle(const char *title) // Set window position on screen (windowed mode) void SetWindowPosition(int x, int y) { + // Update CORE.Window.position as well + CORE.Window.position.x = x; + CORE.Window.position.y = y; glfwSetWindowPos(platform.handle, x, y); } @@ -614,8 +625,9 @@ void SetWindowMonitor(int monitor) { TRACELOG(LOG_INFO, "GLFW: Selected monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); - const int screenWidth = CORE.Window.screen.width; - const int screenHeight = CORE.Window.screen.height; + // Here the render width has to be used again in case high dpi flag is enabled + const int screenWidth = CORE.Window.render.width; + const int screenHeight = CORE.Window.render.height; int monitorWorkareaX = 0; int monitorWorkareaY = 0; int monitorWorkareaWidth = 0; @@ -1568,12 +1580,17 @@ int InitPlatform(void) int monitorWidth = 0; int monitorHeight = 0; glfwGetMonitorWorkarea(monitor, &monitorX, &monitorY, &monitorWidth, &monitorHeight); - - int posX = monitorX + (monitorWidth - (int)CORE.Window.screen.width)/2; - int posY = monitorY + (monitorHeight - (int)CORE.Window.screen.height)/2; + + // Here CORE.Window.render.width/height should be used instead of CORE.Window.screen.width/height to center the window correctly when the high dpi flag is enabled. + int posX = monitorX + (monitorWidth - (int)CORE.Window.render.width)/2; + int posY = monitorY + (monitorHeight - (int)CORE.Window.render.height)/2; if (posX < monitorX) posX = monitorX; if (posY < monitorY) posY = monitorY; SetWindowPosition(posX, posY); + + // Update CORE.Window.position here so it is correct from the start + CORE.Window.position.x = posX; + CORE.Window.position.y = posY; } // Load OpenGL extensions @@ -1585,6 +1602,7 @@ int InitPlatform(void) //---------------------------------------------------------------------------- // Set window callback events glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! + glfwSetWindowPosCallback(platform.handle, WindowPosCallback); glfwSetWindowMaximizeCallback(platform.handle, WindowMaximizeCallback); glfwSetWindowIconifyCallback(platform.handle, WindowIconifyCallback); glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); @@ -1681,7 +1699,12 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) // NOTE: Postprocessing texture is not scaled to new size } - +static void WindowPosCallback(GLFWwindow* window, int x, int y) +{ + // Set current window position + CORE.Window.position.x = x; + CORE.Window.position.y = y; +} static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) { CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f); From d314afc451e4b0ed6fe69249dffccb65bd1e0582 Mon Sep 17 00:00:00 2001 From: Tchan0 <61758157+Tchan0@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:35:49 +0200 Subject: [PATCH 055/107] rlgl.h: glint64 did not exist before OpenGL 3.2 (#4284) Compilation breaks on rlgl.h for early OpenGL versions. Glint64 did not exist on those versions (< OpenGL 3.2) --- src/rlgl.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 2f8515ee2a65..3efed736a48f 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4414,14 +4414,14 @@ void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSi // Get SSBO buffer size unsigned int rlGetShaderBufferSize(unsigned int id) { - GLint64 size = 0; - #if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); -#endif - return (size > 0)? (unsigned int)size : 0; +#else + return 0; +#endif } // Read SSBO buffer data (GPU->CPU) From 8dbf3712445c27e86bebf1b82e1de04cf1099a91 Mon Sep 17 00:00:00 2001 From: Tchan0 <61758157+Tchan0@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:23:08 +0200 Subject: [PATCH 056/107] Examples makefiles: align /usr/local with /src Makefile (#4286) * align /usr/local with src Makefile Align /usr/local with the /src Makefile, where it can be overriden. * /usr/local: allow override align /usr/local with the /src Makefile, where it can be overriden --- examples/Makefile | 5 +++-- examples/Makefile.Web | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index 904917f7c806..bdbb6e3510ad 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -70,8 +70,9 @@ RAYLIB_SRC_PATH ?= ../src # Locations of raylib.h and libraylib.a/libraylib.so # NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD -RAYLIB_INCLUDE_PATH ?= /usr/local/include -RAYLIB_LIB_PATH ?= /usr/local/lib +DESTDIR ?= /usr/local +RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include +RAYLIB_LIB_PATH ?= $(DESTDIR)/lib # Library type compilation: STATIC (.a) or SHARED (.so/.dll) RAYLIB_LIBTYPE ?= STATIC diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 7057da1c03d9..bd48c2a4aad8 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -35,8 +35,9 @@ RAYLIB_PATH ?= .. # Locations of raylib.h and libraylib.a/libraylib.so # NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD -RAYLIB_INCLUDE_PATH ?= /usr/local/include -RAYLIB_LIB_PATH ?= /usr/local/lib +DESTDIR ?= /usr/local +RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include +RAYLIB_LIB_PATH ?= $(DESTDIR)/lib # Library type compilation: STATIC (.a) or SHARED (.so/.dll) RAYLIB_LIBTYPE ?= STATIC From dec7f12b148ea4383c166d42812e19e0c793bdb0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 12:51:55 +0200 Subject: [PATCH 057/107] Update raylib.h --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raylib.h b/src/raylib.h index 7e9af7ada222..30ba8bd2aaac 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1443,7 +1443,7 @@ RLAPI int GetPixelDataSize(int width, int height, int format); // G // Font loading/unloading functions RLAPI Font GetFontDefault(void); // Get the default Font RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' RLAPI bool IsFontReady(Font font); // Check if a font is ready From 157c7c21d451e022f8227ee5b756a99689a1d3a3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 12:52:27 +0200 Subject: [PATCH 058/107] ADDED: more uniform data type options #4137 --- src/rlgl.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index 3efed736a48f..34fabe45ea90 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -527,6 +527,10 @@ typedef enum { RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d } rlShaderUniformDataType; @@ -4236,6 +4240,10 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); From 5604f6d8a34b94118a9f24e433eb06e771d8378c Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 12:57:05 +0200 Subject: [PATCH 059/107] Update rlgl.h --- src/rlgl.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index 34fabe45ea90..9b94d402c6d2 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4240,10 +4240,12 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); From f5ef3578100ff06dc1ad3752076e5771de4717f9 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 13:55:53 +0200 Subject: [PATCH 060/107] Update rtext.c --- src/rtext.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rtext.c b/src/rtext.c index 4f1be2cd7819..34b6fcedb6b9 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -677,6 +677,8 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz { stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); chars[i].advanceX = (int)((float)chars[i].advanceX*scaleFactor); + + if (chh > fontSize) TRACELOG(LOG_WARNING, "FONT: Character [0x%08x] size is bigger than expected font size", ch); // Load characters images chars[i].image.width = chw; From 91a9888baa61e221cb6e68a5fd8fc005fffcbca8 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sun, 25 Aug 2024 09:49:52 -0700 Subject: [PATCH 061/107] [rModels] Correctly split obj meshes by material (#4285) * Correctly split meshes from tinyobj by material so they can be represented by raylib correctly * PR Feedback --- src/rmodels.c | 292 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 200 insertions(+), 92 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 966a8665c622..5fb5124038d5 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2016,6 +2016,8 @@ static void ProcessMaterialsOBJ(Material *materials, tinyobj_material_t *mats, i // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE materials[m] = LoadMaterialDefault(); + if (mats == NULL) continue; + // Get default texture, in case no texture is defined // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; @@ -4073,132 +4075,238 @@ static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform // - the mesh is automatically triangulated by tinyobj static Model LoadOBJ(const char *fileName) { + tinyobj_attrib_t objAttributes = { 0 }; + tinyobj_shape_t* objShapes = NULL; + unsigned int objShapeCount = 0; + + tinyobj_material_t* objMaterials = NULL; + unsigned int objMaterialCount = 0; + Model model = { 0 }; + model.transform = MatrixIdentity(); - tinyobj_attrib_t attrib = { 0 }; - tinyobj_shape_t *meshes = NULL; - unsigned int meshCount = 0; + char* fileText = LoadFileText(fileName); - tinyobj_material_t *materials = NULL; - unsigned int materialCount = 0; + if (fileText == NULL) + { + TRACELOG(LOG_ERROR, "MODEL Unable to read obj file %s", fileName); + return model; + } - char *fileText = LoadFileText(fileName); + char currentDir[1024] = { 0 }; + strcpy(currentDir, GetWorkingDirectory()); // Save current working directory + const char* workingDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness + if (CHDIR(workingDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + } - if (fileText != NULL) + unsigned int dataSize = (unsigned int)strlen(fileText); + + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&objAttributes, &objShapes, &objShapeCount, &objMaterials, &objMaterialCount, fileText, dataSize, flags); + + if (ret != TINYOBJ_SUCCESS) { - unsigned int dataSize = (unsigned int)strlen(fileText); + TRACELOG(LOG_ERROR, "MODEL Unable to read obj data %s", fileName); + return model; + } + + UnloadFileText(fileText); + + unsigned int faceVertIndex = 0; + unsigned int nextShape = 1; + int lastMaterial = -1; + unsigned int meshIndex = 0; + + // count meshes + unsigned int nextShapeEnd = objAttributes.num_face_num_verts; + + // see how many verts till the next shape - char currentDir[1024] = { 0 }; - strcpy(currentDir, GetWorkingDirectory()); // Save current working directory - const char *workingDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness - if (CHDIR(workingDir) != 0) + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + + // walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + if (faceVertIndex >= nextShapeEnd) { - TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + // try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + meshIndex++; + } + else if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) + { + meshIndex++;// if this is a new material, we need to allocate a new mesh } - unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; - int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileText, dataSize, flags); + lastMaterial = objAttributes.material_ids[faceId]; + faceVertIndex += objAttributes.face_num_verts[faceId]; + } - if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName); - else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes/%i materials", fileName, meshCount, materialCount); + // allocate the base meshes and materials + model.meshCount = meshIndex + 1; + model.meshes = (Mesh*)MemAlloc(sizeof(Mesh) * model.meshCount); - // WARNING: We are not splitting meshes by materials (previous implementation) - // Depending on the provided OBJ that was not the best option and it just crashed - // so, implementation was simplified to prioritize parsed meshes - model.meshCount = meshCount; + if (objMaterialCount > 0) + { + model.materialCount = objMaterialCount; + model.materials = (Material*)MemAlloc(sizeof(Material) * objMaterialCount); + } + else // we must allocate at least one material + { + model.materialCount = 1; + model.materials = (Material*)MemAlloc(sizeof(Material) * 1); + } + + model.meshMaterial = (int*)MemAlloc(sizeof(int) * model.meshCount); + + // see how many verts are in each mesh + unsigned int* localMeshVertexCounts = (unsigned int*)MemAlloc(sizeof(unsigned int) * model.meshCount); + + faceVertIndex = 0; + nextShapeEnd = objAttributes.num_face_num_verts; + lastMaterial = -1; + meshIndex = 0; + unsigned int localMeshVertexCount = 0; - // Set number of materials available - // NOTE: There could be more materials available than meshes but it will be resolved at - // model.meshMaterial, just assigning the right material to corresponding mesh - model.materialCount = materialCount; - if (model.materialCount == 0) + nextShape = 1; + if (objShapeCount > 1) + nextShapeEnd = objShapes[nextShape].face_offset; + + // walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + bool newMesh = false; // do we need a new mesh? + if (faceVertIndex >= nextShapeEnd) { - model.materialCount = 1; - TRACELOG(LOG_INFO, "MODEL: No materials provided, setting one default material for all meshes"); + // try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + + newMesh = true; } - else if (model.materialCount > 1 && model.meshCount > 1) + else if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) { - // TEMP warning about multiple materials, to be removed when proper splitting code is implemented - // any obj with multiple materials will need to have it's materials assigned by the user in code to work at this time - TRACELOG(LOG_INFO, "MODEL: OBJ has multiple materials, manual material assignment will be required."); + newMesh = true; } - // Init model meshes and materials - model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); - model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); // Material index assigned to each mesh - model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + lastMaterial = objAttributes.material_ids[faceId]; - // Process each provided mesh - for (int i = 0; i < model.meshCount; i++) + if (newMesh) { - // WARNING: We need to calculate the mesh triangles manually using meshes[i].face_offset - // because in case of triangulated quads, meshes[i].length actually report quads, - // despite the triangulation that is efectively considered on attrib.num_faces - unsigned int tris = 0; - if (i == model.meshCount - 1) tris = attrib.num_faces - meshes[i].face_offset; - else tris = meshes[i + 1].face_offset; - - model.meshes[i].vertexCount = tris*3; - model.meshes[i].triangleCount = tris; // Face count (triangulated) - model.meshes[i].vertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - model.meshes[i].texcoords = (float *)RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); - model.meshes[i].normals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - model.meshMaterial[i] = 0; // By default, assign material 0 to each mesh - - // Process all mesh faces - for (unsigned int face = 0, f = meshes[i].face_offset, v = 0, vt = 0, vn = 0; face < tris; face++, f++, v += 3, vt += 3, vn += 3) - { - // Get indices for the face - tinyobj_vertex_index_t idx0 = attrib.faces[f*3 + 0]; - tinyobj_vertex_index_t idx1 = attrib.faces[f*3 + 1]; - tinyobj_vertex_index_t idx2 = attrib.faces[f*3 + 2]; + localMeshVertexCounts[meshIndex] = localMeshVertexCount; - // Fill vertices buffer (float) using vertex index of the face - for (int n = 0; n < 3; n++) { model.meshes[i].vertices[v*3 + n] = attrib.vertices[idx0.v_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].vertices[(v + 1)*3 + n] = attrib.vertices[idx1.v_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].vertices[(v + 2)*3 + n] = attrib.vertices[idx2.v_idx*3 + n]; } + localMeshVertexCount = 0; + meshIndex++; + } - if (attrib.num_texcoords > 0) - { - // Fill texcoords buffer (float) using vertex index of the face - // NOTE: Y-coordinate must be flipped upside-down - model.meshes[i].texcoords[vt*2 + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; - model.meshes[i].texcoords[vt*2 + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; + faceVertIndex += objAttributes.face_num_verts[faceId]; + localMeshVertexCount += objAttributes.face_num_verts[faceId]; + } + localMeshVertexCounts[meshIndex] = localMeshVertexCount; - model.meshes[i].texcoords[(vt + 1)*2 + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; - model.meshes[i].texcoords[(vt + 1)*2 + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; + for (int i = 0; i < model.meshCount; i++) + { + // allocate the buffers for each mesh + unsigned int vertexCount = localMeshVertexCounts[i]; - model.meshes[i].texcoords[(vt + 2)*2 + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; - model.meshes[i].texcoords[(vt + 2)*2 + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; - } + model.meshes[i].vertexCount = vertexCount; + model.meshes[i].triangleCount = vertexCount / 3; - if (attrib.num_normals > 0) - { - // Fill normals buffer (float) using vertex index of the face - for (int n = 0; n < 3; n++) { model.meshes[i].normals[vn*3 + n] = attrib.normals[idx0.vn_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].normals[(vn + 1)*3 + n] = attrib.normals[idx1.vn_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].normals[(vn + 2)*3 + n] = attrib.normals[idx2.vn_idx*3 + n]; } - } - } + model.meshes[i].vertices = (float*)MemAlloc(sizeof(float) * vertexCount * 3); + model.meshes[i].normals = (float*)MemAlloc(sizeof(float) * vertexCount * 3); + model.meshes[i].texcoords = (float*)MemAlloc(sizeof(float) * vertexCount * 2); + model.meshes[i].colors = (unsigned char*)MemAlloc(sizeof(unsigned char) * vertexCount * 4); + } + + MemFree(localMeshVertexCounts); + localMeshVertexCounts = NULL; + + // fill meshes + faceVertIndex = 0; + + nextShapeEnd = objAttributes.num_face_num_verts; + + // see how many verts till the next shape + nextShape = 1; + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + lastMaterial = -1; + meshIndex = 0; + localMeshVertexCount = 0; + + // walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + bool newMesh = false; // do we need a new mesh? + if (faceVertIndex >= nextShapeEnd) + { + // try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + newMesh = true; } + // if this is a new material, we need to allocate a new mesh + if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) newMesh = true; + lastMaterial = objAttributes.material_ids[faceId];; - // Init model materials - if (materialCount > 0) ProcessMaterialsOBJ(model.materials, materials, materialCount); - else model.materials[0] = LoadMaterialDefault(); // Set default material for the mesh + if (newMesh) + { + localMeshVertexCount = 0; + meshIndex++; + } - tinyobj_attrib_free(&attrib); - tinyobj_shapes_free(meshes, model.meshCount); - tinyobj_materials_free(materials, materialCount); + int matId = 0; + if (lastMaterial >= 0 && lastMaterial < (int)objMaterialCount) + matId = lastMaterial; - UnloadFileText(fileText); + model.meshMaterial[meshIndex] = matId; - // Restore current working directory - if (CHDIR(currentDir) != 0) + for (int f = 0; f < objAttributes.face_num_verts[faceId]; f++) { - TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + int vertIndex = objAttributes.faces[faceVertIndex].v_idx; + int normalIndex = objAttributes.faces[faceVertIndex].vn_idx; + int texcordIndex = objAttributes.faces[faceVertIndex].vt_idx; + + for (int i = 0; i < 3; i++) + model.meshes[meshIndex].vertices[localMeshVertexCount * 3 + i] = objAttributes.vertices[vertIndex * 3 + i]; + + for (int i = 0; i < 3; i++) + model.meshes[meshIndex].normals[localMeshVertexCount * 3 + i] = objAttributes.normals[normalIndex * 3 + i]; + + for (int i = 0; i < 2; i++) + model.meshes[meshIndex].texcoords[localMeshVertexCount * 2 + i] = objAttributes.texcoords[texcordIndex * 2 + i]; + + model.meshes[meshIndex].texcoords[localMeshVertexCount * 2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount * 2 + 1]; + + for (int i = 0; i < 4; i++) + model.meshes[meshIndex].colors[localMeshVertexCount * 4 + i] = 255; + + faceVertIndex++; + localMeshVertexCount++; } } + if (objMaterialCount > 0) ProcessMaterialsOBJ(model.materials, objMaterials, objMaterialCount); + else model.materials[0] = LoadMaterialDefault(); // Set default material for the mesh + + tinyobj_attrib_free(&objAttributes); + tinyobj_shapes_free(objShapes, objShapeCount); + tinyobj_materials_free(objMaterials, objMaterialCount); + + for (int i = 0; i < model.meshCount; i++) + UploadMesh(model.meshes + i, true); + + // Restore current working directory + if (CHDIR(currentDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + } + return model; } #endif From 8ea5db3ec43d1fa702b29df9066307d0d337b229 Mon Sep 17 00:00:00 2001 From: Hesham Abourgheba Date: Sun, 25 Aug 2024 19:51:08 +0300 Subject: [PATCH 062/107] fix(rcore/android): Allow main() to return it its caller on configuration changes. (#4288) --- src/platforms/rcore_android.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 7a2162025e90..55706c591127 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -694,11 +694,12 @@ void PollInputEvents(void) // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); - // NOTE: Never close window, native activity is controlled by the system! + // NOTE: Allow closing the window in case a configuration change happened. + // The android_main function should be allowed to return to its caller in order for the + // Android OS to relaunch the activity. if (platform.app->destroyRequested != 0) { - //CORE.Window.shouldClose = true; - //ANativeActivity_finish(platform.app->activity); + CORE.Window.shouldClose = true; } } } @@ -781,7 +782,7 @@ int InitPlatform(void) // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); - // NOTE: Never close window, native activity is controlled by the system! + // NOTE: It's highly likely destroyRequested will never be non-zero at the start of the activity lifecycle. //if (platform.app->destroyRequested != 0) CORE.Window.shouldClose = true; } } @@ -812,6 +813,12 @@ void ClosePlatform(void) eglTerminate(platform.device); platform.device = EGL_NO_DISPLAY; } + + // NOTE: Reset global state in case the activity is being relaunched. + if (platform.app->destroyRequested != 0) { + CORE = (CoreData){0}; + platform = (PlatformData){0}; + } } // Initialize display device and framebuffer From 5f49ec3d6448502d798d4d404e08e354ec48012a Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:35:35 +0300 Subject: [PATCH 063/107] Fix missing equal sign (#4294) I just noticed there is a missing equal sign. This PR fixes this. --- src/platforms/rcore_drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 291fd93c7446..43dc19c71e77 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -206,7 +206,7 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = { [BTN_TL] = GAMEPAD_BUTTON_LEFT_TRIGGER_1, [BTN_TL2] = GAMEPAD_BUTTON_LEFT_TRIGGER_2, [BTN_TR] = GAMEPAD_BUTTON_RIGHT_TRIGGER_1, - [BTN_TR2] GAMEPAD_BUTTON_RIGHT_TRIGGER_2, + [BTN_TR2] = GAMEPAD_BUTTON_RIGHT_TRIGGER_2, [BTN_SELECT] = GAMEPAD_BUTTON_MIDDLE_LEFT, [BTN_MODE] = GAMEPAD_BUTTON_MIDDLE, [BTN_START] = GAMEPAD_BUTTON_MIDDLE_RIGHT, From 59b44a4908e830bca755af3d83d25a64f6d54bd2 Mon Sep 17 00:00:00 2001 From: carverdamien Date: Mon, 2 Sep 2024 10:01:30 +0200 Subject: [PATCH 064/107] Add uConsole mapping (#4297) --- src/external/glfw/src/mappings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/external/glfw/src/mappings.h b/src/external/glfw/src/mappings.h index 270fa4cd87cf..7b0f35a26d79 100644 --- a/src/external/glfw/src/mappings.h +++ b/src/external/glfw/src/mappings.h @@ -996,6 +996,7 @@ const char* _glfwDefaultMappings[] = "03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,", "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000af1e00002400000010010000,Clockwork Pi DevTerm,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b9,x:b3,y:b0,platform:Linux,", #endif // GLFW_BUILD_LINUX_JOYSTICK }; From 42022c3531e4ca6739fd0ecc760c882eabf4493a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jask=C3=B3lski?= Date: Tue, 3 Sep 2024 14:38:12 +0200 Subject: [PATCH 065/107] fix: In certain cases the connector status is reported UNKNOWN, should be conisdered as CONNECTED (#4305) Co-authored-by: Michal Jaskolski --- src/platforms/rcore_drm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 43dc19c71e77..ee2875838b50 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -763,8 +763,10 @@ int InitPlatform(void) drmModeConnector *con = drmModeGetConnector(platform.fd, res->connectors[i]); TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes); - - if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id)) + + // In certain cases the status of the conneciton is reported as UKNOWN, but it is still connected. + // This might be a hardware or software limitation like on Raspberry Pi Zero with composite output. + if (((con->connection == DRM_MODE_CONNECTED) || (con->connection == DRM_MODE_UNKNOWNCONNECTION)) && (con->encoder_id)) { TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected"); platform.connector = con; From ed61bdb568046e80e6fd30485375433c4231fd46 Mon Sep 17 00:00:00 2001 From: Chris Warren-Smith Date: Wed, 4 Sep 2024 20:37:00 +0930 Subject: [PATCH 066/107] Fix seg fault with long comment lines (#4306) #4304 --- parser/raylib_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/raylib_parser.c b/parser/raylib_parser.c index cfb0133c3d2e..83991c003c75 100644 --- a/parser/raylib_parser.c +++ b/parser/raylib_parser.c @@ -139,7 +139,7 @@ typedef struct EnumInfo { // Function info data typedef struct FunctionInfo { char name[64]; // Function name - char desc[128]; // Function description (comment at the end) + char desc[256]; // Function description (comment at the end) char retType[32]; // Return value type int paramCount; // Number of function parameters char paramType[MAX_FUNCTION_PARAMETERS][32]; // Parameters type From 10b01ba7c2139b9203045bb56aa8403ca810cc6b Mon Sep 17 00:00:00 2001 From: masnm <49545838+masnm@users.noreply.github.com> Date: Sat, 7 Sep 2024 03:16:17 +0600 Subject: [PATCH 067/107] Change implicit conversion to explicit conversion to remove warning (#4308) --- src/external/par_shapes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/external/par_shapes.h b/src/external/par_shapes.h index 994a605a96d2..d5309137b704 100644 --- a/src/external/par_shapes.h +++ b/src/external/par_shapes.h @@ -1130,7 +1130,7 @@ static par_shapes__rule* par_shapes__pick_rule(const char* name, total += rule->weight; } } - float r = (float) rand() / RAND_MAX; + float r = (float) rand() / (float) RAND_MAX; float t = 0; for (int i = 0; i < nrules; i++) { rule = rules + i; From 0656440e38208e4d57201706520eb0571fb13bd9 Mon Sep 17 00:00:00 2001 From: masnm <49545838+masnm@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:24:53 +0600 Subject: [PATCH 068/107] fix vld1q_f16 undeclared in arm on stb_image_resize2.h v2.10 (#4309) --- src/external/stb_image_resize2.h | 1657 +++++++++++++++++------------- 1 file changed, 963 insertions(+), 694 deletions(-) diff --git a/src/external/stb_image_resize2.h b/src/external/stb_image_resize2.h index e0c42824633a..ae8d730bf529 100644 --- a/src/external/stb_image_resize2.h +++ b/src/external/stb_image_resize2.h @@ -1,9 +1,9 @@ -/* stb_image_resize2 - v2.01 - public domain image resizing - - by Jeff Roberts (v2) and Jorge L Rodriguez +/* stb_image_resize2 - v2.10 - public domain image resizing + + by Jeff Roberts (v2) and Jorge L Rodriguez http://github.com/nothings/stb - Can be threaded with the extended API. SSE2, AVX, Neon and WASM SIMD support. Only + Can be threaded with the extended API. SSE2, AVX, Neon and WASM SIMD support. Only scaling and translation is supported, no rotations or shears. COMPILING & LINKING @@ -67,60 +67,60 @@ ADDITIONAL DOCUMENTATION MEMORY ALLOCATION - By default, we use malloc and free for memory allocation. To override the + By default, we use malloc and free for memory allocation. To override the memory allocation, before the implementation #include, add a: #define STBIR_MALLOC(size,user_data) ... #define STBIR_FREE(ptr,user_data) ... - Each resize makes exactly one call to malloc/free (unless you use the + Each resize makes exactly one call to malloc/free (unless you use the extended API where you can do one allocation for many resizes). Under address sanitizer, we do separate allocations to find overread/writes. PERFORMANCE This library was written with an emphasis on performance. When testing - stb_image_resize with RGBA, the fastest mode is STBIR_4CHANNEL with + stb_image_resize with RGBA, the fastest mode is STBIR_4CHANNEL with STBIR_TYPE_UINT8 pixels and CLAMPed edges (which is what many other resize - libs do by default). Also, make sure SIMD is turned on of course (default + libs do by default). Also, make sure SIMD is turned on of course (default for 64-bit targets). Avoid WRAP edge mode if you want the fastest speed. This library also comes with profiling built-in. If you define STBIR_PROFILE, - you can use the advanced API and get low-level profiling information by + you can use the advanced API and get low-level profiling information by calling stbir_resize_extended_profile_info() or stbir_resize_split_profile_info() after a resize. SIMD - Most of the routines have optimized SSE2, AVX, NEON and WASM versions. + Most of the routines have optimized SSE2, AVX, NEON and WASM versions. - On Microsoft compilers, we automatically turn on SIMD for 64-bit x64 and - ARM; for 32-bit x86 and ARM, you select SIMD mode by defining STBIR_SSE2 or + On Microsoft compilers, we automatically turn on SIMD for 64-bit x64 and + ARM; for 32-bit x86 and ARM, you select SIMD mode by defining STBIR_SSE2 or STBIR_NEON. For AVX and AVX2, we auto-select it by detecting the /arch:AVX - or /arch:AVX2 switches. You can also always manually turn SSE2, AVX or AVX2 + or /arch:AVX2 switches. You can also always manually turn SSE2, AVX or AVX2 support on by defining STBIR_SSE2, STBIR_AVX or STBIR_AVX2. On Linux, SSE2 and Neon is on by default for 64-bit x64 or ARM64. For 32-bit, we select x86 SIMD mode by whether you have -msse2, -mavx or -mavx2 enabled on the command line. For 32-bit ARM, you must pass -mfpu=neon-vfpv4 for both - clang and GCC, but GCC also requires an additional -mfp16-format=ieee to + clang and GCC, but GCC also requires an additional -mfp16-format=ieee to automatically enable NEON. On x86 platforms, you can also define STBIR_FP16C to turn on FP16C instructions for converting back and forth to half-floats. This is autoselected when we - are using AVX2. Clang and GCC also require the -mf16c switch. ARM always uses - the built-in half float hardware NEON instructions. + are using AVX2. Clang and GCC also require the -mf16c switch. ARM always uses + the built-in half float hardware NEON instructions. - You can also tell us to use multiply-add instructions with STBIR_USE_FMA. + You can also tell us to use multiply-add instructions with STBIR_USE_FMA. Because x86 doesn't always have fma, we turn it off by default to maintain determinism across all platforms. If you don't care about non-FMA determinism - and are willing to restrict yourself to more recent x86 CPUs (around the AVX + and are willing to restrict yourself to more recent x86 CPUs (around the AVX timeframe), then fma will give you around a 15% speedup. You can force off SIMD in all cases by defining STBIR_NO_SIMD. You can turn off AVX or AVX2 specifically with STBIR_NO_AVX or STBIR_NO_AVX2. AVX is 10% to 40% faster, and AVX2 is generally another 12%. - + ALPHA CHANNEL - Most of the resizing functions provide the ability to control how the alpha + Most of the resizing functions provide the ability to control how the alpha channel of an image is processed. When alpha represents transparency, it is important that when combining @@ -167,33 +167,33 @@ stb_image_resize expects case #1 by default, applying alpha weighting to images, expecting the input images to be unpremultiplied. This is what the - COLOR+ALPHA buffer types tell the resizer to do. + COLOR+ALPHA buffer types tell the resizer to do. - When you use the pixel layouts STBIR_RGBA, STBIR_BGRA, STBIR_ARGB, - STBIR_ABGR, STBIR_RX, or STBIR_XR you are telling us that the pixels are - non-premultiplied. In these cases, the resizer will alpha weight the colors - (effectively creating the premultiplied image), do the filtering, and then + When you use the pixel layouts STBIR_RGBA, STBIR_BGRA, STBIR_ARGB, + STBIR_ABGR, STBIR_RX, or STBIR_XR you are telling us that the pixels are + non-premultiplied. In these cases, the resizer will alpha weight the colors + (effectively creating the premultiplied image), do the filtering, and then convert back to non-premult on exit. When you use the pixel layouts STBIR_RGBA_PM, STBIR_RGBA_PM, STBIR_RGBA_PM, - STBIR_RGBA_PM, STBIR_RX_PM or STBIR_XR_PM, you are telling that the pixels - ARE premultiplied. In this case, the resizer doesn't have to do the - premultipling - it can filter directly on the input. This about twice as - fast as the non-premultiplied case, so it's the right option if your data is + STBIR_RGBA_PM, STBIR_RX_PM or STBIR_XR_PM, you are telling that the pixels + ARE premultiplied. In this case, the resizer doesn't have to do the + premultipling - it can filter directly on the input. This about twice as + fast as the non-premultiplied case, so it's the right option if your data is already setup correctly. - When you use the pixel layout STBIR_4CHANNEL or STBIR_2CHANNEL, you are - telling us that there is no channel that represents transparency; it may be - RGB and some unrelated fourth channel that has been stored in the alpha - channel, but it is actually not alpha. No special processing will be - performed. + When you use the pixel layout STBIR_4CHANNEL or STBIR_2CHANNEL, you are + telling us that there is no channel that represents transparency; it may be + RGB and some unrelated fourth channel that has been stored in the alpha + channel, but it is actually not alpha. No special processing will be + performed. - The difference between the generic 4 or 2 channel layouts, and the + The difference between the generic 4 or 2 channel layouts, and the specialized _PM versions is with the _PM versions you are telling us that the data *is* alpha, just don't premultiply it. That's important when using SRGB pixel formats, we need to know where the alpha is, because it is converted linearly (rather than with the SRGB converters). - + Because alpha weighting produces the same effect as premultiplying, you even have the option with non-premultiplied inputs to let the resizer produce a premultiplied output. Because the intially computed alpha-weighted @@ -201,10 +201,10 @@ than the normal path which un-premultiplies the output image as a final step. Finally, when converting both in and out of non-premulitplied space (for - example, when using STBIR_RGBA), we go to somewhat heroic measures to - ensure that areas with zero alpha value pixels get something reasonable - in the RGB values. If you don't care about the RGB values of zero alpha - pixels, you can call the stbir_set_non_pm_alpha_speed_over_quality() + example, when using STBIR_RGBA), we go to somewhat heroic measures to + ensure that areas with zero alpha value pixels get something reasonable + in the RGB values. If you don't care about the RGB values of zero alpha + pixels, you can call the stbir_set_non_pm_alpha_speed_over_quality() function - this runs a premultiplied resize about 25% faster. That said, when you really care about speed, using premultiplied pixels for both in and out (STBIR_RGBA_PM, etc) much faster than both of these premultiplied @@ -218,38 +218,38 @@ layouts with the same number of channels. DETERMINISM - We commit to being deterministic (from x64 to ARM to scalar to SIMD, etc). - This requires compiling with fast-math off (using at least /fp:precise). + We commit to being deterministic (from x64 to ARM to scalar to SIMD, etc). + This requires compiling with fast-math off (using at least /fp:precise). Also, you must turn off fp-contracting (which turns mult+adds into fmas)! - We attempt to do this with pragmas, but with Clang, you usually want to add + We attempt to do this with pragmas, but with Clang, you usually want to add -ffp-contract=off to the command line as well. - For 32-bit x86, you must use SSE and SSE2 codegen for determinism. That is, - if the scalar x87 unit gets used at all, we immediately lose determinism. + For 32-bit x86, you must use SSE and SSE2 codegen for determinism. That is, + if the scalar x87 unit gets used at all, we immediately lose determinism. On Microsoft Visual Studio 2008 and earlier, from what we can tell there is - no way to be deterministic in 32-bit x86 (some x87 always leaks in, even - with fp:strict). On 32-bit x86 GCC, determinism requires both -msse2 and + no way to be deterministic in 32-bit x86 (some x87 always leaks in, even + with fp:strict). On 32-bit x86 GCC, determinism requires both -msse2 and -fpmath=sse. Note that we will not be deterministic with float data containing NaNs - - the NaNs will propagate differently on different SIMD and platforms. + the NaNs will propagate differently on different SIMD and platforms. - If you turn on STBIR_USE_FMA, then we will be deterministic with other - fma targets, but we will differ from non-fma targets (this is unavoidable, - because a fma isn't simply an add with a mult - it also introduces a - rounding difference compared to non-fma instruction sequences. + If you turn on STBIR_USE_FMA, then we will be deterministic with other + fma targets, but we will differ from non-fma targets (this is unavoidable, + because a fma isn't simply an add with a mult - it also introduces a + rounding difference compared to non-fma instruction sequences. FLOAT PIXEL FORMAT RANGE - Any range of values can be used for the non-alpha float data that you pass - in (0 to 1, -1 to 1, whatever). However, if you are inputting float values - but *outputting* bytes or shorts, you must use a range of 0 to 1 so that we - scale back properly. The alpha channel must also be 0 to 1 for any format - that does premultiplication prior to resizing. + Any range of values can be used for the non-alpha float data that you pass + in (0 to 1, -1 to 1, whatever). However, if you are inputting float values + but *outputting* bytes or shorts, you must use a range of 0 to 1 so that we + scale back properly. The alpha channel must also be 0 to 1 for any format + that does premultiplication prior to resizing. - Note also that with float output, using filters with negative lobes, the - output filtered values might go slightly out of range. You can define - STBIR_FLOAT_LOW_CLAMP and/or STBIR_FLOAT_HIGH_CLAMP to specify the range - to clamp to on output, if that's important. + Note also that with float output, using filters with negative lobes, the + output filtered values might go slightly out of range. You can define + STBIR_FLOAT_LOW_CLAMP and/or STBIR_FLOAT_HIGH_CLAMP to specify the range + to clamp to on output, if that's important. MAX/MIN SCALE FACTORS The input pixel resolutions are in integers, and we do the internal pointer @@ -263,13 +263,13 @@ buffers). FLIPPED IMAGES - Stride is just the delta from one scanline to the next. This means you can - use a negative stride to handle inverted images (point to the final + Stride is just the delta from one scanline to the next. This means you can + use a negative stride to handle inverted images (point to the final scanline and use a negative stride). You can invert the input or output, using negative strides. DEFAULT FILTERS - For functions which don't provide explicit control over what filters to + For functions which don't provide explicit control over what filters to use, you can change the compile-time defaults with: #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something @@ -278,18 +278,18 @@ See stbir_filter in the header-file section for the list of filters. NEW FILTERS - A number of 1D filter kernels are supplied. For a list of supported - filters, see the stbir_filter enum. You can install your own filters by + A number of 1D filter kernels are supplied. For a list of supported + filters, see the stbir_filter enum. You can install your own filters by using the stbir_set_filter_callbacks function. PROGRESS - For interactive use with slow resize operations, you can use the the - scanline callbacks in the extended API. It would have to be a *very* large + For interactive use with slow resize operations, you can use the the + scanline callbacks in the extended API. It would have to be a *very* large image resample to need progress though - we're very fast. CEIL and FLOOR - In scalar mode, the only functions we use from math.h are ceilf and floorf, - but if you have your own versions, you can define the STBIR_CEILF(v) and + In scalar mode, the only functions we use from math.h are ceilf and floorf, + but if you have your own versions, you can define the STBIR_CEILF(v) and STBIR_FLOORF(v) macros and we'll use them instead. In SIMD, we just use our own versions. @@ -304,7 +304,7 @@ * For SIMD encode and decode scanline routines, do any pre-aligning for bad input/output buffer alignments and pitch? * For very wide scanlines, we should we do vertical strips to stay within - L2 cache. Maybe do chunks of 1K pixels at a time. There would be + L2 cache. Maybe do chunks of 1K pixels at a time. There would be some pixel reconversion, but probably dwarfed by things falling out of cache. Probably also something possible with alternating between scattering and gathering at high resize scales? @@ -316,21 +316,38 @@ the pivot cost and the extra memory touches). Need to buffer the whole image so have to balance memory use. * Most of our code is internally function pointers, should we compile - all the SIMD stuff always and dynamically dispatch? + all the SIMD stuff always and dynamically dispatch? CONTRIBUTORS Jeff Roberts: 2.0 implementation, optimizations, SIMD - Martins Mozeiko: NEON simd, WASM simd, clang and GCC whisperer. + Martins Mozeiko: NEON simd, WASM simd, clang and GCC whisperer Fabian Giesen: half float and srgb converters Sean Barrett: API design, optimizations Jorge L Rodriguez: Original 1.0 implementation - Aras Pranckevicius: bugfixes for 1.0 + Aras Pranckevicius: bugfixes Nathan Reed: warning fixes for 1.0 REVISIONS - 2.00 (2022-02-20) mostly new source: new api, optimizations, simd, vertical-first, etc - (2x-5x faster without simd, 4x-12x faster with simd) - (in some cases, 20x to 40x faster - resizing to very small for example) + 2.10 (2024-07-27) fix the defines GCC and mingw for loop unroll control, + fix MSVC 32-bit arm half float routines. + 2.09 (2024-06-19) fix the defines for 32-bit ARM GCC builds (was selecting + hardware half floats). + 2.08 (2024-06-10) fix for RGB->BGR three channel flips and add SIMD (thanks + to Ryan Salsbury), fix for sub-rect resizes, use the + pragmas to control unrolling when they are available. + 2.07 (2024-05-24) fix for slow final split during threaded conversions of very + wide scanlines when downsampling (caused by extra input + converting), fix for wide scanline resamples with many + splits (int overflow), fix GCC warning. + 2.06 (2024-02-10) fix for identical width/height 3x or more down-scaling + undersampling a single row on rare resize ratios (about 1%). + 2.05 (2024-02-07) fix for 2 pixel to 1 pixel resizes with wrap (thanks Aras), + fix for output callback (thanks Julien Koenen). + 2.04 (2023-11-17) fix for rare AVX bug, shadowed symbol (thanks Nikola Smiljanic). + 2.03 (2023-11-01) ASAN and TSAN warnings fixed, minor tweaks. + 2.00 (2023-10-10) mostly new source: new api, optimizations, simd, vertical-first, etc + 2x-5x faster without simd, 4x-12x faster with simd, + in some cases, 20x to 40x faster esp resizing large to very small. 0.96 (2019-03-04) fixed warnings 0.95 (2017-07-23) fixed warnings 0.94 (2017-03-18) fixed warnings @@ -368,7 +385,7 @@ typedef uint64_t stbir_uint64; #define STBIR_SSE #endif #endif -#endif +#endif #if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(_M_AMD64) || defined(__SSE2__) || defined(STBIR_SSE) || defined(STBIR_SSE2) #ifndef STBIR_SSE2 @@ -383,7 +400,7 @@ typedef uint64_t stbir_uint64; #endif #if defined(__AVX2__) || defined(STBIR_AVX2) #ifndef STBIR_NO_AVX2 - #ifndef STBIR_AVX2 + #ifndef STBIR_AVX2 #define STBIR_AVX2 #endif #if defined( _MSC_VER ) && !defined(__clang__) @@ -400,15 +417,15 @@ typedef uint64_t stbir_uint64; #endif #endif -#if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(_M_ARM) || (__ARM_NEON_FP & 4) != 0 && __ARM_FP16_FORMAT_IEEE != 0 +#if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || ((__ARM_NEON_FP & 4) != 0) || defined(__ARM_NEON__) #ifndef STBIR_NEON #define STBIR_NEON #endif #endif -#if defined(_M_ARM) +#if defined(_M_ARM) || defined(__arm__) #ifdef STBIR_USE_FMA -#undef STBIR_USE_FMA // no FMA for 32-bit arm on MSVC +#undef STBIR_USE_FMA // no FMA for 32-bit arm on MSVC #endif #endif @@ -435,7 +452,7 @@ typedef uint64_t stbir_uint64; // // Easy-to-use API: // -// * stride is the offset between successive rows of image data +// * stride is the offset between successive rows of image data // in memory, in bytes. specify 0 for packed continuously in memory // * colorspace is linear or sRGB as specified by function name // * Uses the default filters @@ -448,27 +465,35 @@ typedef uint64_t stbir_uint64; // order of channels // whether color is premultiplied by alpha // for back compatibility, you can cast the old channel count to an stbir_pixel_layout -typedef enum +typedef enum { - STBIR_BGR = 0, // 3-chan, with order specified (for channel flipping) - STBIR_1CHANNEL = 1, + STBIR_1CHANNEL = 1, STBIR_2CHANNEL = 2, - STBIR_RGB = 3, // 3-chan, with order specified (for channel flipping) - STBIR_RGBA = 4, // alpha formats, alpha is NOT premultiplied into color channels - + STBIR_RGB = 3, // 3-chan, with order specified (for channel flipping) + STBIR_BGR = 0, // 3-chan, with order specified (for channel flipping) STBIR_4CHANNEL = 5, + + STBIR_RGBA = 4, // alpha formats, where alpha is NOT premultiplied into color channels STBIR_BGRA = 6, STBIR_ARGB = 7, STBIR_ABGR = 8, STBIR_RA = 9, STBIR_AR = 10, - STBIR_RGBA_PM = 11, // alpha formats, alpha is premultiplied into color channels + STBIR_RGBA_PM = 11, // alpha formats, where alpha is premultiplied into color channels STBIR_BGRA_PM = 12, STBIR_ARGB_PM = 13, STBIR_ABGR_PM = 14, STBIR_RA_PM = 15, STBIR_AR_PM = 16, + + STBIR_RGBA_NO_AW = 11, // alpha formats, where NO alpha weighting is applied at all! + STBIR_BGRA_NO_AW = 12, // these are just synonyms for the _PM flags (which also do + STBIR_ARGB_NO_AW = 13, // no alpha weighting). These names just make it more clear + STBIR_ABGR_NO_AW = 14, // for some folks). + STBIR_RA_NO_AW = 15, + STBIR_AR_NO_AW = 16, + } stbir_pixel_layout; //=============================================================== @@ -549,8 +574,8 @@ STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int inpu // * Separate input and output data types // * Can specify regions with subpixel correctness // * Can specify alpha flags -// * Can specify a memory callback -// * Can specify a callback data type for pixel input and output +// * Can specify a memory callback +// * Can specify a callback data type for pixel input and output // * Can be threaded for a single resize // * Can be used to resize many frames without recalculating the sampler info // @@ -577,7 +602,7 @@ typedef float stbir__kernel_callback( float x, float scale, void * user_data ); typedef float stbir__support_callback( float scale, void * user_data ); // internal structure with precomputed scaling -typedef struct stbir__info stbir__info; +typedef struct stbir__info stbir__info; typedef struct STBIR_RESIZE // use the stbir_resize_init and stbir_override functions to set these values for future compatibility { @@ -604,7 +629,7 @@ typedef struct STBIR_RESIZE // use the stbir_resize_init and stbir_override fun stbir_edge horizontal_edge, vertical_edge; stbir__kernel_callback * horizontal_filter_kernel; stbir__support_callback * horizontal_filter_support; stbir__kernel_callback * vertical_filter_kernel; stbir__support_callback * vertical_filter_support; - stbir__info * samplers; + stbir__info * samplers; } STBIR_RESIZE; // extended complexity api @@ -620,7 +645,7 @@ STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, // You can update these parameters any time after resize_init and there is no cost //-------------------------------- -STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ); +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ); STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ); // no callbacks by default STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ); // pass back STBIR_RESIZE* by default STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ); @@ -636,7 +661,7 @@ STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ); // CLAMP by default STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ); // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default -STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ); +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ); STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets both sub-regions (full regions by default) STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ); // sets input sub-region (full region by default) @@ -658,7 +683,7 @@ STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, i //-------------------------------- // This builds the samplers and does one allocation -STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ); +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ); // You MUST call this, if you call stbir_build_samplers or stbir_build_samplers_with_splits STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ); @@ -681,7 +706,7 @@ STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ); // It returns the number of splits (threads) that you can call it with. /// It might be less if the image resize can't be split up that many ways. -STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int try_splits ); +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int try_splits ); // This function does a split of the resizing (you call this fuction for each // split, on multiple threads). A split is a piece of the output resize pixel space. @@ -691,10 +716,10 @@ STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int try_sp // Usually, you will always call stbir_resize_split with split_start as the thread_index // and "1" for the split_count. // But, if you have a weird situation where you MIGHT want 8 threads, but sometimes -// only 4 threads, you can use 0,2,4,6 for the split_start's and use "2" for the +// only 4 threads, you can use 0,2,4,6 for the split_start's and use "2" for the // split_count each time to turn in into a 4 thread resize. (This is unusual). -STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ); +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ); //=============================================================== @@ -705,10 +730,10 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start // The input callback is super flexible - it calls you with the input address // (based on the stride and base pointer), it gives you an optional_output // pointer that you can fill, or you can just return your own pointer into -// your own data. +// your own data. // -// You can also do conversion from non-supported data types if necessary - in -// this case, you ignore the input_ptr and just use the x and y parameters to +// You can also do conversion from non-supported data types if necessary - in +// this case, you ignore the input_ptr and just use the x and y parameters to // calculate your own input_ptr based on the size of each non-supported pixel. // (Something like the third example below.) // @@ -722,14 +747,14 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start // return input_ptr; // use buffer from call // } // -// Next example, copying: (copy from some other buffer or stream): +// Next example, copying: (copy from some other buffer or stream): // void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) // { // CopyOrStreamData( optional_output, other_data_src, num_pixels * pixel_width_in_bytes ); // return optional_output; // return the optional buffer that we filled // } // -// Third example, input another buffer without copying: (zero-copy from other buffer): +// Third example, input another buffer without copying: (zero-copy from other buffer): // void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) // { // void * pixels = ( (char*) other_image_base ) + ( y * other_image_stride ) + ( x * other_pixel_width_in_bytes ); @@ -758,7 +783,7 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start #ifdef STBIR_PROFILE -typedef struct STBIR_PROFILE_INFO +typedef struct STBIR_PROFILE_INFO { stbir_uint64 total_clocks; @@ -766,7 +791,7 @@ typedef struct STBIR_PROFILE_INFO // there are "resize_count" number of zones stbir_uint64 clocks[ 8 ]; char const ** descriptions; - + // count of clocks and descriptions stbir_uint32 count; } STBIR_PROFILE_INFO; @@ -865,15 +890,15 @@ STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * out_info, ST #endif // the internal pixel layout enums are in a different order, so we can easily do range comparisons of types -// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible -typedef enum +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +typedef enum { STBIRI_1CHANNEL = 0, STBIRI_2CHANNEL = 1, STBIRI_RGB = 2, STBIRI_BGR = 3, STBIRI_4CHANNEL = 4, - + STBIRI_RGBA = 5, STBIRI_BGRA = 6, STBIRI_ARGB = 7, @@ -979,7 +1004,7 @@ typedef struct stbir__span spans[2]; // can be two spans, if doing input subrect with clamp mode WRAP } stbir__extents; -typedef struct +typedef struct { #ifdef STBIR_PROFILE union @@ -1010,7 +1035,7 @@ typedef struct typedef void stbir__decode_pixels_func( float * decode, int width_times_channels, void const * input ); typedef void stbir__alpha_weight_func( float * decode_buffer, int width_times_channels ); -typedef void stbir__horizontal_gather_channels_func( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, +typedef void stbir__horizontal_gather_channels_func( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ); typedef void stbir__alpha_unweight_func(float * encode_buffer, int width_times_channels ); typedef void stbir__encode_pixels_func( void * output, int width_times_channels, float const * encode ); @@ -1053,10 +1078,10 @@ struct stbir__info stbir__horizontal_gather_channels_func * horizontal_gather_channels; stbir__alpha_unweight_func * alpha_unweight; stbir__encode_pixels_func * encode_pixels; - - int alloced_total; + + int alloc_ring_buffer_num_entries; // Number of entries in the ring buffer that will be allocated int splits; // count of splits - + stbir_internal_pixel_layout input_pixel_layout_internal; stbir_internal_pixel_layout output_pixel_layout_internal; @@ -1065,7 +1090,7 @@ struct stbir__info int vertical_first; int channels; int effective_channels; // same as channels, except on RGBA/ARGB (7), or XA/AX (3) - int alloc_ring_buffer_num_entries; // Number of entries in the ring buffer that will be allocated + size_t alloced_total; }; @@ -1076,10 +1101,11 @@ struct stbir__info #define stbir__small_float ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) // min/max friendly -#define STBIR_CLAMP(x, xmin, xmax) do { \ +#define STBIR_CLAMP(x, xmin, xmax) for(;;) { \ if ( (x) < (xmin) ) (x) = (xmin); \ if ( (x) > (xmax) ) (x) = (xmax); \ -} while (0) + break; \ +} static stbir__inline int stbir__min(int a, int b) { @@ -1141,7 +1167,7 @@ static const stbir_uint32 fp32_to_srgb8_tab4[104] = { 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, }; - + static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) { static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps @@ -1172,19 +1198,44 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT 32 // when downsampling and <= 32 scanlines of buffering, use gather. gather used down to 1/8th scaling for 25% win. #endif -// restrict pointers for the output pointers +#ifndef STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS +#define STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS 4 // when threading, what is the minimum number of scanlines for a split? +#endif + +// restrict pointers for the output pointers, other loop and unroll control #if defined( _MSC_VER ) && !defined(__clang__) #define STBIR_STREAMOUT_PTR( star ) star __restrict #define STBIR_NO_UNROLL( ptr ) __assume(ptr) // this oddly keeps msvc from unrolling a loop -#elif defined( __clang__ ) + #if _MSC_VER >= 1900 + #define STBIR_NO_UNROLL_LOOP_START __pragma(loop( no_vector )) + #else + #define STBIR_NO_UNROLL_LOOP_START + #endif +#elif defined( __clang__ ) #define STBIR_STREAMOUT_PTR( star ) star __restrict__ - #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) -#elif defined( __GNUC__ ) + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) + #if ( __clang_major__ >= 4 ) || ( ( __clang_major__ >= 3 ) && ( __clang_minor__ >= 5 ) ) + #define STBIR_NO_UNROLL_LOOP_START _Pragma("clang loop unroll(disable)") _Pragma("clang loop vectorize(disable)") + #else + #define STBIR_NO_UNROLL_LOOP_START + #endif +#elif defined( __GNUC__ ) #define STBIR_STREAMOUT_PTR( star ) star __restrict__ #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) + #if __GNUC__ >= 14 + #define STBIR_NO_UNROLL_LOOP_START _Pragma("GCC unroll 0") _Pragma("GCC novector") + #else + #define STBIR_NO_UNROLL_LOOP_START + #endif + #define STBIR_NO_UNROLL_LOOP_START_INF_FOR #else #define STBIR_STREAMOUT_PTR( star ) star #define STBIR_NO_UNROLL( ptr ) + #define STBIR_NO_UNROLL_LOOP_START +#endif + +#ifndef STBIR_NO_UNROLL_LOOP_START_INF_FOR +#define STBIR_NO_UNROLL_LOOP_START_INF_FOR STBIR_NO_UNROLL_LOOP_START #endif #ifdef STBIR_NO_SIMD // force simd off for whatever reason @@ -1223,7 +1274,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #ifdef STBIR_SSE2 #include - + #define stbir__simdf __m128 #define stbir__simdi __m128i @@ -1254,7 +1305,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdi_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), (reg) ) #define stbir__prefetch( ptr ) _mm_prefetch((char*)(ptr), _MM_HINT_T0 ) - + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ { \ stbir__simdi zero = _mm_setzero_si128(); \ @@ -1285,7 +1336,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),_mm_setzero_ps())))) #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())))) - #define stbir__simdi_to_int( i ) _mm_cvtsi128_si32(i) + #define stbir__simdi_to_int( i ) _mm_cvtsi128_si32(i) #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = _mm_cvtepi32_ps( ireg ) #define stbir__simdf_add( out, reg0, reg1 ) (out) = _mm_add_ps( reg0, reg1 ) #define stbir__simdf_mult( out, reg0, reg1 ) (out) = _mm_mul_ps( reg0, reg1 ) @@ -1440,10 +1491,10 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdi8_convert_i32_to_float(out, ireg) (out) = _mm256_cvtepi32_ps( ireg ) #define stbir__simdf8_convert_float_to_i32( i, f ) (i) = _mm256_cvttps_epi32(f) - + #define stbir__simdf8_bot4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (0<<0)+(2<<4) ) #define stbir__simdf8_top4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (1<<0)+(3<<4) ) - + #define stbir__simdf8_gettop4( reg ) _mm256_extractf128_ps(reg,1) #ifdef STBIR_AVX2 @@ -1471,8 +1522,8 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) out = _mm256_castsi256_si128( _mm256_permute4x64_epi64( _mm256_packus_epi16( t, t ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ) ); \ } - #define stbir__simdi8_expand_u16_to_u32(out,ireg) out = _mm256_unpacklo_epi16( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), _mm256_setzero_si256() ); - + #define stbir__simdi8_expand_u16_to_u32(out,ireg) out = _mm256_unpacklo_epi16( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), _mm256_setzero_si256() ); + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ { \ stbir__simdf8 af,bf; \ @@ -1496,7 +1547,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) a = _mm_unpackhi_epi8( ireg, zero ); \ out1 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ } - + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ { \ stbir__simdi t; \ @@ -1514,7 +1565,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) t = _mm_packus_epi16( t, t ); \ out = _mm_castps_si128( _mm_shuffle_ps( _mm_castsi128_ps(out), _mm_castsi128_ps(t), (0<<0)+(1<<2)+(0<<4)+(1<<6) ) ); \ } - + #define stbir__simdi8_expand_u16_to_u32(out,ireg) \ { \ stbir__simdi a,b,zero = _mm_setzero_si128(); \ @@ -1549,7 +1600,6 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdf8_0123to2222( out, in ) (out) = stbir__simdf_swiz(_mm256_castps256_ps128(in), 2,2,2,2 ) - #define stbir__simdf8_load2( out, ptr ) (out) = _mm256_castsi256_ps(_mm256_castsi128_si256( _mm_loadl_epi64( (__m128i*)(ptr)) )) // top values can be random (not denormal or nan for perf) #define stbir__simdf8_load4b( out, ptr ) (out) = _mm256_broadcast_ps( (__m128 const *)(ptr) ) static __m256i stbir_00112233 = { STBIR__CONST_4d_32i( 0, 0, 1, 1 ), STBIR__CONST_4d_32i( 2, 2, 3, 3 ) }; @@ -1582,11 +1632,11 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_fmadd_ps( mul1, mul2, add ) #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_fmadd_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ), add ) - #define stbir__simdf8_madd_mem4( out, add, mul, ptr ) (out) = _mm256_fmadd_ps( _mm256_castps128_ps256( mul ), _mm256_castps128_ps256( _mm_loadu_ps( (float const*)(ptr) ) ), add ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr )(out) = _mm256_fmadd_ps( _mm256_setr_m128( mul, _mm_setzero_ps() ), _mm256_setr_m128( _mm_loadu_ps( (float const*)(ptr) ), _mm_setzero_ps() ), add ) #else #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul1, mul2 ) ) #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ) ) ) - #define stbir__simdf8_madd_mem4( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_castps128_ps256( _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ) ) ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_setr_m128( _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ), _mm_setzero_ps() ) ) #endif #define stbir__if_simdf8_cast_to_simdf4( val ) _mm256_castps256_ps128( val ) @@ -1627,7 +1677,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) } #elif defined(STBIR_NEON) - + #include #define stbir__simdf float32x4_t @@ -1686,7 +1736,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdf_convert_float_to_i32( i, f ) (i) = vreinterpretq_u32_s32( vcvtq_s32_f32(f) ) #define stbir__simdf_convert_float_to_int( f ) vgetq_lane_s32(vcvtq_s32_f32(f), 0) - #define stbir__simdi_to_int( i ) (int)vgetq_lane_u32(i, 0) + #define stbir__simdi_to_int( i ) (int)vgetq_lane_u32(i, 0) #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),vdupq_n_f32(0))), 0)) #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),vdupq_n_f32(0))), 0)) #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = vcvtq_f32_s32( vreinterpretq_s32_u32(ireg) ) @@ -1737,12 +1787,20 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56)), \ vcreate_u8( (4*c+0) | ((4*c+1)<<8) | ((4*c+2)<<16) | ((4*c+3)<<24) | \ ((stbir_uint64)(4*d+0)<<32) | ((stbir_uint64)(4*d+1)<<40) | ((stbir_uint64)(4*d+2)<<48) | ((stbir_uint64)(4*d+3)<<56) ) ) + + static stbir__inline uint8x16x2_t stbir_make16x2(float32x4_t rega,float32x4_t regb) + { + uint8x16x2_t r = { vreinterpretq_u8_f32(rega), vreinterpretq_u8_f32(regb) }; + return r; + } #else #define stbir_make16(a,b,c,d) (uint8x16_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3,4*c+0,4*c+1,4*c+2,4*c+3,4*d+0,4*d+1,4*d+2,4*d+3} + #define stbir_make16x2(a,b) (uint8x16x2_t){{vreinterpretq_u8_f32(a),vreinterpretq_u8_f32(b)}} #endif #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vqtbl1q_u8( vreinterpretq_u8_f32(reg), stbir_make16(one, two, three, four) ) ) - + #define stbir__simdf_swiz2( rega, regb, one, two, three, four ) vreinterpretq_f32_u8( vqtbl2q_u8( stbir_make16x2(rega,regb), stbir_make16(one, two, three, four) ) ) + #define stbir__simdi_16madd( out, reg0, reg1 ) \ { \ int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ @@ -1942,7 +2000,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdf_convert_float_to_i32( i, f ) (i) = wasm_i32x4_trunc_sat_f32x4(f) #define stbir__simdf_convert_float_to_int( f ) wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(f), 0) - #define stbir__simdi_to_int( i ) wasm_i32x4_extract_lane(i, 0) + #define stbir__simdi_to_int( i ) wasm_i32x4_extract_lane(i, 0) #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint8_as_float),wasm_f32x4_const_splat(0))), 0)) #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint16_as_float),wasm_f32x4_const_splat(0))), 0)) #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = wasm_f32x4_convert_i32x4(ireg) @@ -2125,7 +2183,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #endif -#if defined(STBIR_NEON) && !defined(_M_ARM) +#if defined(STBIR_NEON) && !defined(_M_ARM) && !defined(__arm__) #if defined( _MSC_VER ) && !defined(__clang__) typedef __int16 stbir__FP16; @@ -2142,7 +2200,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #endif -#if !defined(STBIR_NEON) && !defined(STBIR_FP16C) || defined(STBIR_NEON) && defined(_M_ARM) +#if (!defined(STBIR_NEON) && !defined(STBIR_FP16C)) || (defined(STBIR_NEON) && defined(_M_ARM)) || (defined(STBIR_NEON) && defined(__arm__)) // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 @@ -2168,7 +2226,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) unsigned int sign_mask = 0x80000000u; stbir__FP16 o = { 0 }; stbir__FP32 f; - unsigned int sign; + unsigned int sign; f.f = val; sign = f.u & sign_mask; @@ -2369,24 +2427,6 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) stbir__simdi_store( output,final ); } -#elif defined(STBIR_WASM) || (defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM)) // WASM or 32-bit ARM on MSVC/clang - - static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) - { - for (int i=0; i<8; i++) - { - output[i] = stbir__half_to_float(input[i]); - } - } - - static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) - { - for (int i=0; i<8; i++) - { - output[i] = stbir__float_to_half(input[i]); - } - } - #elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang) static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) @@ -2415,7 +2455,7 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0).n16_u16[0]; } -#elif defined(STBIR_NEON) // 64-bit ARM +#elif defined(STBIR_NEON) && ( defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) ) // 64-bit ARM static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) { @@ -2441,6 +2481,23 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0); } +#elif defined(STBIR_WASM) || (defined(STBIR_NEON) && (defined(_MSC_VER) || defined(_M_ARM) || defined(__arm__))) // WASM or 32-bit ARM on MSVC/clang + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__half_to_float(input[i]); + } + } + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__float_to_half(input[i]); + } + } + #endif @@ -2462,10 +2519,10 @@ static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) #define stbir__simdf_0123to3012( out, reg ) (out) = stbir__simdf_swiz( reg, 3,0,1,2 ) #define stbir__simdf_0123to0011( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,1,1 ) #define stbir__simdf_0123to1100( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,0,0 ) -#define stbir__simdf_0123to2233( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,3,3 ) -#define stbir__simdf_0123to1133( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,3,3 ) -#define stbir__simdf_0123to0022( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,2 ) -#define stbir__simdf_0123to1032( out, reg ) (out) = stbir__simdf_swiz( reg, 1,0,3,2 ) +#define stbir__simdf_0123to2233( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,3,3 ) +#define stbir__simdf_0123to1133( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,3,3 ) +#define stbir__simdf_0123to0022( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,2 ) +#define stbir__simdf_0123to1032( out, reg ) (out) = stbir__simdf_swiz( reg, 1,0,3,2 ) typedef union stbir__simdi_u32 { @@ -2493,14 +2550,16 @@ static const STBIR__SIMDI_CONST(STBIR_topscale, 0x02000000); // Adding this switch saves about 5K on clang which is Captain Unroll the 3rd. #define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) #define STBIR_SIMD_NO_UNROLL(ptr) STBIR_NO_UNROLL(ptr) +#define STBIR_SIMD_NO_UNROLL_LOOP_START STBIR_NO_UNROLL_LOOP_START +#define STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR STBIR_NO_UNROLL_LOOP_START_INF_FOR #ifdef STBIR_MEMCPY #undef STBIR_MEMCPY -#define STBIR_MEMCPY stbir_simd_memcpy #endif +#define STBIR_MEMCPY stbir_simd_memcpy // override normal use of memcpy with much simpler copy (faster and smaller with our sized copies) -static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) +static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) { char STBIR_SIMD_STREAMOUT_PTR (*) d = (char*) dest; char STBIR_SIMD_STREAMOUT_PTR( * ) d_end = ((char*) dest) + bytes; @@ -2513,8 +2572,9 @@ static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) { if ( bytes < 16 ) { - if ( bytes ) + if ( bytes ) { + STBIR_SIMD_NO_UNROLL_LOOP_START do { STBIR_SIMD_NO_UNROLL(d); @@ -2529,8 +2589,9 @@ static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) // do one unaligned to get us aligned for the stream out below stbir__simdf_load( x, ( d + ofs_to_src ) ); stbir__simdf_store( d, x ); - d = (char*)( ( ( (ptrdiff_t)d ) + 16 ) & ~15 ); + d = (char*)( ( ( (size_t)d ) + 16 ) & ~15 ); + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { STBIR_SIMD_NO_UNROLL(d); @@ -2561,12 +2622,13 @@ static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); - d = (char*)( ( ( (ptrdiff_t)d ) + (16*stbir__simdfX_float_count) ) & ~((16*stbir__simdfX_float_count)-1) ); + d = (char*)( ( ( (size_t)d ) + (16*stbir__simdfX_float_count) ) & ~((16*stbir__simdfX_float_count)-1) ); + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { STBIR_SIMD_NO_UNROLL(d); - + if ( d > ( d_end - (16*stbir__simdfX_float_count) ) ) { if ( d == d_end ) @@ -2590,7 +2652,7 @@ static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) // memcpy that is specically intentionally overlapping (src is smaller then dest, so can be // a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to // the diff between dest and src) -static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) { char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; @@ -2599,6 +2661,7 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte if ( ofs_to_dest >= 16 ) // is the overlap more than 16 away? { char STBIR_SIMD_STREAMOUT_PTR( * ) s_end16 = ((char*) src) + (bytes&~15); + STBIR_SIMD_NO_UNROLL_LOOP_START do { stbir__simdf x; @@ -2615,7 +2678,7 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte do { STBIR_SIMD_NO_UNROLL(sd); - *(int*)( sd + ofs_to_dest ) = *(int*) sd; + *(int*)( sd + ofs_to_dest ) = *(int*) sd; sd += 4; } while ( sd < s_end ); } @@ -2624,13 +2687,17 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte // when in scalar mode, we let unrolling happen, so this macro just does the __restrict #define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) -#define STBIR_SIMD_NO_UNROLL(ptr) +#define STBIR_SIMD_NO_UNROLL(ptr) +#define STBIR_SIMD_NO_UNROLL_LOOP_START +#define STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR #endif // SSE2 #ifdef STBIR_PROFILE +#ifndef STBIR_PROFILE_FUNC + #if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) #ifdef _MSC_VER @@ -2640,7 +2707,7 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte #else // non msvc - static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() { stbir_uint32 lo, hi; asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) ); @@ -2649,7 +2716,7 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte #endif // msvc -#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) +#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) #if defined( _MSC_VER ) && !defined(__clang__) @@ -2670,8 +2737,9 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte #error Unknown platform for profiling. -#endif //x64 and +#endif // x64, arm +#endif // STBIR_PROFILE_FUNC #define STBIR_ONLY_PROFILE_GET_SPLIT_INFO ,stbir__per_split_info * split_info #define STBIR_ONLY_PROFILE_SET_SPLIT_INFO ,split_info @@ -2680,7 +2748,7 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte #define STBIR_ONLY_PROFILE_BUILD_SET_INFO ,profile_info // super light-weight micro profiler -#define STBIR_PROFILE_START_ll( info, wh ) { stbir_uint64 wh##thiszonetime = STBIR_PROFILE_FUNC(); stbir_uint64 * wh##save_parent_excluded_ptr = info->current_zone_excluded_ptr; stbir_uint64 wh##current_zone_excluded = 0; info->current_zone_excluded_ptr = &wh##current_zone_excluded; +#define STBIR_PROFILE_START_ll( info, wh ) { stbir_uint64 wh##thiszonetime = STBIR_PROFILE_FUNC(); stbir_uint64 * wh##save_parent_excluded_ptr = info->current_zone_excluded_ptr; stbir_uint64 wh##current_zone_excluded = 0; info->current_zone_excluded_ptr = &wh##current_zone_excluded; #define STBIR_PROFILE_END_ll( info, wh ) wh##thiszonetime = STBIR_PROFILE_FUNC() - wh##thiszonetime; info->profile.named.wh += wh##thiszonetime - wh##current_zone_excluded; *wh##save_parent_excluded_ptr += wh##thiszonetime; info->current_zone_excluded_ptr = wh##save_parent_excluded_ptr; } #define STBIR_PROFILE_FIRST_START_ll( info, wh ) { int i; info->current_zone_excluded_ptr = &info->profile.named.total; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } STBIR_PROFILE_START_ll( info, wh ); #define STBIR_PROFILE_CLEAR_EXTRAS_ll( info, num ) { int extra; for(extra=1;extra<(num);extra++) { int i; for(i=0;iprofile.array);i++) (info)[extra].profile.array[i]=0; } } @@ -2710,8 +2778,8 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte #define STBIR_PROFILE_FIRST_START( wh ) #define STBIR_PROFILE_CLEAR_EXTRAS( ) -#define STBIR_PROFILE_BUILD_START( wh ) -#define STBIR_PROFILE_BUILD_END( wh ) +#define STBIR_PROFILE_BUILD_START( wh ) +#define STBIR_PROFILE_BUILD_END( wh ) #define STBIR_PROFILE_BUILD_FIRST_START( wh ) #define STBIR_PROFILE_BUILD_CLEAR( info ) @@ -2736,10 +2804,10 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte #ifndef STBIR_SIMD -// memcpy that is specically intentionally overlapping (src is smaller then dest, so can be +// memcpy that is specifically intentionally overlapping (src is smaller then dest, so can be // a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to // the diff between dest and src) -static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) { char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; @@ -2748,10 +2816,11 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte if ( ofs_to_dest >= 8 ) // is the overlap more than 8 away? { char STBIR_SIMD_STREAMOUT_PTR( * ) s_end8 = ((char*) src) + (bytes&~7); + STBIR_NO_UNROLL_LOOP_START do { STBIR_NO_UNROLL(sd); - *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; + *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; sd += 8; } while ( sd < s_end8 ); @@ -2759,10 +2828,11 @@ static void stbir_overlapping_memcpy( void * dest, void const * src, size_t byte return; } + STBIR_NO_UNROLL_LOOP_START do { STBIR_NO_UNROLL(sd); - *(int*)( sd + ofs_to_dest ) = *(int*) sd; + *(int*)( sd + ofs_to_dest ) = *(int*) sd; sd += 4; } while ( sd < s_end ); } @@ -2863,13 +2933,6 @@ static float stbir__filter_mitchell(float x, float s, void * user_data) return (0.0f); } -static float stbir__support_zero(float s, void * user_data) -{ - STBIR__UNUSED(s); - STBIR__UNUSED(user_data); - return 0; -} - static float stbir__support_zeropoint5(float s, void * user_data) { STBIR__UNUSED(s); @@ -2884,7 +2947,7 @@ static float stbir__support_one(float s, void * user_data) return 1; } -static float stbir__support_two(float s, void * user_data) +static float stbir__support_two(float s, void * user_data) { STBIR__UNUSED(s); STBIR__UNUSED(user_data); @@ -2903,7 +2966,7 @@ static int stbir__get_filter_pixel_width(stbir__support_callback * support, floa return (int)STBIR_CEILF(support(scale,user_data) * 2.0f / scale); } -// this is how many coefficents per run of the filter (which is different +// this is how many coefficents per run of the filter (which is different // from the filter_pixel_width depending on if we are scattering or gathering) static int stbir__get_coefficient_width(stbir__sampler * samp, int is_gather, void * user_data) { @@ -2924,7 +2987,7 @@ static int stbir__get_coefficient_width(stbir__sampler * samp, int is_gather, vo } } -static int stbir__get_contributors(stbir__sampler * samp, int is_gather) +static int stbir__get_contributors(stbir__sampler * samp, int is_gather) { if (is_gather) return samp->scale_info.output_sub_size; @@ -2954,7 +3017,7 @@ static int stbir__edge_reflect_full( int n, int max ) { if (n < 0) { - if (n > -max) + if (n > -max) return -n; else return max - 1; @@ -3056,7 +3119,7 @@ static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline left_margin = -min_n; min_n = 0; } - + right_margin = 0; if ( max_n >= input_full_size ) { @@ -3081,7 +3144,7 @@ static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline // don't have to do edge calc for zero clamp if ( edge == STBIR_EDGE_ZERO ) return; - + // convert margin pixels to the pixels within the input (min and max) for( j = -left_margin ; j < 0 ; j++ ) { @@ -3179,20 +3242,20 @@ static void stbir__calculate_in_pixel_range( int * first_pixel, int * last_pixel float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; - float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) * inv_scale; - float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) * inv_scale; + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) * inv_scale; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) * inv_scale; first = (int)(STBIR_FLOORF(in_pixel_influence_lowerbound + 0.5f)); last = (int)(STBIR_FLOORF(in_pixel_influence_upperbound - 0.5f)); if ( edge == STBIR_EDGE_WRAP ) { - if ( first <= -input_size ) - first = -(input_size-1); + if ( first < -input_size ) + first = -input_size; if ( last >= (input_size*2)) last = (input_size*2) - 1; } - + *first_pixel = first; *last_pixel = last; } @@ -3213,10 +3276,10 @@ static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_ int i; int last_non_zero; float out_pixel_center = (float)n + 0.5f; - float in_center_of_out = (out_pixel_center + out_shift) * inv_scale; + float in_center_of_out = (out_pixel_center + out_shift) * inv_scale; int in_first_pixel, in_last_pixel; - + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, out_pixel_center, out_filter_radius, inv_scale, out_shift, input_size, edge ); last_non_zero = -1; @@ -3229,7 +3292,7 @@ static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_ if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) { if ( i == 0 ) // if we're at the front, just eat zero contributors - { + { STBIR_ASSERT ( ( in_last_pixel - in_first_pixel ) != 0 ); // there should be at least one contrib ++in_first_pixel; i--; @@ -3239,10 +3302,10 @@ static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_ } else last_non_zero = i; - + coefficient_group[i] = coeff; } - + in_last_pixel = last_non_zero+in_first_pixel; // kills trailing zeros contributors->n0 = in_first_pixel; contributors->n1 = in_last_pixel; @@ -3354,7 +3417,7 @@ static void stbir__calculate_coefficients_for_gather_downsample( int start, int stbir__contributors * contribs = contributors + out; // is this the first time this output pixel has been seen? Init it. - if ( out > first_out_inited ) + if ( out > first_out_inited ) { STBIR_ASSERT( out == ( first_out_inited + 1 ) ); // ensure we have only advanced one at time first_out_inited = out; @@ -3362,7 +3425,7 @@ static void stbir__calculate_coefficients_for_gather_downsample( int start, int contribs->n1 = in_pixel; coeffs[0] = coeff; } - else + else { // insert on end (always in order) if ( coeffs[0] == 0.0f ) // if the first coefficent is zero, then zap it for this coeffs @@ -3379,10 +3442,16 @@ static void stbir__calculate_coefficients_for_gather_downsample( int start, int } } +#ifdef STBIR_RENORMALIZE_IN_FLOAT +#define STBIR_RENORM_TYPE float +#else +#define STBIR_RENORM_TYPE double +#endif + static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter_extent_info* filter_info, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float * coefficient_group, int coefficient_width ) { int input_size = scale_info->input_full_size; - int input_last_n1 = input_size - 1; + int input_last_n1 = input_size - 1; int n, end; int lowest = 0x7fffffff; int highest = -0x7fffffff; @@ -3400,14 +3469,14 @@ static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter for (n = 0; n < end; n++) { int i; - float filter_scale, total_filter = 0; + STBIR_RENORM_TYPE filter_scale, total_filter = 0; int e; // add all contribs e = contribs->n1 - contribs->n0; for( i = 0 ; i <= e ; i++ ) { - total_filter += coeffs[i]; + total_filter += (STBIR_RENORM_TYPE) coeffs[i]; STBIR_ASSERT( ( coeffs[i] >= -2.0f ) && ( coeffs[i] <= 2.0f ) ); // check for wonky weights } @@ -3423,10 +3492,11 @@ static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter // if the total isn't 1.0, rescale everything if ( ( total_filter < (1.0f-stbir__small_float) ) || ( total_filter > (1.0f+stbir__small_float) ) ) { - filter_scale = 1.0f / total_filter; + filter_scale = ((STBIR_RENORM_TYPE)1.0) / total_filter; + // scale them all for (i = 0; i <= e; i++) - coeffs[i] *= filter_scale; + coeffs[i] = (float) ( coeffs[i] * filter_scale ); } } ++contribs; @@ -3483,13 +3553,13 @@ static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter else if ( ( edge == STBIR_EDGE_CLAMP ) || ( edge == STBIR_EDGE_REFLECT ) ) { // for clamp and reflect, calculate the true inbounds position (based on edge type) and just add that to the existing weight - + // right hand side first if ( contribs->n1 > input_last_n1 ) { int start = contribs->n0; int endi = contribs->n1; - contribs->n1 = input_last_n1; + contribs->n1 = input_last_n1; for( i = input_size; i <= endi; i++ ) stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), coeffs[i-start] ); } @@ -3500,18 +3570,18 @@ static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter int save_n0; float save_n0_coeff; float * c = coeffs - ( contribs->n0 + 1 ); - + // reinsert the coeffs with it reflected or clamped (insert accumulates, if the coeffs exist) - for( i = -1 ; i > contribs->n0 ; i-- ) + for( i = -1 ; i > contribs->n0 ; i-- ) stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), *c-- ); save_n0 = contribs->n0; save_n0_coeff = c[0]; // save it, since we didn't do the final one (i==n0), because there might be too many coeffs to hold (before we resize)! // now slide all the coeffs down (since we have accumulated them in the positive contribs) and reset the first contrib - contribs->n0 = 0; + contribs->n0 = 0; for(i = 0 ; i <= contribs->n1 ; i++ ) coeffs[i] = coeffs[i-save_n0]; - + // now that we have shrunk down the contribs, we insert the first one safely stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( save_n0, input_size ), save_n0_coeff ); } @@ -3547,7 +3617,9 @@ static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter filter_info->widest = widest; } -static int stbir__pack_coefficients( int num_contributors, stbir__contributors* contributors, float * coefficents, int coefficient_width, int widest, int row_width ) +#undef STBIR_RENORM_TYPE + +static int stbir__pack_coefficients( int num_contributors, stbir__contributors* contributors, float * coefficents, int coefficient_width, int widest, int row0, int row1 ) { #define STBIR_MOVE_1( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint32*)(dest))[0] = ((stbir_uint32*)(src))[0]; } #define STBIR_MOVE_2( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; } @@ -3556,6 +3628,10 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* #else #define STBIR_MOVE_4( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; ((stbir_uint64*)(dest))[1] = ((stbir_uint64*)(src))[1]; } #endif + + int row_end = row1 + 1; + STBIR__UNUSED( row0 ); // only used in an assert + if ( coefficient_width != widest ) { float * pc = coefficents; @@ -3564,6 +3640,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* switch( widest ) { case 1: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_1( pc, coeffs ); ++pc; @@ -3571,6 +3648,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 2: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_2( pc, coeffs ); pc += 2; @@ -3578,6 +3656,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 3: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_2( pc, coeffs ); STBIR_MOVE_1( pc+2, coeffs+2 ); @@ -3586,6 +3665,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 4: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); pc += 4; @@ -3593,6 +3673,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 5: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_1( pc+4, coeffs+4 ); @@ -3601,6 +3682,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 6: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_2( pc+4, coeffs+4 ); @@ -3609,6 +3691,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 7: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_2( pc+4, coeffs+4 ); @@ -3618,6 +3701,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 8: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_4( pc+4, coeffs+4 ); @@ -3626,6 +3710,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 9: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_4( pc+4, coeffs+4 ); @@ -3635,6 +3720,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 10: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_4( pc+4, coeffs+4 ); @@ -3644,6 +3730,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 11: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_4( pc+4, coeffs+4 ); @@ -3654,6 +3741,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; case 12: + STBIR_NO_UNROLL_LOOP_START do { STBIR_MOVE_4( pc, coeffs ); STBIR_MOVE_4( pc+4, coeffs+4 ); @@ -3663,6 +3751,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } while ( pc < pc_end ); break; default: + STBIR_NO_UNROLL_LOOP_START do { float * copy_end = pc + widest - 4; float * c = coeffs; @@ -3673,6 +3762,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* c += 4; } while ( pc <= copy_end ); copy_end += 4; + STBIR_NO_UNROLL_LOOP_START while ( pc < copy_end ) { STBIR_MOVE_1( pc, c ); @@ -3688,7 +3778,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* coefficents[ widest * num_contributors ] = 8888.0f; // the minimum we might read for unrolled filters widths is 12. So, we need to - // make sure we never read outside the decode buffer, by possibly moving + // make sure we never read outside the decode buffer, by possibly moving // the sample area back into the scanline, and putting zeros weights first. // we start on the right edge and check until we're well past the possible // clip area (2*widest). @@ -3697,13 +3787,13 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* float * coeffs = coefficents + widest * ( num_contributors - 1 ); // go until no chance of clipping (this is usually less than 8 lops) - while ( ( ( contribs->n0 + widest*2 ) >= row_width ) && ( contribs >= contributors ) ) + while ( ( contribs >= contributors ) && ( ( contribs->n0 + widest*2 ) >= row_end ) ) { // might we clip?? - if ( ( contribs->n0 + widest ) > row_width ) + if ( ( contribs->n0 + widest ) > row_end ) { int stop_range = widest; - + // if range is larger than 12, it will be handled by generic loops that can terminate on the exact length // of this contrib n1, instead of a fixed widest amount - so calculate this if ( widest > 12 ) @@ -3712,22 +3802,22 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* // how far will be read in the n_coeff loop (which depends on the widest count mod4); mod = widest & 3; - stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; // the n_coeff loops do a minimum amount of coeffs, so factor that in! if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; } // now see if we still clip with the refined range - if ( ( contribs->n0 + stop_range ) > row_width ) + if ( ( contribs->n0 + stop_range ) > row_end ) { - int new_n0 = row_width - stop_range; + int new_n0 = row_end - stop_range; int num = contribs->n1 - contribs->n0 + 1; int backup = contribs->n0 - new_n0; float * from_co = coeffs + num - 1; float * to_co = from_co + backup; - STBIR_ASSERT( ( new_n0 >= 0 ) && ( new_n0 < contribs->n0 ) ); + STBIR_ASSERT( ( new_n0 >= row0 ) && ( new_n0 < contribs->n0 ) ); // move the coeffs over while( num ) @@ -3746,7 +3836,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* // how far will be read in the n_coeff loop (which depends on the widest count mod4); mod = widest & 3; - stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; // the n_coeff loops do a minimum amount of coeffs, so factor that in! if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; @@ -3774,7 +3864,7 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot int input_full_size = samp->scale_info.input_full_size; int gather_num_contributors = samp->num_contributors; stbir__contributors* gather_contributors = samp->contributors; - float * gather_coeffs = samp->coefficients; + float * gather_coeffs = samp->coefficients; int gather_coefficient_width = samp->coefficient_width; switch ( samp->is_gather ) @@ -3792,16 +3882,16 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot break; case 0: // scatter downsample (only on vertical) - case 2: // gather downsample + case 2: // gather downsample { float in_pixels_radius = support(scale,user_data) * inv_scale; int filter_pixel_margin = samp->filter_pixel_margin; int input_end = input_full_size + filter_pixel_margin; - + // if this is a scatter, we do a downsample gather to get the coeffs, and then pivot after if ( !samp->is_gather ) { - // check if we are using the same gather downsample on the horizontal as this vertical, + // check if we are using the same gather downsample on the horizontal as this vertical, // if so, then we don't have to generate them, we can just pivot from the horizontal. if ( other_axis_for_pivot ) { @@ -3846,30 +3936,37 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot float * scatter_coeffs = samp->coefficients + ( gn0 + filter_pixel_margin ) * scatter_coefficient_width; float * g_coeffs = gather_coeffs; scatter_contributors = samp->contributors + ( gn0 + filter_pixel_margin ); - + for (k = gn0 ; k <= gn1 ; k++ ) { float gc = *g_coeffs++; - if ( ( k > highest_set ) || ( scatter_contributors->n0 > scatter_contributors->n1 ) ) + + // skip zero and denormals - must skip zeros to avoid adding coeffs beyond scatter_coefficient_width + // (which happens when pivoting from horizontal, which might have dummy zeros) + if ( ( ( gc >= stbir__small_float ) || ( gc <= -stbir__small_float ) ) ) { + if ( ( k > highest_set ) || ( scatter_contributors->n0 > scatter_contributors->n1 ) ) { - // if we are skipping over several contributors, we need to clear the skipped ones - stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); - while ( clear_contributors < scatter_contributors ) { - clear_contributors->n0 = 0; - clear_contributors->n1 = -1; - ++clear_contributors; + // if we are skipping over several contributors, we need to clear the skipped ones + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + while ( clear_contributors < scatter_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } } + scatter_contributors->n0 = n; + scatter_contributors->n1 = n; + scatter_coeffs[0] = gc; + highest_set = k; } - scatter_contributors->n0 = n; - scatter_contributors->n1 = n; - scatter_coeffs[0] = gc; - highest_set = k; - } - else - { - stbir__insert_coeff( scatter_contributors, scatter_coeffs, n, gc ); + else + { + stbir__insert_coeff( scatter_contributors, scatter_coeffs, n, gc ); + } + STBIR_ASSERT( ( scatter_contributors->n1 - scatter_contributors->n0 + 1 ) <= scatter_coefficient_width ); } ++scatter_contributors; scatter_coeffs += scatter_coefficient_width; @@ -3908,11 +4005,11 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot #define stbir__decode_suffix BGRA #define stbir__decode_swizzle -#define stbir__decode_order0 2 +#define stbir__decode_order0 2 #define stbir__decode_order1 1 #define stbir__decode_order2 0 #define stbir__decode_order3 3 -#define stbir__encode_order0 2 +#define stbir__encode_order0 2 #define stbir__encode_order1 1 #define stbir__encode_order2 0 #define stbir__encode_order3 3 @@ -3922,11 +4019,11 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot #define stbir__decode_suffix ARGB #define stbir__decode_swizzle -#define stbir__decode_order0 1 +#define stbir__decode_order0 1 #define stbir__decode_order1 2 #define stbir__decode_order2 3 #define stbir__decode_order3 0 -#define stbir__encode_order0 3 +#define stbir__encode_order0 3 #define stbir__encode_order1 0 #define stbir__encode_order2 1 #define stbir__encode_order3 2 @@ -3936,11 +4033,11 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot #define stbir__decode_suffix ABGR #define stbir__decode_swizzle -#define stbir__decode_order0 3 +#define stbir__decode_order0 3 #define stbir__decode_order1 2 #define stbir__decode_order2 1 #define stbir__decode_order3 0 -#define stbir__encode_order0 3 +#define stbir__encode_order0 3 #define stbir__encode_order1 2 #define stbir__encode_order2 1 #define stbir__encode_order3 0 @@ -3950,12 +4047,12 @@ static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * ot #define stbir__decode_suffix AR #define stbir__decode_swizzle -#define stbir__decode_order0 1 -#define stbir__decode_order1 0 +#define stbir__decode_order0 1 +#define stbir__decode_order1 0 #define stbir__decode_order2 3 #define stbir__decode_order3 2 -#define stbir__encode_order0 1 -#define stbir__encode_order1 0 +#define stbir__encode_order0 1 +#define stbir__encode_order1 0 #define stbir__encode_order2 3 #define stbir__encode_order3 2 #define stbir__coder_min_num 2 @@ -3973,9 +4070,10 @@ static void stbir__fancy_alpha_weight_4ch( float * out_buffer, int width_times_c // fancy alpha is stored internally as R G B A Rpm Gpm Bpm #ifdef STBIR_SIMD - + #ifdef STBIR_SIMD8 decode += 16; + STBIR_NO_UNROLL_LOOP_START while ( decode <= end_decode ) { stbir__simdf8 d0,d1,a0,a1,p0,p1; @@ -3998,8 +4096,9 @@ static void stbir__fancy_alpha_weight_4ch( float * out_buffer, int width_times_c out += 28; } decode -= 16; - #else + #else decode += 8; + STBIR_NO_UNROLL_LOOP_START while ( decode <= end_decode ) { stbir__simdf d0,a0,d1,a1,p0,p1; @@ -4022,12 +4121,14 @@ static void stbir__fancy_alpha_weight_4ch( float * out_buffer, int width_times_c // might be one last odd pixel #ifdef STBIR_SIMD8 + STBIR_NO_UNROLL_LOOP_START while ( decode < end_decode ) #else if ( decode < end_decode ) #endif { stbir__simdf d,a,p; + STBIR_NO_UNROLL(decode); stbir__simdf_load( d, decode ); stbir__simdf_0123to3333( a, d ); stbir__simdf_mult( p, a, d ); @@ -4069,6 +4170,7 @@ static void stbir__fancy_alpha_weight_2ch( float * out_buffer, int width_times_c decode += 8; if ( decode <= end_decode ) { + STBIR_NO_UNROLL_LOOP_START do { #ifdef STBIR_SIMD8 stbir__simdf8 d0,a0,p0; @@ -4077,11 +4179,11 @@ static void stbir__fancy_alpha_weight_2ch( float * out_buffer, int width_times_c stbir__simdf8_0123to11331133( p0, d0 ); stbir__simdf8_0123to00220022( a0, d0 ); stbir__simdf8_mult( p0, p0, a0 ); - + stbir__simdf_store2( out, stbir__if_simdf8_cast_to_simdf4( d0 ) ); stbir__simdf_store( out+2, stbir__if_simdf8_cast_to_simdf4( p0 ) ); stbir__simdf_store2h( out+3, stbir__if_simdf8_cast_to_simdf4( d0 ) ); - + stbir__simdf_store2( out+6, stbir__simdf8_gettop4( d0 ) ); stbir__simdf_store( out+8, stbir__simdf8_gettop4( p0 ) ); stbir__simdf_store2h( out+9, stbir__simdf8_gettop4( d0 ) ); @@ -4112,6 +4214,7 @@ static void stbir__fancy_alpha_weight_2ch( float * out_buffer, int width_times_c decode -= 8; #endif + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode < end_decode ) { float x = decode[0], y = decode[1]; @@ -4132,6 +4235,7 @@ static void stbir__fancy_alpha_unweight_4ch( float * encode_buffer, int width_ti // fancy RGBA is stored internally as R G B A Rpm Gpm Bpm + STBIR_SIMD_NO_UNROLL_LOOP_START do { float alpha = input[3]; #ifdef STBIR_SIMD @@ -4199,6 +4303,7 @@ static void stbir__simple_alpha_weight_4ch( float * decode_buffer, int width_tim #ifdef STBIR_SIMD { decode += 2 * stbir__simdfX_float_count; + STBIR_NO_UNROLL_LOOP_START while ( decode <= end_decode ) { stbir__simdfX d0,a0,d1,a1; @@ -4217,6 +4322,7 @@ static void stbir__simple_alpha_weight_4ch( float * decode_buffer, int width_tim // few last pixels remnants #ifdef STBIR_SIMD8 + STBIR_NO_UNROLL_LOOP_START while ( decode < end_decode ) #else if ( decode < end_decode ) @@ -4252,6 +4358,7 @@ static void stbir__simple_alpha_weight_2ch( float * decode_buffer, int width_tim #ifdef STBIR_SIMD decode += 2 * stbir__simdfX_float_count; + STBIR_NO_UNROLL_LOOP_START while ( decode <= end_decode ) { stbir__simdfX d0,a0,d1,a1; @@ -4269,6 +4376,7 @@ static void stbir__simple_alpha_weight_2ch( float * decode_buffer, int width_tim decode -= 2 * stbir__simdfX_float_count; #endif + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode < end_decode ) { float alpha = decode[1]; @@ -4283,6 +4391,7 @@ static void stbir__simple_alpha_unweight_4ch( float * encode_buffer, int width_t float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; float const * end_output = encode_buffer + width_times_channels; + STBIR_SIMD_NO_UNROLL_LOOP_START do { float alpha = encode[3]; @@ -4330,9 +4439,77 @@ static void stbir__simple_flip_3ch( float * decode_buffer, int width_times_chann float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; float const * end_decode = decode_buffer + width_times_channels; - decode += 12; +#ifdef STBIR_SIMD + #ifdef stbir__simdf_swiz2 // do we have two argument swizzles? + end_decode -= 12; + STBIR_NO_UNROLL_LOOP_START + while( decode <= end_decode ) + { + // on arm64 8 instructions, no overlapping stores + stbir__simdf a,b,c,na,nb; + STBIR_SIMD_NO_UNROLL(decode); + stbir__simdf_load( a, decode ); + stbir__simdf_load( b, decode+4 ); + stbir__simdf_load( c, decode+8 ); + + na = stbir__simdf_swiz2( a, b, 2, 1, 0, 5 ); + b = stbir__simdf_swiz2( a, b, 4, 3, 6, 7 ); + nb = stbir__simdf_swiz2( b, c, 0, 1, 4, 3 ); + c = stbir__simdf_swiz2( b, c, 2, 7, 6, 5 ); + + stbir__simdf_store( decode, na ); + stbir__simdf_store( decode+4, nb ); + stbir__simdf_store( decode+8, c ); + decode += 12; + } + end_decode += 12; + #else + end_decode -= 24; + STBIR_NO_UNROLL_LOOP_START + while( decode <= end_decode ) + { + // 26 instructions on x64 + stbir__simdf a,b,c,d,e,f,g; + float i21, i23; + STBIR_SIMD_NO_UNROLL(decode); + stbir__simdf_load( a, decode ); + stbir__simdf_load( b, decode+3 ); + stbir__simdf_load( c, decode+6 ); + stbir__simdf_load( d, decode+9 ); + stbir__simdf_load( e, decode+12 ); + stbir__simdf_load( f, decode+15 ); + stbir__simdf_load( g, decode+18 ); + + a = stbir__simdf_swiz( a, 2, 1, 0, 3 ); + b = stbir__simdf_swiz( b, 2, 1, 0, 3 ); + c = stbir__simdf_swiz( c, 2, 1, 0, 3 ); + d = stbir__simdf_swiz( d, 2, 1, 0, 3 ); + e = stbir__simdf_swiz( e, 2, 1, 0, 3 ); + f = stbir__simdf_swiz( f, 2, 1, 0, 3 ); + g = stbir__simdf_swiz( g, 2, 1, 0, 3 ); + + // stores overlap, need to be in order, + stbir__simdf_store( decode, a ); + i21 = decode[21]; + stbir__simdf_store( decode+3, b ); + i23 = decode[23]; + stbir__simdf_store( decode+6, c ); + stbir__simdf_store( decode+9, d ); + stbir__simdf_store( decode+12, e ); + stbir__simdf_store( decode+15, f ); + stbir__simdf_store( decode+18, g ); + decode[21] = i23; + decode[23] = i21; + decode += 24; + } + end_decode += 24; + #endif +#else + end_decode -= 12; + STBIR_NO_UNROLL_LOOP_START while( decode <= end_decode ) { + // 16 instructions float t0,t1,t2,t3; STBIR_NO_UNROLL(decode); t0 = decode[0]; t1 = decode[3]; t2 = decode[6]; t3 = decode[9]; @@ -4340,8 +4517,10 @@ static void stbir__simple_flip_3ch( float * decode_buffer, int width_times_chann decode[2] = t0; decode[5] = t1; decode[8] = t2; decode[11] = t3; decode += 12; } - decode -= 12; + end_decode += 12; +#endif + STBIR_NO_UNROLL_LOOP_START while( decode < end_decode ) { float t = decode[0]; @@ -4362,14 +4541,14 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir_edge edge_horizontal = stbir_info->horizontal.edge; stbir_edge edge_vertical = stbir_info->vertical.edge; int row = stbir__edge_wrap(edge_vertical, n, stbir_info->vertical.scale_info.input_full_size); - const void* input_plane_data = ( (char *) stbir_info->input_data ) + (ptrdiff_t)row * (ptrdiff_t) stbir_info->input_stride_bytes; + const void* input_plane_data = ( (char *) stbir_info->input_data ) + (size_t)row * (size_t) stbir_info->input_stride_bytes; stbir__span const * spans = stbir_info->scanline_extents.spans; float* full_decode_buffer = output_buffer - stbir_info->scanline_extents.conservative.n0 * effective_channels; // if we are on edge_zero, and we get in here with an out of bounds n, then the calculate filters has failed STBIR_ASSERT( !(edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->vertical.scale_info.input_full_size)) ); - do + do { float * decode_buffer; void const * input_data; @@ -4377,7 +4556,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float int width_times_channels; int width; - if ( spans->n1 < spans->n0 ) + if ( spans->n1 < spans->n0 ) break; width = spans->n1 + 1 - spans->n0; @@ -4394,7 +4573,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float // call the callback with a temp buffer (that they can choose to use or not). the temp is just right aligned memory in the decode_buffer itself input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ), input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); } - + STBIR_PROFILE_START( decode ); // convert the pixels info the float decode_buffer, (we index from end_decode, so that when channelsdecode_pixels( (float*)end_decode - width_times_channels, width_times_channels, input_data ); @@ -4418,7 +4597,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float // this code only runs if we're in edge_wrap, and we're doing the entire scanline int e, start_x[2]; int input_full_size = stbir_info->horizontal.scale_info.input_full_size; - + start_x[0] = -stbir_info->scanline_extents.edge_sizes[0]; // left edge start x start_x[1] = input_full_size; // right edge @@ -4447,7 +4626,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf tot,c; \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load1( c, hc ); \ - stbir__simdf_mult1_mem( tot, c, decode ); + stbir__simdf_mult1_mem( tot, c, decode ); #define stbir__2_coeff_only() \ stbir__simdf tot,c,d; \ @@ -4456,7 +4635,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_load2( d, decode ); \ stbir__simdf_mult( tot, c, d ); \ stbir__simdf_0123to1230( c, tot ); \ - stbir__simdf_add1( tot, tot, c ); + stbir__simdf_add1( tot, tot, c ); #define stbir__3_coeff_only() \ stbir__simdf tot,c,t; \ @@ -4466,7 +4645,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to1230( c, tot ); \ stbir__simdf_0123to2301( t, tot ); \ stbir__simdf_add1( tot, tot, c ); \ - stbir__simdf_add1( tot, tot, t ); + stbir__simdf_add1( tot, tot, t ); #define stbir__store_output_tiny() \ stbir__simdf_store1( output, tot ); \ @@ -4483,7 +4662,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load( c, hc + (ofs) ); \ - stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); #define stbir__1_coeff_remnant( ofs ) \ { stbir__simdf d; \ @@ -4495,7 +4674,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float { stbir__simdf d; \ stbir__simdf_load2z( c, hc+(ofs) ); \ stbir__simdf_load2( d, decode+(ofs) ); \ - stbir__simdf_madd( tot, tot, d, c ); } + stbir__simdf_madd( tot, tot, d, c ); } #define stbir__3_coeff_setup() \ stbir__simdf mask; \ @@ -4520,18 +4699,18 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float #define stbir__1_coeff_only() \ float tot; \ - tot = decode[0]*hc[0]; + tot = decode[0]*hc[0]; #define stbir__2_coeff_only() \ float tot; \ tot = decode[0] * hc[0]; \ - tot += decode[1] * hc[1]; + tot += decode[1] * hc[1]; #define stbir__3_coeff_only() \ float tot; \ tot = decode[0] * hc[0]; \ tot += decode[1] * hc[1]; \ - tot += decode[2] * hc[2]; + tot += decode[2] * hc[2]; #define stbir__store_output_tiny() \ output[0] = tot; \ @@ -4544,16 +4723,16 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float tot0 = decode[0] * hc[0]; \ tot1 = decode[1] * hc[1]; \ tot2 = decode[2] * hc[2]; \ - tot3 = decode[3] * hc[3]; + tot3 = decode[3] * hc[3]; #define stbir__4_coeff_continue_from_4( ofs ) \ tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ tot2 += decode[2+(ofs)] * hc[2+(ofs)]; \ - tot3 += decode[3+(ofs)] * hc[3+(ofs)]; + tot3 += decode[3+(ofs)] * hc[3+(ofs)]; #define stbir__1_coeff_remnant( ofs ) \ - tot0 += decode[0+(ofs)] * hc[0+(ofs)]; + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; #define stbir__2_coeff_remnant( ofs ) \ tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ @@ -4562,7 +4741,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float #define stbir__3_coeff_remnant( ofs ) \ tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ - tot2 += decode[2+(ofs)] * hc[2+(ofs)]; + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; #define stbir__store_output() \ output[0] = (tot0+tot2)+(tot1+tot3); \ @@ -4570,7 +4749,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float ++horizontal_contributors; \ output += 1; -#endif +#endif #define STBIR__horizontal_channels 1 #define STB_IMAGE_RESIZE_DO_HORIZONTALS @@ -4588,14 +4767,14 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_load1z( c, hc ); \ stbir__simdf_0123to0011( c, c ); \ stbir__simdf_load2( d, decode ); \ - stbir__simdf_mult( tot, d, c ); + stbir__simdf_mult( tot, d, c ); #define stbir__2_coeff_only() \ stbir__simdf tot,c; \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load2( c, hc ); \ stbir__simdf_0123to0011( c, c ); \ - stbir__simdf_mult_mem( tot, c, decode ); + stbir__simdf_mult_mem( tot, c, decode ); #define stbir__3_coeff_only() \ stbir__simdf tot,c,cs,d; \ @@ -4605,7 +4784,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_mult_mem( tot, c, decode ); \ stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_load2z( d, decode+4 ); \ - stbir__simdf_madd( tot, tot, d, c ); + stbir__simdf_madd( tot, tot, d, c ); #define stbir__store_output_tiny() \ stbir__simdf_0123to2301( c, tot ); \ @@ -4628,7 +4807,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load4b( cs, hc + (ofs) ); \ stbir__simdf8_0123to00112233( c, cs ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); #define stbir__1_coeff_remnant( ofs ) \ { stbir__simdf t; \ @@ -4649,13 +4828,13 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_load4b( cs, hc + (ofs) ); \ stbir__simdf8_0123to00112233( c, cs ); \ stbir__simdf8_load6z( d, decode+(ofs)*2 ); \ - stbir__simdf8_madd( tot0, tot0, c, d ); } + stbir__simdf8_madd( tot0, tot0, c, d ); } #define stbir__store_output() \ - { stbir__simdf t,c; \ + { stbir__simdf t,d; \ stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ - stbir__simdf_0123to2301( c, t ); \ - stbir__simdf_add( t, t, c ); \ + stbir__simdf_0123to2301( d, t ); \ + stbir__simdf_add( t, t, d ); \ stbir__simdf_store2( output, t ); \ horizontal_coefficients += coefficient_width; \ ++horizontal_contributors; \ @@ -4670,7 +4849,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to0011( c, cs ); \ stbir__simdf_mult_mem( tot0, c, decode ); \ stbir__simdf_0123to2233( c, cs ); \ - stbir__simdf_mult_mem( tot1, c, decode+4 ); + stbir__simdf_mult_mem( tot1, c, decode+4 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -4678,7 +4857,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to0011( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ stbir__simdf_0123to2233( c, cs ); \ - stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*2+4 ); + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*2+4 ); #define stbir__1_coeff_remnant( ofs ) \ { stbir__simdf d; \ @@ -4690,7 +4869,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float #define stbir__2_coeff_remnant( ofs ) \ stbir__simdf_load2( cs, hc + (ofs) ); \ stbir__simdf_0123to0011( c, cs ); \ - stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); #define stbir__3_coeff_remnant( ofs ) \ { stbir__simdf d; \ @@ -4699,7 +4878,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_load2z( d, decode + (ofs) * 2 + 4 ); \ - stbir__simdf_madd( tot1, tot1, d, c ); } + stbir__simdf_madd( tot1, tot1, d, c ); } #define stbir__store_output() \ stbir__simdf_add( tot0, tot0, tot1 ); \ @@ -4718,7 +4897,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float float tota,totb,c; \ c = hc[0]; \ tota = decode[0]*c; \ - totb = decode[1]*c; + totb = decode[1]*c; #define stbir__2_coeff_only() \ float tota,totb,c; \ @@ -4727,7 +4906,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float totb = decode[1]*c; \ c = hc[1]; \ tota += decode[2]*c; \ - totb += decode[3]*c; + totb += decode[3]*c; // this weird order of add matches the simd #define stbir__3_coeff_only() \ @@ -4740,7 +4919,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float totb += decode[5]*c; \ c = hc[1]; \ tota += decode[2]*c; \ - totb += decode[3]*c; + totb += decode[3]*c; #define stbir__store_output_tiny() \ output[0] = tota; \ @@ -4762,7 +4941,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float totb2 = decode[5]*c; \ c = hc[3]; \ tota3 = decode[6]*c; \ - totb3 = decode[7]*c; + totb3 = decode[7]*c; #define stbir__4_coeff_continue_from_4( ofs ) \ c = hc[0+(ofs)]; \ @@ -4776,12 +4955,12 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float totb2 += decode[5+(ofs)*2]*c; \ c = hc[3+(ofs)]; \ tota3 += decode[6+(ofs)*2]*c; \ - totb3 += decode[7+(ofs)*2]*c; + totb3 += decode[7+(ofs)*2]*c; #define stbir__1_coeff_remnant( ofs ) \ c = hc[0+(ofs)]; \ tota0 += decode[0+(ofs)*2] * c; \ - totb0 += decode[1+(ofs)*2] * c; + totb0 += decode[1+(ofs)*2] * c; #define stbir__2_coeff_remnant( ofs ) \ c = hc[0+(ofs)]; \ @@ -4789,7 +4968,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float totb0 += decode[1+(ofs)*2] * c; \ c = hc[1+(ofs)]; \ tota1 += decode[2+(ofs)*2] * c; \ - totb1 += decode[3+(ofs)*2] * c; + totb1 += decode[3+(ofs)*2] * c; #define stbir__3_coeff_remnant( ofs ) \ c = hc[0+(ofs)]; \ @@ -4800,7 +4979,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float totb1 += decode[3+(ofs)*2] * c; \ c = hc[2+(ofs)]; \ tota2 += decode[4+(ofs)*2] * c; \ - totb2 += decode[5+(ofs)*2] * c; + totb2 += decode[5+(ofs)*2] * c; #define stbir__store_output() \ output[0] = (tota0+tota2)+(tota1+tota3); \ @@ -4809,7 +4988,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float ++horizontal_contributors; \ output += 2; -#endif +#endif #define STBIR__horizontal_channels 2 #define STB_IMAGE_RESIZE_DO_HORIZONTALS @@ -4827,7 +5006,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_load1z( c, hc ); \ stbir__simdf_0123to0001( c, c ); \ stbir__simdf_load( d, decode ); \ - stbir__simdf_mult( tot, d, c ); + stbir__simdf_mult( tot, d, c ); #define stbir__2_coeff_only() \ stbir__simdf tot,c,cs,d; \ @@ -4838,7 +5017,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_mult( tot, d, c ); \ stbir__simdf_0123to1111( c, cs ); \ stbir__simdf_load( d, decode+3 ); \ - stbir__simdf_madd( tot, tot, d, c ); + stbir__simdf_madd( tot, tot, d, c ); #define stbir__3_coeff_only() \ stbir__simdf tot,c,d,cs; \ @@ -4852,7 +5031,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd( tot, tot, d, c ); \ stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_load( d, decode+6 ); \ - stbir__simdf_madd( tot, tot, d, c ); + stbir__simdf_madd( tot, tot, d, c ); #define stbir__store_output_tiny() \ stbir__simdf_store2( output, tot ); \ @@ -4872,7 +5051,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to00001111( c, cs ); \ stbir__simdf8_mult_mem( tot0, c, decode - 1 ); \ stbir__simdf8_0123to22223333( c, cs ); \ - stbir__simdf8_mult_mem( tot1, c, decode+6 - 1 ); + stbir__simdf8_mult_mem( tot1, c, decode+6 - 1 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -4880,26 +5059,26 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to00001111( c, cs ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ stbir__simdf8_0123to22223333( c, cs ); \ - stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*3 + 6 - 1 ); + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*3 + 6 - 1 ); #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load1rep4( t, hc + (ofs) ); \ - stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*3 - 1 ); + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*3 - 1 ); #define stbir__2_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ stbir__simdf8_0123to22223333( c, cs ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); - + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); + #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load4b( cs, hc + (ofs) ); \ stbir__simdf8_0123to00001111( c, cs ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ stbir__simdf8_0123to2222( t, cs ); \ - stbir__simdf8_madd_mem4( tot1, tot1, t, decode+(ofs)*3 + 6 - 1 ); + stbir__simdf8_madd_mem4( tot1, tot1, t, decode+(ofs)*3 + 6 - 1 ); #define stbir__store_output() \ stbir__simdf8_add( tot0, tot0, tot1 ); \ @@ -4930,7 +5109,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to1122( c, cs ); \ stbir__simdf_mult_mem( tot1, c, decode+4 ); \ stbir__simdf_0123to2333( c, cs ); \ - stbir__simdf_mult_mem( tot2, c, decode+8 ); + stbir__simdf_mult_mem( tot2, c, decode+8 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -4940,13 +5119,13 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to1122( c, cs ); \ stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ stbir__simdf_0123to2333( c, cs ); \ - stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*3+8 ); + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*3+8 ); #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load1z( c, hc + (ofs) ); \ stbir__simdf_0123to0001( c, c ); \ - stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); #define stbir__2_coeff_remnant( ofs ) \ { stbir__simdf d; \ @@ -4956,7 +5135,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ stbir__simdf_0123to1122( c, cs ); \ stbir__simdf_load2z( d, decode+(ofs)*3+4 ); \ - stbir__simdf_madd( tot1, tot1, c, d ); } + stbir__simdf_madd( tot1, tot1, c, d ); } #define stbir__3_coeff_remnant( ofs ) \ { stbir__simdf d; \ @@ -4968,7 +5147,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_load1z( d, decode+(ofs)*3+8 ); \ - stbir__simdf_madd( tot2, tot2, c, d ); } + stbir__simdf_madd( tot2, tot2, c, d ); } #define stbir__store_output() \ stbir__simdf_0123ABCDto3ABx( c, tot0, tot1 ); \ @@ -4999,7 +5178,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float c = hc[0]; \ tot0 = decode[0]*c; \ tot1 = decode[1]*c; \ - tot2 = decode[2]*c; + tot2 = decode[2]*c; #define stbir__2_coeff_only() \ float tot0, tot1, tot2, c; \ @@ -5010,7 +5189,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float c = hc[1]; \ tot0 += decode[3]*c; \ tot1 += decode[4]*c; \ - tot2 += decode[5]*c; + tot2 += decode[5]*c; #define stbir__3_coeff_only() \ float tot0, tot1, tot2, c; \ @@ -5025,7 +5204,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float c = hc[2]; \ tot0 += decode[6]*c; \ tot1 += decode[7]*c; \ - tot2 += decode[8]*c; + tot2 += decode[8]*c; #define stbir__store_output_tiny() \ output[0] = tot0; \ @@ -5052,7 +5231,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float c = hc[3]; \ totd0 = decode[9]*c; \ totd1 = decode[10]*c; \ - totd2 = decode[11]*c; + totd2 = decode[11]*c; #define stbir__4_coeff_continue_from_4( ofs ) \ c = hc[0+(ofs)]; \ @@ -5070,7 +5249,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float c = hc[3+(ofs)]; \ totd0 += decode[9+(ofs)*3]*c; \ totd1 += decode[10+(ofs)*3]*c; \ - totd2 += decode[11+(ofs)*3]*c; + totd2 += decode[11+(ofs)*3]*c; #define stbir__1_coeff_remnant( ofs ) \ c = hc[0+(ofs)]; \ @@ -5100,7 +5279,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float c = hc[2+(ofs)]; \ totc0 += decode[6+(ofs)*3]*c; \ totc1 += decode[7+(ofs)*3]*c; \ - totc2 += decode[8+(ofs)*3]*c; + totc2 += decode[8+(ofs)*3]*c; #define stbir__store_output() \ output[0] = (tota0+totc0)+(totb0+totd0); \ @@ -5110,7 +5289,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float ++horizontal_contributors; \ output += 3; -#endif +#endif #define STBIR__horizontal_channels 3 #define STB_IMAGE_RESIZE_DO_HORIZONTALS @@ -5126,7 +5305,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load1( c, hc ); \ stbir__simdf_0123to0000( c, c ); \ - stbir__simdf_mult_mem( tot, c, decode ); + stbir__simdf_mult_mem( tot, c, decode ); #define stbir__2_coeff_only() \ stbir__simdf tot,c,cs; \ @@ -5135,7 +5314,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to0000( c, cs ); \ stbir__simdf_mult_mem( tot, c, decode ); \ stbir__simdf_0123to1111( c, cs ); \ - stbir__simdf_madd_mem( tot, tot, c, decode+4 ); + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); #define stbir__3_coeff_only() \ stbir__simdf tot,c,cs; \ @@ -5146,7 +5325,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to1111( c, cs ); \ stbir__simdf_madd_mem( tot, tot, c, decode+4 ); \ stbir__simdf_0123to2222( c, cs ); \ - stbir__simdf_madd_mem( tot, tot, c, decode+8 ); + stbir__simdf_madd_mem( tot, tot, c, decode+8 ); #define stbir__store_output_tiny() \ stbir__simdf_store( output, tot ); \ @@ -5163,7 +5342,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to00001111( c, cs ); \ stbir__simdf8_mult_mem( tot0, c, decode ); \ stbir__simdf8_0123to22223333( c, cs ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+8 ); + stbir__simdf8_madd_mem( tot0, tot0, c, decode+8 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5171,26 +5350,26 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to00001111( c, cs ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ stbir__simdf8_0123to22223333( c, cs ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load1rep4( t, hc + (ofs) ); \ - stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4 ); + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4 ); #define stbir__2_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ stbir__simdf8_0123to22223333( c, cs ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); - + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load4b( cs, hc + (ofs) ); \ stbir__simdf8_0123to00001111( c, cs ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ stbir__simdf8_0123to2222( t, cs ); \ - stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4+8 ); + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4+8 ); #define stbir__store_output() \ stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ @@ -5199,7 +5378,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float ++horizontal_contributors; \ output += 4; -#else +#else #define stbir__4_coeff_start() \ stbir__simdf tot0,tot1,c,cs; \ @@ -5212,7 +5391,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+8 ); \ stbir__simdf_0123to3333( c, cs ); \ - stbir__simdf_madd_mem( tot1, tot1, c, decode+12 ); + stbir__simdf_madd_mem( tot1, tot1, c, decode+12 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5224,13 +5403,13 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); \ stbir__simdf_0123to3333( c, cs ); \ - stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+12 ); + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+12 ); #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load1( c, hc + (ofs) ); \ stbir__simdf_0123to0000( c, c ); \ - stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); #define stbir__2_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5238,8 +5417,8 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_0123to0000( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ stbir__simdf_0123to1111( c, cs ); \ - stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); - + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); + #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load( cs, hc + (ofs) ); \ @@ -5365,7 +5544,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float x0 += decode[0+(ofs)*4] * c; \ x1 += decode[1+(ofs)*4] * c; \ x2 += decode[2+(ofs)*4] * c; \ - x3 += decode[3+(ofs)*4] * c; + x3 += decode[3+(ofs)*4] * c; #define stbir__2_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5378,8 +5557,8 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float y0 += decode[4+(ofs)*4] * c; \ y1 += decode[5+(ofs)*4] * c; \ y2 += decode[6+(ofs)*4] * c; \ - y3 += decode[7+(ofs)*4] * c; - + y3 += decode[7+(ofs)*4] * c; + #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ c = hc[0+(ofs)]; \ @@ -5396,7 +5575,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float x0 += decode[8+(ofs)*4] * c; \ x1 += decode[9+(ofs)*4] * c; \ x2 += decode[10+(ofs)*4] * c; \ - x3 += decode[11+(ofs)*4] * c; + x3 += decode[11+(ofs)*4] * c; #define stbir__store_output() \ output[0] = x0 + y0; \ @@ -5407,7 +5586,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float ++horizontal_contributors; \ output += 4; -#endif +#endif #define STBIR__horizontal_channels 4 #define STB_IMAGE_RESIZE_DO_HORIZONTALS @@ -5426,7 +5605,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_load1( c, hc ); \ stbir__simdf_0123to0000( c, c ); \ stbir__simdf_mult_mem( tot0, c, decode ); \ - stbir__simdf_mult_mem( tot1, c, decode+3 ); + stbir__simdf_mult_mem( tot1, c, decode+3 ); #define stbir__2_coeff_only() \ stbir__simdf tot0,tot1,c,cs; \ @@ -5437,7 +5616,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_mult_mem( tot1, c, decode+3 ); \ stbir__simdf_0123to1111( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ - stbir__simdf_madd_mem( tot1, tot1, c,decode+10 ); + stbir__simdf_madd_mem( tot1, tot1, c,decode+10 ); #define stbir__3_coeff_only() \ stbir__simdf tot0,tot1,c,cs; \ @@ -5451,7 +5630,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot1, tot1, c, decode+10 ); \ stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ - stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); #define stbir__store_output_tiny() \ stbir__simdf_store( output+3, tot1 ); \ @@ -5473,7 +5652,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to22222222( c, cs ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+14 ); \ stbir__simdf8_0123to33333333( c, cs ); \ - stbir__simdf8_madd_mem( tot1, tot1, c, decode+21 ); + stbir__simdf8_madd_mem( tot1, tot1, c, decode+21 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5485,19 +5664,19 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to22222222( c, cs ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ stbir__simdf8_0123to33333333( c, cs ); \ - stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+21 ); + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+21 ); #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load1b( c, hc + (ofs) ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); #define stbir__2_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf8_load1b( c, hc + (ofs) ); \ stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ stbir__simdf8_load1b( c, hc + (ofs)+1 ); \ - stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5507,7 +5686,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf8_0123to11111111( c, cs ); \ stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ stbir__simdf8_0123to22222222( c, cs ); \ - stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); #define stbir__store_output() \ stbir__simdf8_add( tot0, tot0, tot1 ); \ @@ -5540,7 +5719,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); \ stbir__simdf_0123to3333( c, cs ); \ stbir__simdf_madd_mem( tot2, tot2, c, decode+21 ); \ - stbir__simdf_madd_mem( tot3, tot3, c, decode+24 ); + stbir__simdf_madd_mem( tot3, tot3, c, decode+24 ); #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5556,7 +5735,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); \ stbir__simdf_0123to3333( c, cs ); \ stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+21 ); \ - stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+24 ); + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+24 ); #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5573,8 +5752,8 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ stbir__simdf_0123to1111( c, cs ); \ stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ - stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); - + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); + #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ stbir__simdf_load( cs, hc + (ofs) ); \ @@ -5586,7 +5765,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ stbir__simdf_0123to2222( c, cs ); \ stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ - stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); #define stbir__store_output() \ stbir__simdf_add( tot0, tot0, tot2 ); \ @@ -5610,7 +5789,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float tot3 = decode[3]*c; \ tot4 = decode[4]*c; \ tot5 = decode[5]*c; \ - tot6 = decode[6]*c; + tot6 = decode[6]*c; #define stbir__2_coeff_only() \ float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ @@ -5704,7 +5883,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float y3 += decode[24] * c; \ y4 += decode[25] * c; \ y5 += decode[26] * c; \ - y6 += decode[27] * c; + y6 += decode[27] * c; #define stbir__4_coeff_continue_from_4( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5739,7 +5918,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float y3 += decode[24+(ofs)*7] * c; \ y4 += decode[25+(ofs)*7] * c; \ y5 += decode[26+(ofs)*7] * c; \ - y6 += decode[27+(ofs)*7] * c; + y6 += decode[27+(ofs)*7] * c; #define stbir__1_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ @@ -5770,7 +5949,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float y4 += decode[11+(ofs)*7] * c; \ y5 += decode[12+(ofs)*7] * c; \ y6 += decode[13+(ofs)*7] * c; \ - + #define stbir__3_coeff_remnant( ofs ) \ STBIR_SIMD_NO_UNROLL(decode); \ c = hc[0+(ofs)]; \ @@ -5810,7 +5989,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float ++horizontal_contributors; \ output += 7; -#endif +#endif #define STBIR__horizontal_channels 7 #define STB_IMAGE_RESIZE_DO_HORIZONTALS @@ -5937,7 +6116,7 @@ static void stbir__encode_scanline( stbir__info const * stbir_info, void *output // if we have an output callback, we first convert the decode buffer in place (and then hand that to the callback) if ( stbir_info->out_pixels_cb ) output_buffer = encode_buffer; - + STBIR_PROFILE_START( encode ); // convert into the output buffer stbir_info->encode_pixels( output_buffer, width_times_channels, encode_buffer ); @@ -5945,7 +6124,7 @@ static void stbir__encode_scanline( stbir__info const * stbir_info, void *output // if we have an output callback, call it to send the data if ( stbir_info->out_pixels_cb ) - stbir_info->out_pixels_cb( output_buffer_data, num_pixels, row, stbir_info->user_data ); + stbir_info->out_pixels_cb( output_buffer, num_pixels, row, stbir_info->user_data ); } @@ -6015,7 +6194,7 @@ static void stbir__resample_vertical_gather(stbir__info const * stbir_info, stbi stbir__resample_horizontal_gather(stbir_info, encode_buffer, decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); } - stbir__encode_scanline( stbir_info, ( (char *) stbir_info->output_data ) + ((ptrdiff_t)n * (ptrdiff_t)stbir_info->output_stride_bytes), + stbir__encode_scanline( stbir_info, ( (char *) stbir_info->output_data ) + ((size_t)n * (size_t)stbir_info->output_stride_bytes), encode_buffer, n STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); } @@ -6030,7 +6209,7 @@ static void stbir__decode_and_resample_for_vertical_gather_loop(stbir__info cons // update new end scanline split_info->ring_buffer_last_scanline = n; - // get ring buffer + // get ring buffer ring_buffer_index = (split_info->ring_buffer_begin_index + (split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; ring_buffer = stbir__get_ring_buffer_entry(stbir_info, split_info, ring_buffer_index); @@ -6056,7 +6235,7 @@ static void stbir__vertical_gather_loop( stbir__info const * stbir_info, stbir__ // initialize the ring buffer for gathering split_info->ring_buffer_begin_index = 0; - split_info->ring_buffer_first_scanline = stbir_info->vertical.extent_info.lowest; + split_info->ring_buffer_first_scanline = vertical_contributors->n0; split_info->ring_buffer_last_scanline = split_info->ring_buffer_first_scanline - 1; // means "empty" for (y = start_output_y; y < end_output_y; y++) @@ -6080,12 +6259,12 @@ static void stbir__vertical_gather_loop( stbir__info const * stbir_info, stbir__ split_info->ring_buffer_first_scanline++; split_info->ring_buffer_begin_index++; } - + if ( stbir_info->vertical_first ) { float * ring_buffer = stbir__get_ring_buffer_scanline( stbir_info, split_info, ++split_info->ring_buffer_last_scanline ); // Decode the nth scanline from the source image into the decode buffer. - stbir__decode_scanline( stbir_info, split_info->ring_buffer_last_scanline, ring_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + stbir__decode_scanline( stbir_info, split_info->ring_buffer_last_scanline, ring_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); } else { @@ -6108,10 +6287,10 @@ static void stbir__encode_first_scanline_from_scatter(stbir__info const * stbir_ { // evict a scanline out into the output buffer float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); - + // dump the scanline out - stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (ptrdiff_t)split_info->ring_buffer_first_scanline * (ptrdiff_t)stbir_info->output_stride_bytes ), ring_buffer_entry, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); - + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (size_t)split_info->ring_buffer_first_scanline * (size_t)stbir_info->output_stride_bytes ), ring_buffer_entry, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + // mark it as empty ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; @@ -6129,10 +6308,10 @@ static void stbir__horizontal_resample_and_encode_first_scanline_from_scatter(st // Now resample it into the buffer. stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, ring_buffer_entry STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); - + // dump the scanline out - stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (ptrdiff_t)split_info->ring_buffer_first_scanline * (ptrdiff_t)stbir_info->output_stride_bytes ), split_info->vertical_buffer, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); - + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (size_t)split_info->ring_buffer_first_scanline * (size_t)stbir_info->output_stride_bytes ), split_info->vertical_buffer, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + // mark it as empty ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; @@ -6172,7 +6351,7 @@ static void stbir__resample_vertical_scatter(stbir__info const * stbir_info, stb STBIR_PROFILE_END( vertical ); } -typedef void stbir__handle_scanline_for_scatter_func(stbir__info const * stbir_info, stbir__per_split_info* split_info); +typedef void stbir__handle_scanline_for_scatter_func(stbir__info const * stbir_info, stbir__per_split_info* split_info); static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) { @@ -6193,7 +6372,7 @@ static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir_ end_input_y = split_info[split_count-1].end_input_y; // adjust for starting offset start_input_y - y = start_input_y + stbir_info->vertical.filter_pixel_margin; + y = start_input_y + stbir_info->vertical.filter_pixel_margin; vertical_contributors += y ; vertical_coefficients += stbir_info->vertical.coefficient_width * y; @@ -6240,7 +6419,7 @@ static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir_ split_info->start_input_y = y; on_first_input_y = 0; - // clip the region + // clip the region if ( out_first_scanline < start_output_y ) { vc += start_output_y - out_first_scanline; @@ -6253,11 +6432,11 @@ static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir_ // if very first scanline, init the index if (split_info->ring_buffer_begin_index < 0) split_info->ring_buffer_begin_index = out_first_scanline - start_output_y; - + STBIR_ASSERT( split_info->ring_buffer_begin_index <= out_first_scanline ); // Decode the nth scanline from the source image into the decode buffer. - stbir__decode_scanline( stbir_info, y, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + stbir__decode_scanline( stbir_info, y, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); // When horizontal first, we resample horizontally into the vertical buffer before we scatter it out if ( !stbir_info->vertical_first ) @@ -6269,7 +6448,7 @@ static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir_ if ( ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) && ( out_last_scanline > split_info->ring_buffer_last_scanline ) ) handle_scanline_for_scatter( stbir_info, split_info ); - + // Now the horizontal buffer is ready to write to all ring buffer rows, so do it. stbir__resample_vertical_scatter(stbir_info, split_info, out_first_scanline, out_last_scanline, vc, (float*)scanline_scatter_buffer, (float*)scanline_scatter_buffer_end ); @@ -6305,7 +6484,7 @@ static void stbir__set_sampler(stbir__sampler * samp, stbir_filter filter, stbir if (scale_info->scale >= ( 1.0f - stbir__small_float ) ) { if ( (scale_info->scale <= ( 1.0f + stbir__small_float ) ) && ( STBIR_CEILF(scale_info->pixel_shift) == scale_info->pixel_shift ) ) - filter = STBIR_FILTER_POINT_SAMPLE; + filter = STBIR_FILTER_POINT_SAMPLE; else filter = STBIR_DEFAULT_FILTER_UPSAMPLE; } @@ -6313,7 +6492,7 @@ static void stbir__set_sampler(stbir__sampler * samp, stbir_filter filter, stbir samp->filter_enum = filter; STBIR_ASSERT(samp->filter_enum != 0); - STBIR_ASSERT((unsigned)samp->filter_enum < STBIR_FILTER_OTHER); + STBIR_ASSERT((unsigned)samp->filter_enum < STBIR_FILTER_OTHER); samp->filter_kernel = stbir__builtin_kernels[ filter ]; samp->filter_support = stbir__builtin_supports[ filter ]; @@ -6339,15 +6518,31 @@ static void stbir__set_sampler(stbir__sampler * samp, stbir_filter filter, stbir // pre calculate stuff based on the above samp->coefficient_width = stbir__get_coefficient_width(samp, samp->is_gather, user_data); + // filter_pixel_width is the conservative size in pixels of input that affect an output pixel. + // In rare cases (only with 2 pix to 1 pix with the default filters), it's possible that the + // filter will extend before or after the scanline beyond just one extra entire copy of the + // scanline (we would hit the edge twice). We don't let you do that, so we clamp the total + // width to 3x the total of input pixel (once for the scanline, once for the left side + // overhang, and once for the right side). We only do this for edge mode, since the other + // modes can just re-edge clamp back in again. if ( edge == STBIR_EDGE_WRAP ) - if ( samp->filter_pixel_width > ( scale_info->input_full_size * 2 ) ) // this can only happen when shrinking to a single pixel - samp->filter_pixel_width = scale_info->input_full_size * 2; + if ( samp->filter_pixel_width > ( scale_info->input_full_size * 3 ) ) + samp->filter_pixel_width = scale_info->input_full_size * 3; // This is how much to expand buffers to account for filters seeking outside // the image boundaries. samp->filter_pixel_margin = samp->filter_pixel_width / 2; + + // filter_pixel_margin is the amount that this filter can overhang on just one side of either + // end of the scanline (left or the right). Since we only allow you to overhang 1 scanline's + // worth of pixels, we clamp this one side of overhang to the input scanline size. Again, + // this clamping only happens in rare cases with the default filters (2 pix to 1 pix). + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_margin > scale_info->input_full_size ) + samp->filter_pixel_margin = scale_info->input_full_size; samp->num_contributors = stbir__get_contributors(samp, samp->is_gather); + samp->contributors_size = samp->num_contributors * sizeof(stbir__contributors); samp->coefficients_size = samp->num_contributors * samp->coefficient_width * sizeof(float) + sizeof(float); // extra sizeof(float) is padding @@ -6397,8 +6592,8 @@ static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contr range->n0 = in_first_pixel; stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, (float)output_sub_size, 0, inv_scale, out_shift, input_full_size, edge ); range->n1 = in_last_pixel; - - // now go through the margin to the start of area to find bottom + + // now go through the margin to the start of area to find bottom n = range->n0 + 1; input_end = -filter_pixel_margin; while( n >= input_end ) @@ -6413,7 +6608,7 @@ static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contr --n; } - // now go through the end of the area through the margin to find top + // now go through the end of the area through the margin to find top n = range->n1 - 1; input_end = n + 1 + filter_pixel_margin; while( n <= input_end ) @@ -6462,7 +6657,7 @@ static void stbir__get_split_info( stbir__per_split_info* split_info, int splits cur = 0; for( i = 0 ; i < splits ; i++ ) { - int each; + int each; split_info[i].start_output_y = cur; each = left / ( splits - i ); split_info[i].end_output_y = cur + each; @@ -6478,7 +6673,7 @@ static void stbir__get_split_info( stbir__per_split_info* split_info, int splits static void stbir__free_internal_mem( stbir__info *info ) { #define STBIR__FREE_AND_CLEAR( ptr ) { if ( ptr ) { void * p = (ptr); (ptr) = 0; STBIR_FREE( p, info->user_data); } } - + if ( info ) { #ifndef STBIR__SEPARATE_ALLOCATIONS @@ -6496,16 +6691,16 @@ static void stbir__free_internal_mem( stbir__info *info ) for( j = 0 ; j < info->alloc_ring_buffer_num_entries ; j++ ) { #ifdef STBIR_SIMD8 - if ( info->effective_channels == 3 ) + if ( info->effective_channels == 3 ) --info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer - #endif + #endif STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers[j] ); } #ifdef STBIR_SIMD8 - if ( info->effective_channels == 3 ) + if ( info->effective_channels == 3 ) --info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer - #endif + #endif STBIR__FREE_AND_CLEAR( info->split_info[i].decode_buffer ); STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers ); STBIR__FREE_AND_CLEAR( info->split_info[i].vertical_buffer ); @@ -6522,7 +6717,7 @@ static void stbir__free_internal_mem( stbir__info *info ) STBIR__FREE_AND_CLEAR( info ); #endif } - + #undef STBIR__FREE_AND_CLEAR } @@ -6534,20 +6729,20 @@ static int stbir__get_max_split( int splits, int height ) for( i = 0 ; i < splits ; i++ ) { int each = height / ( splits - i ); - if ( each > max ) + if ( each > max ) max = each; height -= each; } return max; } -static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_n_coeffs_funcs[8] = -{ +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_n_coeffs_funcs[8] = +{ 0, stbir__horizontal_gather_1_channels_with_n_coeffs_funcs, stbir__horizontal_gather_2_channels_with_n_coeffs_funcs, stbir__horizontal_gather_3_channels_with_n_coeffs_funcs, stbir__horizontal_gather_4_channels_with_n_coeffs_funcs, 0,0, stbir__horizontal_gather_7_channels_with_n_coeffs_funcs }; -static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_channels_funcs[8] = -{ +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_channels_funcs[8] = +{ 0, stbir__horizontal_gather_1_channels_funcs, stbir__horizontal_gather_2_channels_funcs, stbir__horizontal_gather_3_channels_funcs, stbir__horizontal_gather_4_channels_funcs, 0,0, stbir__horizontal_gather_7_channels_funcs }; @@ -6622,28 +6817,28 @@ static STBIR__V_FIRST_INFO STBIR__V_FIRST_INFO_BUFFER = {0}; #endif // Figure out whether to scale along the horizontal or vertical first. -// This only *super* important when you are scaling by a massively -// different amount in the vertical vs the horizontal (for example, if -// you are scaling by 2x in the width, and 0.5x in the height, then you -// want to do the vertical scale first, because it's around 3x faster +// This only *super* important when you are scaling by a massively +// different amount in the vertical vs the horizontal (for example, if +// you are scaling by 2x in the width, and 0.5x in the height, then you +// want to do the vertical scale first, because it's around 3x faster // in that order. // -// In more normal circumstances, this makes a 20-40% differences, so +// In more normal circumstances, this makes a 20-40% differences, so // it's good to get right, but not critical. The normal way that you -// decide which direction goes first is just figuring out which -// direction does more multiplies. But with modern CPUs with their +// decide which direction goes first is just figuring out which +// direction does more multiplies. But with modern CPUs with their // fancy caches and SIMD and high IPC abilities, so there's just a lot -// more that goes into it. +// more that goes into it. // -// My handwavy sort of solution is to have an app that does a whole +// My handwavy sort of solution is to have an app that does a whole // bunch of timing for both vertical and horizontal first modes, // and then another app that can read lots of these timing files // and try to search for the best weights to use. Dotimings.c // is the app that does a bunch of timings, and vf_train.c is the -// app that solves for the best weights (and shows how well it +// app that solves for the best weights (and shows how well it // does currently). -static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int horizontal_filter_pixel_width, float horizontal_scale, int horizontal_output_size, int vertical_filter_pixel_width, float vertical_scale, int vertical_output_size, int is_gather, STBIR__V_FIRST_INFO * info ) +static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int horizontal_filter_pixel_width, float horizontal_scale, int horizontal_output_size, int vertical_filter_pixel_width, float vertical_scale, int vertical_output_size, int is_gather, STBIR__V_FIRST_INFO * info ) { double v_cost, h_cost; float * weights; @@ -6655,15 +6850,15 @@ static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLA v_classification = ( vertical_output_size < horizontal_output_size ) ? 6 : 7; else if ( vertical_scale <= 1.0f ) v_classification = ( is_gather ) ? 1 : 0; - else if ( vertical_scale <= 2.0f) + else if ( vertical_scale <= 2.0f) v_classification = 2; - else if ( vertical_scale <= 3.0f) + else if ( vertical_scale <= 3.0f) v_classification = 3; - else if ( vertical_scale <= 4.0f) + else if ( vertical_scale <= 4.0f) v_classification = 5; - else + else v_classification = 6; - + // use the right weights weights = weights_table[ v_classification ]; @@ -6684,10 +6879,10 @@ static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLA info->is_gather = is_gather; } - // and this allows us to override everything for testing (see dotiming.c) - if ( ( info ) && ( info->control_v_first ) ) + // and this allows us to override everything for testing (see dotiming.c) + if ( ( info ) && ( info->control_v_first ) ) vertical_first = ( info->control_v_first == 2 ) ? 1 : 0; - + return vertical_first; } @@ -6699,9 +6894,9 @@ static unsigned char stbir__pixel_channels[] = { }; // the internal pixel layout enums are in a different order, so we can easily do range comparisons of types -// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible static stbir_internal_pixel_layout stbir__pixel_layout_convert_public_to_internal[] = { - STBIRI_BGR, STBIRI_1CHANNEL, STBIRI_2CHANNEL, STBIRI_RGB, STBIRI_RGBA, + STBIRI_BGR, STBIRI_1CHANNEL, STBIRI_2CHANNEL, STBIRI_RGB, STBIRI_RGBA, STBIRI_4CHANNEL, STBIRI_BGRA, STBIRI_ARGB, STBIRI_ABGR, STBIRI_RA, STBIRI_AR, STBIRI_RGBA_PM, STBIRI_BGRA_PM, STBIRI_ARGB_PM, STBIRI_ABGR_PM, STBIRI_RA_PM, STBIRI_AR_PM, }; @@ -6712,17 +6907,17 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample stbir__info * info = 0; void * alloced = 0; - int alloced_total = 0; + size_t alloced_total = 0; int vertical_first; int decode_buffer_size, ring_buffer_length_bytes, ring_buffer_size, vertical_buffer_size, alloc_ring_buffer_num_entries; int alpha_weighting_type = 0; // 0=none, 1=simple, 2=fancy - int conservative_split_output_size = stbir__get_max_split( splits, vertical->scale_info.output_sub_size ); - stbir_internal_pixel_layout input_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ input_pixel_layout_public ]; + int conservative_split_output_size = stbir__get_max_split( splits, vertical->scale_info.output_sub_size ); + stbir_internal_pixel_layout input_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ input_pixel_layout_public ]; stbir_internal_pixel_layout output_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ output_pixel_layout_public ]; - int channels = stbir__pixel_channels[ input_pixel_layout ]; + int channels = stbir__pixel_channels[ input_pixel_layout ]; int effective_channels = channels; - + // first figure out what type of alpha weighting to use (if any) if ( ( horizontal->filter_enum != STBIR_FILTER_POINT_SAMPLE ) || ( vertical->filter_enum != STBIR_FILTER_POINT_SAMPLE ) ) // no alpha weighting on point sampling { @@ -6760,11 +6955,11 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample // sometimes read one float off in some of the unrolled loops (with a weight of zero coeff, so it doesn't have an effect) decode_buffer_size = ( conservative->n1 - conservative->n0 + 1 ) * effective_channels * sizeof(float) + sizeof(float); // extra float for padding - + #if defined( STBIR__SEPARATE_ALLOCATIONS ) && defined(STBIR_SIMD8) if ( effective_channels == 3 ) decode_buffer_size += sizeof(float); // avx in 3 channel mode needs one float at the start of the buffer (only with separate allocations) -#endif +#endif ring_buffer_length_bytes = horizontal->scale_info.output_sub_size * effective_channels * sizeof(float) + sizeof(float); // extra float for padding @@ -6803,9 +6998,9 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = ((char*)advance_mem) + (size); #endif - STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); + STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); - STBIR__NEXT_PTR( info->split_info, sizeof( stbir__per_split_info ) * splits, stbir__per_split_info ); + STBIR__NEXT_PTR( info->split_info, sizeof( stbir__per_split_info ) * splits, stbir__per_split_info ); if ( info ) { @@ -6820,39 +7015,39 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample info->channels = channels; info->effective_channels = effective_channels; - + info->offset_x = new_x; info->offset_y = new_y; info->alloc_ring_buffer_num_entries = alloc_ring_buffer_num_entries; - info->ring_buffer_num_entries = 0; + info->ring_buffer_num_entries = 0; info->ring_buffer_length_bytes = ring_buffer_length_bytes; info->splits = splits; info->vertical_first = vertical_first; - info->input_pixel_layout_internal = input_pixel_layout; + info->input_pixel_layout_internal = input_pixel_layout; info->output_pixel_layout_internal = output_pixel_layout; // setup alpha weight functions info->alpha_weight = 0; info->alpha_unweight = 0; - + // handle alpha weighting functions and overrides if ( alpha_weighting_type == 2 ) { // high quality alpha multiplying on the way in, dividing on the way out - info->alpha_weight = fancy_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_weight = fancy_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; info->alpha_unweight = fancy_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; } else if ( alpha_weighting_type == 4 ) { // fast alpha multiplying on the way in, dividing on the way out - info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; } else if ( alpha_weighting_type == 1 ) { // fast alpha on the way in, leave in premultiplied form on way out - info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; } else if ( alpha_weighting_type == 3 ) { @@ -6871,7 +7066,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample info->alpha_weight = stbir__simple_flip_3ch; } - } + } // get all the per-split buffers for( i = 0 ; i < splits ; i++ ) @@ -6883,7 +7078,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample #ifdef STBIR_SIMD8 if ( ( info ) && ( effective_channels == 3 ) ) ++info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer - #endif + #endif STBIR__NEXT_PTR( info->split_info[i].ring_buffers, alloc_ring_buffer_num_entries * sizeof(float*), float* ); { @@ -6894,7 +7089,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample #ifdef STBIR_SIMD8 if ( ( info ) && ( effective_channels == 3 ) ) ++info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer - #endif + #endif } } #else @@ -6922,10 +7117,10 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample #endif if ( temp_mem_amt >= both ) { - if ( info ) - { - vertical->gather_prescatter_contributors = (stbir__contributors*)info->split_info[0].decode_buffer; - vertical->gather_prescatter_coefficients = (float*) ( ( (char*)info->split_info[0].decode_buffer ) + vertical->gather_prescatter_contributors_size ); + if ( info ) + { + vertical->gather_prescatter_contributors = (stbir__contributors*)info->split_info[0].decode_buffer; + vertical->gather_prescatter_coefficients = (float*) ( ( (char*)info->split_info[0].decode_buffer ) + vertical->gather_prescatter_contributors_size ); } } else @@ -6948,7 +7143,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample if ( diff_shift < 0.0f ) diff_shift = -diff_shift; if ( ( diff_scale <= stbir__small_float ) && ( diff_shift <= stbir__small_float ) ) { - if ( horizontal->is_gather == vertical->is_gather ) + if ( horizontal->is_gather == vertical->is_gather ) { copy_horizontal = 1; goto no_vert_alloc; @@ -6975,16 +7170,16 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample // but if the number of coeffs <= 12, use another set of special cases. <=12 coeffs is any enlarging resize, or shrinking resize down to about 1/3 size if ( horizontal->extent_info.widest <= 12 ) info->horizontal_gather_channels = stbir__horizontal_gather_channels_funcs[ effective_channels ][ horizontal->extent_info.widest - 1 ]; - + info->scanline_extents.conservative.n0 = conservative->n0; info->scanline_extents.conservative.n1 = conservative->n1; - + // get exact extents stbir__get_extents( horizontal, &info->scanline_extents ); // pack the horizontal coeffs - horizontal->coefficient_width = stbir__pack_coefficients(horizontal->num_contributors, horizontal->contributors, horizontal->coefficients, horizontal->coefficient_width, horizontal->extent_info.widest, info->scanline_extents.conservative.n1 + 1 ); - + horizontal->coefficient_width = stbir__pack_coefficients(horizontal->num_contributors, horizontal->contributors, horizontal->coefficients, horizontal->coefficient_width, horizontal->extent_info.widest, info->scanline_extents.conservative.n0, info->scanline_extents.conservative.n1 ); + STBIR_MEMCPY( &info->horizontal, horizontal, sizeof( stbir__sampler ) ); STBIR_PROFILE_BUILD_END( horizontal ); @@ -7014,36 +7209,27 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample info->ring_buffer_num_entries = conservative_split_output_size; STBIR_ASSERT( info->ring_buffer_num_entries <= info->alloc_ring_buffer_num_entries ); - // a few of the horizontal gather functions read one dword past the end (but mask it out), so put in a normal value so no snans or denormals accidentally sneak in + // a few of the horizontal gather functions read past the end of the decode (but mask it out), + // so put in normal values so no snans or denormals accidentally sneak in (also, in the ring + // buffer for vertical first) for( i = 0 ; i < splits ; i++ ) { - int width, ofs; - - // find the right most span - if ( info->scanline_extents.spans[0].n1 > info->scanline_extents.spans[1].n1 ) - width = info->scanline_extents.spans[0].n1 - info->scanline_extents.spans[0].n0; - else - width = info->scanline_extents.spans[1].n1 - info->scanline_extents.spans[1].n0; - - // this calc finds the exact end of the decoded scanline for all filter modes. - // usually this is just the width * effective channels. But we have to account - // for the area to the left of the scanline for wrap filtering and alignment, this - // is stored as a negative value in info->scanline_extents.conservative.n0. Next, - // we need to skip the exact size of the right hand size filter area (again for - // wrap mode), this is in info->scanline_extents.edge_sizes[1]). - ofs = ( width + 1 - info->scanline_extents.conservative.n0 + info->scanline_extents.edge_sizes[1] ) * effective_channels; - - // place a known, but numerically valid value in the decode buffer - info->split_info[i].decode_buffer[ ofs ] = 9999.0f; - - // if vertical filtering first, place a known, but numerically valid value in the all - // of the ring buffer accumulators + int t, ofs, start; + + ofs = decode_buffer_size / 4; + start = ofs - 4; + if ( start < 0 ) start = 0; + + for( t = start ; t < ofs; t++ ) + info->split_info[i].decode_buffer[ t ] = 9999.0f; + if ( vertical_first ) { - int j; + int j; for( j = 0; j < info->ring_buffer_num_entries ; j++ ) { - stbir__get_ring_buffer_entry( info, info->split_info + i, j )[ ofs ] = 9999.0f; + for( t = start ; t < ofs; t++ ) + stbir__get_ring_buffer_entry( info, info->split_info + i, j )[ t ] = 9999.0f; } } } @@ -7055,7 +7241,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample // is this the first time through loop? if ( info == 0 ) { - alloced_total = (int) ( 15 + (size_t)advance_mem ); + alloced_total = ( 15 + (size_t)advance_mem ); alloced = STBIR_MALLOC( alloced_total, user_data ); if ( alloced == 0 ) return 0; @@ -7065,7 +7251,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample } } -static int stbir__perform_resize( stbir__info const * info, int split_start, int split_count ) +static int stbir__perform_resize( stbir__info const * info, int split_start, int split_count ) { stbir__per_split_info * split_info = info->split_info + split_start; @@ -7085,7 +7271,7 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r { static stbir__decode_pixels_func * decode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= { - /* 1ch-4ch */ stbir__decode_uint8_srgb, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear, + /* 1ch-4ch */ stbir__decode_uint8_srgb, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear, }; static stbir__decode_pixels_func * decode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= @@ -7148,7 +7334,7 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r stbir_datatype input_type, output_type; input_type = resize->input_data_type; - output_type = resize->output_data_type; + output_type = resize->output_data_type; info->input_data = resize->input_pixels; info->input_stride_bytes = resize->input_stride_in_bytes; info->output_stride_bytes = resize->output_stride_in_bytes; @@ -7156,7 +7342,7 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r // if we're completely point sampling, then we can turn off SRGB if ( ( info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( info->vertical.filter_enum == STBIR_FILTER_POINT_SAMPLE ) ) { - if ( ( ( input_type == STBIR_TYPE_UINT8_SRGB ) || ( input_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) && + if ( ( ( input_type == STBIR_TYPE_UINT8_SRGB ) || ( input_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) && ( ( output_type == STBIR_TYPE_UINT8_SRGB ) || ( output_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) ) { input_type = STBIR_TYPE_UINT8; @@ -7164,7 +7350,7 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r } } - // recalc the output and input strides + // recalc the output and input strides if ( info->input_stride_bytes == 0 ) info->input_stride_bytes = info->channels * info->horizontal.scale_info.input_full_size * stbir__type_size[input_type]; @@ -7172,7 +7358,7 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r info->output_stride_bytes = info->channels * info->horizontal.scale_info.output_sub_size * stbir__type_size[output_type]; // calc offset - info->output_data = ( (char*) resize->output_pixels ) + ( (ptrdiff_t) info->offset_y * (ptrdiff_t) resize->output_stride_in_bytes ) + ( info->offset_x * info->channels * stbir__type_size[output_type] ); + info->output_data = ( (char*) resize->output_pixels ) + ( (size_t) info->offset_y * (size_t) resize->output_stride_in_bytes ) + ( info->offset_x * info->channels * stbir__type_size[output_type] ); info->in_pixels_cb = resize->input_cb; info->user_data = resize->user_data; @@ -7205,7 +7391,7 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r if ( ( output_type == STBIR_TYPE_UINT8 ) || ( output_type == STBIR_TYPE_UINT16 ) ) { int non_scaled = 0; - + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) @@ -7225,16 +7411,16 @@ static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * r } info->input_type = input_type; - info->output_type = output_type; + info->output_type = output_type; info->decode_pixels = decode_pixels; - info->encode_pixels = encode_pixels; + info->encode_pixels = encode_pixels; } static void stbir__clip( int * outx, int * outsubw, int outw, double * u0, double * u1 ) { double per, adj; int over; - + // do left/top edge if ( *outx < 0 ) { @@ -7253,7 +7439,7 @@ static void stbir__clip( int * outx, int * outsubw, int outw, double * u0, doubl *u1 += adj; // decrease u1 *outsubw = outw - *outx; } -} +} // converts a double to a rational that has less than one float bit of error (returns 0 if unable to do so) static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 *numer, stbir_uint32 *denom, int limit_denom ) // limit_denom (1) or limit numer (0) @@ -7270,7 +7456,7 @@ static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 bot = 1 << 25; // keep refining, but usually stops in a few loops - usually 5 for bad cases - for(;;) + for(;;) { stbir_uint64 est, temp; @@ -7303,13 +7489,13 @@ static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 bot = temp; // move remainders - temp = est * denom_estimate + denom_last; - denom_last = denom_estimate; + temp = est * denom_estimate + denom_last; + denom_last = denom_estimate; denom_estimate = temp; // move remainders - temp = est * numer_estimate + numer_last; - numer_last = numer_estimate; + temp = est * numer_estimate + numer_last; + numer_last = numer_estimate; numer_estimate = temp; } @@ -7353,11 +7539,11 @@ static int stbir__calculate_region_transform( stbir__scale_info * scale_info, in output_s = ( (double)output_sub_range) / output_range; - // figure out the scaling to use - ratio = output_s / input_s; + // figure out the scaling to use + ratio = output_s / input_s; // save scale before clipping - scale = ( output_range / input_range ) * ratio; + scale = ( output_range / input_range ) * ratio; scale_info->scale = (float)scale; scale_info->inv_scale = (float)( 1.0 / scale ); @@ -7368,11 +7554,11 @@ static int stbir__calculate_region_transform( stbir__scale_info * scale_info, in input_s = input_s1 - input_s0; // after clipping do we have zero input area? - if ( input_s <= stbir__small_float ) + if ( input_s <= stbir__small_float ) return 0; - // calculate and store the starting source offsets in output pixel space - scale_info->pixel_shift = (float) ( input_s0 * ratio * output_range ); + // calculate and store the starting source offsets in output pixel space + scale_info->pixel_shift = (float) ( input_s0 * ratio * output_range ); scale_info->scale_is_rational = stbir__double_to_rational( scale, ( scale <= 1.0 ) ? output_full_range : input_full_range, &scale_info->scale_numerator, &scale_info->scale_denominator, ( scale >= 1.0 ) ); @@ -7389,7 +7575,6 @@ static void stbir__init_and_set_layout( STBIR_RESIZE * resize, stbir_pixel_layou resize->output_cb = 0; resize->user_data = resize; resize->samplers = 0; - resize->needs_rebuild = 1; resize->called_alloc = 0; resize->horizontal_filter = STBIR_FILTER_DEFAULT; resize->horizontal_filter_kernel = 0; resize->horizontal_filter_support = 0; @@ -7403,9 +7588,10 @@ static void stbir__init_and_set_layout( STBIR_RESIZE * resize, stbir_pixel_layou resize->output_data_type = data_type; resize->input_pixel_layout_public = pixel_layout; resize->output_pixel_layout_public = pixel_layout; + resize->needs_rebuild = 1; } -STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero stbir_pixel_layout pixel_layout, stbir_datatype data_type ) @@ -7428,17 +7614,27 @@ STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_t { resize->input_data_type = input_type; resize->output_data_type = output_type; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); } STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ) // no callbacks by default { resize->input_cb = input_cb; resize->output_cb = output_cb; + + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + { + resize->samplers->in_pixels_cb = input_cb; + resize->samplers->out_pixels_cb = output_cb; + } } STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ) // pass back STBIR_RESIZE* by default { resize->user_data = user_data; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + resize->samplers->user_data = user_data; } STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ) @@ -7447,6 +7643,8 @@ STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_p resize->input_stride_in_bytes = input_stride_in_bytes; resize->output_pixels = output_pixels; resize->output_stride_in_bytes = output_stride_in_bytes; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); } @@ -7549,7 +7747,7 @@ STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, return 1; } -static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) +static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) { stbir__contributors conservative = { 0, 0 }; stbir__sampler horizontal, vertical; @@ -7563,13 +7761,13 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) // have we already built the samplers? if ( resize->samplers ) return 0; - + #define STBIR_RETURN_ERROR_AND_ASSERT( exp ) STBIR_ASSERT( !(exp) ); if (exp) return 0; STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->horizontal_filter >= STBIR_FILTER_OTHER) STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->vertical_filter >= STBIR_FILTER_OTHER) #undef STBIR_RETURN_ERROR_AND_ASSERT - if ( splits <= 0 ) + if ( splits <= 0 ) return 0; STBIR_PROFILE_BUILD_FIRST_START( build ); @@ -7593,9 +7791,9 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); stbir__set_sampler(&vertical, resize->vertical_filter, resize->horizontal_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); - if ( ( vertical.scale_info.output_sub_size / splits ) < 4 ) // each split should be a minimum of 4 scanlines (handwavey choice) + if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) { - splits = vertical.scale_info.output_sub_size / 4; + splits = vertical.scale_info.output_sub_size / STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS; if ( splits == 0 ) splits = 1; } @@ -7603,7 +7801,7 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) out_info = stbir__alloc_internal_mem_and_build_samplers( &horizontal, &vertical, &conservative, resize->input_pixel_layout_public, resize->output_pixel_layout_public, splits, new_output_subx, new_output_suby, resize->fast_alpha, resize->user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); STBIR_PROFILE_BUILD_END( alloc ); STBIR_PROFILE_BUILD_END( build ); - + if ( out_info ) { resize->splits = splits; @@ -7612,6 +7810,10 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) #ifdef STBIR_PROFILE STBIR_MEMCPY( &out_info->profile, &profile_infod.profile, sizeof( out_info->profile ) ); #endif + + // update anything that can be changed without recalcing samplers + stbir__update_info_from_resize( out_info, resize ); + return splits; } @@ -7640,7 +7842,7 @@ STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int splits } STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); - + return 1; } @@ -7652,7 +7854,7 @@ STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ) STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) { int result; - + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) { int alloc_state = resize->called_alloc; // remember allocated state @@ -7665,10 +7867,10 @@ STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) if ( !stbir_build_samplers( resize ) ) return 0; - + resize->called_alloc = alloc_state; - // if build_samplers succeeded (above), but there are no samplers set, then + // if build_samplers succeeded (above), but there are no samplers set, then // the area to stretch into was zero pixels, so don't do anything and return // success if ( resize->samplers == 0 ) @@ -7680,10 +7882,6 @@ STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); } - - // update anything that can be changed without recalcing samplers - stbir__update_info_from_resize( resize->samplers, resize ); - // do resize result = stbir__perform_resize( resize->samplers, 0, resize->splits ); @@ -7692,7 +7890,7 @@ STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) { stbir_free_samplers( resize ); resize->samplers = 0; - } + } return result; } @@ -7707,14 +7905,11 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start // you **must** build samplers first when using split resize if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) - return 0; - + return 0; + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) return 0; - - // update anything that can be changed without recalcing samplers - stbir__update_info_from_resize( resize->samplers, resize ); - + // do resize return stbir__perform_resize( resize->samplers, split_start, split_count ); } @@ -7735,7 +7930,7 @@ static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * o if ( output_stride_in_bytes < pitch ) return 0; - size = output_stride_in_bytes * output_h; + size = (size_t)output_stride_in_bytes * (size_t)output_h; if ( size == 0 ) return 0; @@ -7752,7 +7947,7 @@ static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * o *ret_pitch = pitch; } - return 1; + return 1; } @@ -7767,9 +7962,9 @@ STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_p if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) return 0; - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, pixel_layout, STBIR_TYPE_UINT8 ); if ( !stbir_resize_extended( &resize ) ) @@ -7793,9 +7988,9 @@ STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pix if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) return 0; - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, pixel_layout, STBIR_TYPE_UINT8_SRGB ); if ( !stbir_resize_extended( &resize ) ) @@ -7820,9 +8015,9 @@ STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int inpu if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( float ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) return 0; - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, pixel_layout, STBIR_TYPE_FLOAT ); if ( !stbir_resize_extended( &resize ) ) @@ -7838,7 +8033,7 @@ STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int inpu STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, stbir_edge edge, stbir_filter filter ) { STBIR_RESIZE resize; @@ -7848,9 +8043,9 @@ STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, stbir__type_size[data_type], output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) return 0; - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, pixel_layout, data_type ); resize.horizontal_edge = edge; @@ -7958,7 +8153,7 @@ STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * info, STB #else // STB_IMAGE_RESIZE_HORIZONTALS&STB_IMAGE_RESIZE_DO_VERTICALS // we reinclude the header file to define all the horizontal functions -// specializing each function for the number of coeffs is 20-40% faster *OVERALL* +// specializing each function for the number of coeffs is 20-40% faster *OVERALL* // by including the header file again this way, we can still debug the functions @@ -7991,16 +8186,16 @@ STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * info, STB #define stbir__encode_order2 2 #define stbir__encode_order3 3 #define stbir__decode_simdf8_flip(reg) -#define stbir__decode_simdf4_flip(reg) +#define stbir__decode_simdf4_flip(reg) #define stbir__encode_simdf8_unflip(reg) -#define stbir__encode_simdf4_unflip(reg) +#define stbir__encode_simdf4_unflip(reg) #endif #ifdef STBIR_SIMD8 #define stbir__encode_simdfX_unflip stbir__encode_simdf8_unflip #else #define stbir__encode_simdfX_unflip stbir__encode_simdf4_unflip -#endif +#endif static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * decodep, int width_times_channels, void const * inputp ) { @@ -8013,6 +8208,7 @@ static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * deco if ( width_times_channels >= 16 ) { decode_end -= 16; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { #ifdef STBIR_SIMD8 @@ -8054,7 +8250,7 @@ static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * deco #endif decode += 16; input += 16; - if ( decode <= decode_end ) + if ( decode <= decode_end ) continue; if ( decode == ( decode_end + 16 ) ) break; @@ -8068,6 +8264,7 @@ static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * deco // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode <= decode_end ) { STBIR_SIMD_NO_UNROLL(decode); @@ -8083,6 +8280,7 @@ static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * deco // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -8109,6 +8307,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outpu { float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; end_output -= stbir__simdfX_float_count*2; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdfX e0, e1; @@ -8119,15 +8318,15 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outpu stbir__encode_simdfX_unflip( e0 ); stbir__encode_simdfX_unflip( e1 ); #ifdef STBIR_SIMD8 - stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); stbir__simdi_store( output, i ); #else - stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdf_pack_to_8bytes( i, e0, e1 ); stbir__simdi_store2( output, i ); #endif encode += stbir__simdfX_float_count*2; output += stbir__simdfX_float_count*2; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) break; @@ -8140,6 +8339,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outpu // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_NO_UNROLL_LOOP_START while( output <= end_output ) { stbir__simdf e0; @@ -8158,9 +8358,10 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outpu // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { - stbir__simdf e0; + stbir__simdf e0; STBIR_NO_UNROLL(encode); stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_uint8( e0 ); #if stbir__coder_min_num >= 2 @@ -8173,7 +8374,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outpu encode += stbir__coder_min_num; } #endif - + #else // try to do blocks of 4 when you can @@ -8194,6 +8395,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outpu // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { float f; @@ -8223,6 +8425,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int if ( width_times_channels >= 16 ) { decode_end -= 16; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { #ifdef STBIR_SIMD8 @@ -8258,7 +8461,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int #endif decode += 16; input += 16; - if ( decode <= decode_end ) + if ( decode <= decode_end ) continue; if ( decode == ( decode_end + 16 ) ) break; @@ -8272,6 +8475,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode <= decode_end ) { STBIR_SIMD_NO_UNROLL(decode); @@ -8287,6 +8491,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -8313,6 +8518,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int { float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; end_output -= stbir__simdfX_float_count*2; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdfX e0, e1; @@ -8323,15 +8529,15 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int stbir__encode_simdfX_unflip( e0 ); stbir__encode_simdfX_unflip( e1 ); #ifdef STBIR_SIMD8 - stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); stbir__simdi_store( output, i ); #else - stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdf_pack_to_8bytes( i, e0, e1 ); stbir__simdi_store2( output, i ); #endif encode += stbir__simdfX_float_count*2; output += stbir__simdfX_float_count*2; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) break; @@ -8344,6 +8550,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_NO_UNROLL_LOOP_START while( output <= end_output ) { stbir__simdf e0; @@ -8382,6 +8589,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { float f; @@ -8422,6 +8630,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int wi // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -8441,7 +8650,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int wi #define stbir__min_max_shift20( i, f ) \ stbir__simdf_max( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_zero )) ); \ stbir__simdf_min( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_one )) ); \ - stbir__simdi_32shr( i, stbir_simdi_castf( f ), 20 ); + stbir__simdi_32shr( i, stbir_simdi_castf( f ), 20 ); #define stbir__scale_and_convert( i, f ) \ stbir__simdf_madd( f, STBIR__CONSTF( STBIR_simd_point5 ), STBIR__CONSTF( STBIR_max_uint8_as_float ), f ); \ @@ -8468,7 +8677,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int wi temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ v0 = temp0.m128i_i128; \ v1 = temp1.m128i_i128; \ -} +} #define stbir__simdi_table_lookup3( v0,v1,v2, table ) \ { \ @@ -8499,7 +8708,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int wi v1 = temp1.m128i_i128; \ v2 = temp2.m128i_i128; \ v3 = temp3.m128i_i128; \ -} +} static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int width_times_channels, float const * encode ) { @@ -8507,16 +8716,16 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int w unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; #ifdef STBIR_SIMD - stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; if ( width_times_channels >= 16 ) { float const * end_encode_m16 = encode + width_times_channels - 16; end_output -= 16; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdf f0, f1, f2, f3; - stbir__simdi i0, i1, i2, i3; + stbir__simdi i0, i1, i2, i3; STBIR_SIMD_NO_UNROLL(encode); stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); @@ -8525,9 +8734,9 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int w stbir__min_max_shift20( i1, f1 ); stbir__min_max_shift20( i2, f2 ); stbir__min_max_shift20( i3, f3 ); - - stbir__simdi_table_lookup4( i0, i1, i2, i3, to_srgb ); - + + stbir__simdi_table_lookup4( i0, i1, i2, i3, ( fp32_to_srgb8_tab4 - (127-13)*8 ) ); + stbir__linear_to_srgb_finish( i0, f0 ); stbir__linear_to_srgb_finish( i1, f1 ); stbir__linear_to_srgb_finish( i2, f2 ); @@ -8537,7 +8746,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int w encode += 16; output += 16; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + 16 ) ) break; @@ -8551,6 +8760,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int w // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while ( output <= end_output ) { STBIR_SIMD_NO_UNROLL(encode); @@ -8568,7 +8778,8 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int w // do the remnants #if stbir__coder_min_num < 4 - while( output < end_output ) + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) { STBIR_NO_UNROLL(encode); output[0] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); @@ -8608,12 +8819,12 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * o unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; #ifdef STBIR_SIMD - stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; if ( width_times_channels >= 16 ) { float const * end_encode_m16 = encode + width_times_channels - 16; end_output -= 16; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdf f0, f1, f2, f3; @@ -8625,10 +8836,10 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * o stbir__min_max_shift20( i0, f0 ); stbir__min_max_shift20( i1, f1 ); stbir__min_max_shift20( i2, f2 ); - stbir__scale_and_convert( i3, f3 ); - - stbir__simdi_table_lookup3( i0, i1, i2, to_srgb ); - + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup3( i0, i1, i2, ( fp32_to_srgb8_tab4 - (127-13)*8 ) ); + stbir__linear_to_srgb_finish( i0, f0 ); stbir__linear_to_srgb_finish( i1, f1 ); stbir__linear_to_srgb_finish( i2, f2 ); @@ -8638,7 +8849,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * o output += 16; encode += 16; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + 16 ) ) break; @@ -8649,9 +8860,10 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * o } #endif + STBIR_SIMD_NO_UNROLL_LOOP_START do { float f; - STBIR_SIMD_NO_UNROLL(encode); + STBIR_SIMD_NO_UNROLL(encode); output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); output[stbir__decode_order1] = stbir__linear_to_srgb_uchar( encode[1] ); @@ -8686,7 +8898,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint8_srgb2_linearalpha)( float * de decode += 4; } decode -= 4; - if( decode < decode_end ) + if( decode < decode_end ) { decode[0] = stbir__srgb_uchar_to_linear_float[ stbir__decode_order0 ]; decode[1] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; @@ -8699,16 +8911,16 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * o unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; #ifdef STBIR_SIMD - stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; if ( width_times_channels >= 16 ) { float const * end_encode_m16 = encode + width_times_channels - 16; end_output -= 16; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdf f0, f1, f2, f3; - stbir__simdi i0, i1, i2, i3; + stbir__simdi i0, i1, i2, i3; STBIR_SIMD_NO_UNROLL(encode); stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); @@ -8717,9 +8929,9 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * o stbir__scale_and_convert( i1, f1 ); stbir__min_max_shift20( i2, f2 ); stbir__scale_and_convert( i3, f3 ); - - stbir__simdi_table_lookup2( i0, i2, to_srgb ); - + + stbir__simdi_table_lookup2( i0, i2, ( fp32_to_srgb8_tab4 - (127-13)*8 ) ); + stbir__linear_to_srgb_finish( i0, f0 ); stbir__linear_to_srgb_finish( i2, f2 ); @@ -8727,7 +8939,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * o output += 16; encode += 16; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + 16 ) ) break; @@ -8738,6 +8950,7 @@ static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * o } #endif + STBIR_SIMD_NO_UNROLL_LOOP_START do { float f; STBIR_SIMD_NO_UNROLL(encode); @@ -8766,6 +8979,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decod if ( width_times_channels >= 8 ) { decode_end -= 8; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { #ifdef STBIR_SIMD8 @@ -8793,9 +9007,9 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decod stbir__simdf_store( decode + 0, of0 ); stbir__simdf_store( decode + 4, of1 ); #endif - decode += 8; + decode += 8; input += 8; - if ( decode <= decode_end ) + if ( decode <= decode_end ) continue; if ( decode == ( decode_end + 8 ) ) break; @@ -8809,6 +9023,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decod // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode <= decode_end ) { STBIR_SIMD_NO_UNROLL(decode); @@ -8824,6 +9039,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decod // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -8852,6 +9068,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * output { float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; end_output -= stbir__simdfX_float_count*2; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdfX e0, e1; @@ -8865,7 +9082,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * output stbir__simdiX_store( output, i ); encode += stbir__simdfX_float_count*2; output += stbir__simdfX_float_count*2; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) break; @@ -8879,6 +9096,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * output // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_NO_UNROLL_LOOP_START while( output <= end_output ) { stbir__simdf e; @@ -8897,6 +9115,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * output // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { stbir__simdf e; @@ -8912,12 +9131,13 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * output encode += stbir__coder_min_num; } #endif - + #else // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( output <= end_output ) { float f; @@ -8934,6 +9154,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * output // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { float f; @@ -8963,6 +9184,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int if ( width_times_channels >= 8 ) { decode_end -= 8; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { #ifdef STBIR_SIMD8 @@ -8989,7 +9211,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int #endif decode += 8; input += 8; - if ( decode <= decode_end ) + if ( decode <= decode_end ) continue; if ( decode == ( decode_end + 8 ) ) break; @@ -9003,6 +9225,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode <= decode_end ) { STBIR_SIMD_NO_UNROLL(decode); @@ -9018,6 +9241,7 @@ static void STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -9045,6 +9269,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int { float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; end_output -= stbir__simdfX_float_count*2; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdfX e0, e1; @@ -9058,7 +9283,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int stbir__simdiX_store( output, i ); encode += stbir__simdfX_float_count*2; output += stbir__simdfX_float_count*2; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) break; @@ -9072,6 +9297,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_NO_UNROLL_LOOP_START while( output <= end_output ) { stbir__simdf e; @@ -9093,6 +9319,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( output <= end_output ) { float f; @@ -9111,6 +9338,7 @@ static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { float f; @@ -9139,6 +9367,7 @@ static void STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, { stbir__FP16 const * end_input_m8 = input + width_times_channels - 8; decode_end -= 8; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { STBIR_NO_UNROLL(decode); @@ -9166,7 +9395,7 @@ static void STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, #endif decode += 8; input += 8; - if ( decode <= decode_end ) + if ( decode <= decode_end ) continue; if ( decode == ( decode_end + 8 ) ) break; @@ -9180,6 +9409,7 @@ static void STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode <= decode_end ) { STBIR_SIMD_NO_UNROLL(decode); @@ -9195,6 +9425,7 @@ static void STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -9221,6 +9452,7 @@ static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp { float const * end_encode_m8 = encode + width_times_channels - 8; end_output -= 8; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { STBIR_SIMD_NO_UNROLL(encode); @@ -9247,7 +9479,7 @@ static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp #endif encode += 8; output += 8; - if ( output <= end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + 8 ) ) break; @@ -9261,6 +9493,7 @@ static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( output <= end_output ) { STBIR_SIMD_NO_UNROLL(output); @@ -9276,6 +9509,7 @@ static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { STBIR_NO_UNROLL(output); @@ -9304,6 +9538,7 @@ static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int { float const * end_input_m16 = input + width_times_channels - 16; decode_end -= 16; + STBIR_NO_UNROLL_LOOP_START_INF_FOR for(;;) { STBIR_NO_UNROLL(decode); @@ -9338,7 +9573,7 @@ static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int #endif decode += 16; input += 16; - if ( decode <= decode_end ) + if ( decode <= decode_end ) continue; if ( decode == ( decode_end + 16 ) ) break; @@ -9352,6 +9587,7 @@ static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( decode <= decode_end ) { STBIR_SIMD_NO_UNROLL(decode); @@ -9367,6 +9603,7 @@ static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( decode < decode_end ) { STBIR_NO_UNROLL(decode); @@ -9383,10 +9620,10 @@ static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int #endif #else - + if ( (void*)decodep != inputp ) STBIR_MEMCPY( decodep, inputp, width_times_channels * sizeof( float ) ); - + #endif } @@ -9426,6 +9663,7 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int { float const * end_encode_m8 = encode + width_times_channels - ( stbir__simdfX_float_count * 2 ); end_output -= ( stbir__simdfX_float_count * 2 ); + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR for(;;) { stbir__simdfX e0, e1; @@ -9435,18 +9673,18 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int #ifdef STBIR_FLOAT_HIGH_CLAMP stbir__simdfX_min( e0, e0, high_clamp ); stbir__simdfX_min( e1, e1, high_clamp ); -#endif +#endif #ifdef STBIR_FLOAT_LOW_CLAMP stbir__simdfX_max( e0, e0, low_clamp ); stbir__simdfX_max( e1, e1, low_clamp ); -#endif +#endif stbir__encode_simdfX_unflip( e0 ); stbir__encode_simdfX_unflip( e1 ); stbir__simdfX_store( output, e0 ); stbir__simdfX_store( output+stbir__simdfX_float_count, e1 ); encode += stbir__simdfX_float_count * 2; output += stbir__simdfX_float_count * 2; - if ( output < end_output ) + if ( output < end_output ) continue; if ( output == ( end_output + ( stbir__simdfX_float_count * 2 ) ) ) break; @@ -9459,6 +9697,7 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_NO_UNROLL_LOOP_START while( output <= end_output ) { stbir__simdf e0; @@ -9466,10 +9705,10 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int stbir__simdf_load( e0, encode ); #ifdef STBIR_FLOAT_HIGH_CLAMP stbir__simdf_min( e0, e0, high_clamp ); -#endif +#endif #ifdef STBIR_FLOAT_LOW_CLAMP stbir__simdf_max( e0, e0, low_clamp ); -#endif +#endif stbir__encode_simdf4_unflip( e0 ); stbir__simdf_store( output-4, e0 ); output += 4; @@ -9483,6 +9722,7 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int // try to do blocks of 4 when you can #if stbir__coder_min_num != 3 // doesn't divide cleanly by four output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START while( output <= end_output ) { float e; @@ -9502,6 +9742,7 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int // do the remnants #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START while( output < end_output ) { float e; @@ -9517,18 +9758,18 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int encode += stbir__coder_min_num; } #endif - + #endif } -#undef stbir__decode_suffix +#undef stbir__decode_suffix #undef stbir__decode_simdf8_flip #undef stbir__decode_simdf4_flip -#undef stbir__decode_order0 +#undef stbir__decode_order0 #undef stbir__decode_order1 #undef stbir__decode_order2 #undef stbir__decode_order3 -#undef stbir__encode_order0 +#undef stbir__encode_order0 #undef stbir__encode_order1 #undef stbir__encode_order2 #undef stbir__encode_order3 @@ -9612,7 +9853,8 @@ static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** output stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) - while ( ( (char*)input_end - (char*) input ) >= (16*stbir__simdfX_float_count) ) + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input_end - (char*) input ) >= (16*stbir__simdfX_float_count) ) { stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; STBIR_SIMD_NO_UNROLL(output0); @@ -9621,52 +9863,53 @@ static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** output #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE stbIF0( stbir__simdfX_load( o0, output0 ); stbir__simdfX_load( o1, output0+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output0+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) stbIF1( stbir__simdfX_load( o0, output1 ); stbir__simdfX_load( o1, output1+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output1+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) stbIF2( stbir__simdfX_load( o0, output2 ); stbir__simdfX_load( o1, output2+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output2+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) stbIF3( stbir__simdfX_load( o0, output3 ); stbir__simdfX_load( o1, output3+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output3+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) stbIF4( stbir__simdfX_load( o0, output4 ); stbir__simdfX_load( o1, output4+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output4+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) stbIF5( stbir__simdfX_load( o0, output5 ); stbir__simdfX_load( o1, output5+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output5+(2*stbir__simdfX_float_count)); stbir__simdfX_load( o3, output5+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) stbIF6( stbir__simdfX_load( o0, output6 ); stbir__simdfX_load( o1, output6+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output6+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) stbIF7( stbir__simdfX_load( o0, output7 ); stbir__simdfX_load( o1, output7+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output7+(3*stbir__simdfX_float_count) ); - stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) #else - stbIF0( stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); + stbIF0( stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) - stbIF1( stbir__simdfX_mult( o0, r0, c1 ); stbir__simdfX_mult( o1, r1, c1 ); stbir__simdfX_mult( o2, r2, c1 ); stbir__simdfX_mult( o3, r3, c1 ); + stbIF1( stbir__simdfX_mult( o0, r0, c1 ); stbir__simdfX_mult( o1, r1, c1 ); stbir__simdfX_mult( o2, r2, c1 ); stbir__simdfX_mult( o3, r3, c1 ); stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) - stbIF2( stbir__simdfX_mult( o0, r0, c2 ); stbir__simdfX_mult( o1, r1, c2 ); stbir__simdfX_mult( o2, r2, c2 ); stbir__simdfX_mult( o3, r3, c2 ); + stbIF2( stbir__simdfX_mult( o0, r0, c2 ); stbir__simdfX_mult( o1, r1, c2 ); stbir__simdfX_mult( o2, r2, c2 ); stbir__simdfX_mult( o3, r3, c2 ); stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) - stbIF3( stbir__simdfX_mult( o0, r0, c3 ); stbir__simdfX_mult( o1, r1, c3 ); stbir__simdfX_mult( o2, r2, c3 ); stbir__simdfX_mult( o3, r3, c3 ); + stbIF3( stbir__simdfX_mult( o0, r0, c3 ); stbir__simdfX_mult( o1, r1, c3 ); stbir__simdfX_mult( o2, r2, c3 ); stbir__simdfX_mult( o3, r3, c3 ); stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) - stbIF4( stbir__simdfX_mult( o0, r0, c4 ); stbir__simdfX_mult( o1, r1, c4 ); stbir__simdfX_mult( o2, r2, c4 ); stbir__simdfX_mult( o3, r3, c4 ); + stbIF4( stbir__simdfX_mult( o0, r0, c4 ); stbir__simdfX_mult( o1, r1, c4 ); stbir__simdfX_mult( o2, r2, c4 ); stbir__simdfX_mult( o3, r3, c4 ); stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) - stbIF5( stbir__simdfX_mult( o0, r0, c5 ); stbir__simdfX_mult( o1, r1, c5 ); stbir__simdfX_mult( o2, r2, c5 ); stbir__simdfX_mult( o3, r3, c5 ); + stbIF5( stbir__simdfX_mult( o0, r0, c5 ); stbir__simdfX_mult( o1, r1, c5 ); stbir__simdfX_mult( o2, r2, c5 ); stbir__simdfX_mult( o3, r3, c5 ); stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) - stbIF6( stbir__simdfX_mult( o0, r0, c6 ); stbir__simdfX_mult( o1, r1, c6 ); stbir__simdfX_mult( o2, r2, c6 ); stbir__simdfX_mult( o3, r3, c6 ); + stbIF6( stbir__simdfX_mult( o0, r0, c6 ); stbir__simdfX_mult( o1, r1, c6 ); stbir__simdfX_mult( o2, r2, c6 ); stbir__simdfX_mult( o3, r3, c6 ); stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) - stbIF7( stbir__simdfX_mult( o0, r0, c7 ); stbir__simdfX_mult( o1, r1, c7 ); stbir__simdfX_mult( o2, r2, c7 ); stbir__simdfX_mult( o3, r3, c7 ); + stbIF7( stbir__simdfX_mult( o0, r0, c7 ); stbir__simdfX_mult( o1, r1, c7 ); stbir__simdfX_mult( o2, r2, c7 ); stbir__simdfX_mult( o3, r3, c7 ); stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) #endif input += (4*stbir__simdfX_float_count); stbIF0( output0 += (4*stbir__simdfX_float_count); ) stbIF1( output1 += (4*stbir__simdfX_float_count); ) stbIF2( output2 += (4*stbir__simdfX_float_count); ) stbIF3( output3 += (4*stbir__simdfX_float_count); ) stbIF4( output4 += (4*stbir__simdfX_float_count); ) stbIF5( output5 += (4*stbir__simdfX_float_count); ) stbIF6( output6 += (4*stbir__simdfX_float_count); ) stbIF7( output7 += (4*stbir__simdfX_float_count); ) } - while ( ( (char*)input_end - (char*) input ) >= 16 ) + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input_end - (char*) input ) >= 16 ) { stbir__simdf o0, r0; STBIR_SIMD_NO_UNROLL(output0); @@ -9692,13 +9935,14 @@ static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** output stbIF6( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) stbIF7( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) #endif - + input += 4; stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) } } #else - while ( ( (char*)input_end - (char*) input ) >= 16 ) + STBIR_NO_UNROLL_LOOP_START + while ( ( (char*)input_end - (char*) input ) >= 16 ) { float r0, r1, r2, r3; STBIR_NO_UNROLL(input); @@ -9729,7 +9973,8 @@ static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** output stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) } #endif - while ( input < input_end ) + STBIR_NO_UNROLL_LOOP_START + while ( input < input_end ) { float r = input[0]; STBIR_NO_UNROLL(output0); @@ -9779,7 +10024,7 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, STBIR_MEMCPY( output, input0, (char*)input0_end - (char*)input0 ); return; } -#endif +#endif #ifdef STBIR_SIMD { @@ -9791,14 +10036,15 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) - - while ( ( (char*)input0_end - (char*) input0 ) >= (16*stbir__simdfX_float_count) ) + + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input0_end - (char*) input0 ) >= (16*stbir__simdfX_float_count) ) { stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; STBIR_SIMD_NO_UNROLL(output); // prefetch four loop iterations ahead (doesn't affect much for small resizes, but helps with big ones) - stbIF0( stbir__prefetch( input0 + (16*stbir__simdfX_float_count) ); ) + stbIF0( stbir__prefetch( input0 + (16*stbir__simdfX_float_count) ); ) stbIF1( stbir__prefetch( input1 + (16*stbir__simdfX_float_count) ); ) stbIF2( stbir__prefetch( input2 + (16*stbir__simdfX_float_count) ); ) stbIF3( stbir__prefetch( input3 + (16*stbir__simdfX_float_count) ); ) @@ -9836,7 +10082,8 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, stbIF0( input0 += (4*stbir__simdfX_float_count); ) stbIF1( input1 += (4*stbir__simdfX_float_count); ) stbIF2( input2 += (4*stbir__simdfX_float_count); ) stbIF3( input3 += (4*stbir__simdfX_float_count); ) stbIF4( input4 += (4*stbir__simdfX_float_count); ) stbIF5( input5 += (4*stbir__simdfX_float_count); ) stbIF6( input6 += (4*stbir__simdfX_float_count); ) stbIF7( input7 += (4*stbir__simdfX_float_count); ) } - while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) { stbir__simdf o0, r0; STBIR_SIMD_NO_UNROLL(output); @@ -9860,7 +10107,8 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, } } #else - while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + STBIR_NO_UNROLL_LOOP_START + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) { float o0, o1, o2, o3; STBIR_NO_UNROLL(output); @@ -9881,7 +10129,8 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) } #endif - while ( input0 < input0_end ) + STBIR_NO_UNROLL_LOOP_START + while ( input0 < input0_end ) { float o0; STBIR_NO_UNROLL(output); @@ -9897,7 +10146,7 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, stbIF5( o0 += input5[0] * c5s; ) stbIF6( o0 += input6[0] * c6s; ) stbIF7( o0 += input7[0] * c7s; ) - output[0] = o0; + output[0] = o0; ++output; stbIF0( ++input0; ) stbIF1( ++input1; ) stbIF2( ++input2; ) stbIF3( ++input3; ) stbIF4( ++input4; ) stbIF5( ++input5; ) stbIF6( ++input6; ) stbIF7( ++input7; ) } @@ -9928,25 +10177,25 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, #ifndef stbir__2_coeff_only #define stbir__2_coeff_only() \ stbir__1_coeff_only(); \ - stbir__1_coeff_remnant(1); + stbir__1_coeff_remnant(1); #endif #ifndef stbir__2_coeff_remnant #define stbir__2_coeff_remnant( ofs ) \ stbir__1_coeff_remnant(ofs); \ - stbir__1_coeff_remnant((ofs)+1); + stbir__1_coeff_remnant((ofs)+1); #endif - + #ifndef stbir__3_coeff_only #define stbir__3_coeff_only() \ stbir__2_coeff_only(); \ - stbir__1_coeff_remnant(2); + stbir__1_coeff_remnant(2); #endif - + #ifndef stbir__3_coeff_remnant #define stbir__3_coeff_remnant( ofs ) \ stbir__2_coeff_remnant(ofs); \ - stbir__1_coeff_remnant((ofs)+2); + stbir__1_coeff_remnant((ofs)+2); #endif #ifndef stbir__3_coeff_setup @@ -9956,13 +10205,13 @@ static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, #ifndef stbir__4_coeff_start #define stbir__4_coeff_start() \ stbir__2_coeff_only(); \ - stbir__2_coeff_remnant(2); + stbir__2_coeff_remnant(2); #endif - + #ifndef stbir__4_coeff_continue_from_4 #define stbir__4_coeff_continue_from_4( ofs ) \ stbir__2_coeff_remnant(ofs); \ - stbir__2_coeff_remnant((ofs)+2); + stbir__2_coeff_remnant((ofs)+2); #endif #ifndef stbir__store_output_tiny @@ -9973,8 +10222,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_1_coeff)( floa { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__1_coeff_only(); stbir__store_output_tiny(); @@ -9985,8 +10235,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_2_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__2_coeff_only(); stbir__store_output_tiny(); @@ -9997,8 +10248,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_3_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__3_coeff_only(); stbir__store_output_tiny(); @@ -10009,8 +10261,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_4_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__store_output(); @@ -10021,8 +10274,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_5_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__1_coeff_remnant(4); @@ -10034,8 +10288,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_6_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__2_coeff_remnant(4); @@ -10048,10 +10303,11 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_7_coeffs)( flo float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; stbir__3_coeff_setup(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; - + stbir__4_coeff_start(); stbir__3_coeff_remnant(4); stbir__store_output(); @@ -10062,8 +10318,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_8_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__4_coeff_continue_from_4(4); @@ -10075,8 +10332,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_9_coeffs)( flo { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__4_coeff_continue_from_4(4); @@ -10089,8 +10347,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_10_coeffs)( fl { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__4_coeff_continue_from_4(4); @@ -10104,8 +10363,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_11_coeffs)( fl float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; stbir__3_coeff_setup(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__4_coeff_continue_from_4(4); @@ -10118,8 +10378,9 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_12_coeffs)( fl { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); stbir__4_coeff_continue_from_4(4); @@ -10132,12 +10393,14 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod0 { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; - int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 4 + 3 ) >> 2; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 4 + 3 ) >> 2; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { hc += 4; decode += STBIR__horizontal_channels * 4; @@ -10152,19 +10415,21 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod1 { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; - int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 5 + 3 ) >> 2; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 5 + 3 ) >> 2; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { hc += 4; decode += STBIR__horizontal_channels * 4; stbir__4_coeff_continue_from_4( 0 ); --n; } while ( n > 0 ); - stbir__1_coeff_remnant( 4 ); + stbir__1_coeff_remnant( 4 ); stbir__store_output(); } while ( output < output_end ); } @@ -10173,19 +10438,21 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod2 { float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; - int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 6 + 3 ) >> 2; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 6 + 3 ) >> 2; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { hc += 4; decode += STBIR__horizontal_channels * 4; stbir__4_coeff_continue_from_4( 0 ); --n; } while ( n > 0 ); - stbir__2_coeff_remnant( 4 ); + stbir__2_coeff_remnant( 4 ); stbir__store_output(); } while ( output < output_end ); @@ -10196,19 +10463,21 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod3 float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; stbir__3_coeff_setup(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { - float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; - int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 7 + 3 ) >> 2; + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 7 + 3 ) >> 2; float const * hc = horizontal_coefficients; stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START do { hc += 4; decode += STBIR__horizontal_channels * 4; stbir__4_coeff_continue_from_4( 0 ); --n; } while ( n > 0 ); - stbir__3_coeff_remnant( 4 ); + stbir__3_coeff_remnant( 4 ); stbir__store_output(); } while ( output < output_end ); @@ -10216,26 +10485,26 @@ static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod3 static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_funcs)[4]= { - STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod0), - STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod1), - STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod2), - STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod3), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod0), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod1), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod2), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod3), }; static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_funcs)[12]= { - STBIR_chans(stbir__horizontal_gather_,_channels_with_1_coeff), - STBIR_chans(stbir__horizontal_gather_,_channels_with_2_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_1_coeff), + STBIR_chans(stbir__horizontal_gather_,_channels_with_2_coeffs), STBIR_chans(stbir__horizontal_gather_,_channels_with_3_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_4_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_5_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_6_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_4_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_5_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_6_coeffs), STBIR_chans(stbir__horizontal_gather_,_channels_with_7_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_8_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_9_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_10_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_11_coeffs), - STBIR_chans(stbir__horizontal_gather_,_channels_with_12_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_8_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_9_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_10_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_11_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_12_coeffs), }; #undef STBIR__horizontal_channels @@ -10266,38 +10535,38 @@ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ From fd961deba76c7c4939440fa6e6574eb86e68c5fb Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Sep 2024 00:44:25 -0500 Subject: [PATCH 069/107] Update BINDINGS.md (#4311) I've updated the bindings --- BINDINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index 7fd0dfd93a5e..2c986f236426 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -9,7 +9,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib](https://github.com/raysan5/raylib) | **5.0** | [C/C++](https://en.wikipedia.org/wiki/C_(programming_language)) | Zlib | | [raylib-beef](https://github.com/Starpelly/raylib-beef) | **5.0** | [Beef](https://www.beeflang.org) | MIT | | [raylib-boo](https://github.com/Rabios/raylib-boo) | 3.7 | [Boo](http://boo-language.github.io) | MIT | -| [raybit](https://github.com/Alex-Velez/raybit) | 3.7 | [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck) | MIT | +| [raybit](https://github.com/Alex-Velez/raybit) | **5.0** | [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck) | MIT | | [Raylib-cs](https://github.com/ChrisDill/Raylib-cs) | **5.0** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | Zlib | | [Raylib-CsLo](https://github.com/NotNotTech/Raylib-CsLo) | 4.2 | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | | [Raylib-CSharp-Vinculum](https://github.com/ZeroElectric/Raylib-CSharp-Vinculum) | **5.0** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | From ed702673ea0ff49da2ca494686814376ad040941 Mon Sep 17 00:00:00 2001 From: Jett <30197659+JettMonstersGoBoom@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:57:19 -0400 Subject: [PATCH 070/107] fix for hardcoded index values in vboID array (#4312) changing any of the #defines in CONFIG.H would cause issues when rendering. --- src/config.h | 1 + src/rmodels.c | 60 +++++++++++++++++++++++++-------------------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/config.h b/src/config.h index c54583e74fbb..9cbe90e35afb 100644 --- a/src/config.h +++ b/src/config.h @@ -119,6 +119,7 @@ #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 // Default shader vertex attribute names to set location points // NOTE: When a new shader is loaded, the following locations are tried to be set for convenience diff --git a/src/rmodels.c b/src/rmodels.c index 5fb5124038d5..9994ddb7697b 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1246,13 +1246,13 @@ void UploadMesh(Mesh *mesh, bool dynamic) mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); mesh->vaoId = 0; // Vertex Array Object - mesh->vboId[0] = 0; // Vertex buffer: positions - mesh->vboId[1] = 0; // Vertex buffer: texcoords - mesh->vboId[2] = 0; // Vertex buffer: normals - mesh->vboId[3] = 0; // Vertex buffer: colors - mesh->vboId[4] = 0; // Vertex buffer: tangents - mesh->vboId[5] = 0; // Vertex buffer: texcoords2 - mesh->vboId[6] = 0; // Vertex buffer: indices + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = 0; // Vertex buffer: positions + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = 0; // Vertex buffer: texcoords + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = 0; // Vertex buffer: normals + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = 0; // Vertex buffer: colors + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; // Vertex buffer: tangents + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) mesh->vaoId = rlLoadVertexArray(); @@ -1262,12 +1262,12 @@ void UploadMesh(Mesh *mesh, bool dynamic) // Enable vertex attributes: position (shader-location = 0) void *vertices = (mesh->animVertices != NULL)? mesh->animVertices : mesh->vertices; - mesh->vboId[0] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic); rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); // Enable vertex attributes: texcoords (shader-location = 1) - mesh->vboId[1] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic); rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); @@ -1278,7 +1278,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) { // Enable vertex attributes: normals (shader-location = 2) void *normals = (mesh->animNormals != NULL)? mesh->animNormals : mesh->normals; - mesh->vboId[2] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic); rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); } @@ -1294,7 +1294,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) if (mesh->colors != NULL) { // Enable vertex attribute: color (shader-location = 3) - mesh->vboId[3] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic); rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, 4, RL_UNSIGNED_BYTE, 1, 0, 0); rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); } @@ -1310,7 +1310,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) if (mesh->tangents != NULL) { // Enable vertex attribute: tangent (shader-location = 4) - mesh->vboId[4] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic); rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, 4, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT); } @@ -1326,7 +1326,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) if (mesh->texcoords2 != NULL) { // Enable vertex attribute: texcoord2 (shader-location = 5) - mesh->vboId[5] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic); rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2); } @@ -1341,7 +1341,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) if (mesh->indices != NULL) { - mesh->vboId[6] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic); + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic); } if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); @@ -1478,19 +1478,19 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) if (!rlEnableVertexArray(mesh.vaoId)) { // Bind mesh VBO data: vertex position (shader-location = 0) - rlEnableVertexBuffer(mesh.vboId[0]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); // Bind mesh VBO data: vertex texcoords (shader-location = 1) - rlEnableVertexBuffer(mesh.vboId[1]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) { // Bind mesh VBO data: vertex normals (shader-location = 2) - rlEnableVertexBuffer(mesh.vboId[2]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); } @@ -1498,9 +1498,9 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Bind mesh VBO data: vertex colors (shader-location = 3, if available) if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) { - if (mesh.vboId[3] != 0) + if (mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] != 0) { - rlEnableVertexBuffer(mesh.vboId[3]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); } @@ -1517,7 +1517,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) { - rlEnableVertexBuffer(mesh.vboId[4]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); } @@ -1525,12 +1525,12 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) { - rlEnableVertexBuffer(mesh.vboId[5]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } - if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); } int eyeCount = 1; @@ -1696,19 +1696,19 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i if (!rlEnableVertexArray(mesh.vaoId)) { // Bind mesh VBO data: vertex position (shader-location = 0) - rlEnableVertexBuffer(mesh.vboId[0]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); // Bind mesh VBO data: vertex texcoords (shader-location = 1) - rlEnableVertexBuffer(mesh.vboId[1]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) { // Bind mesh VBO data: vertex normals (shader-location = 2) - rlEnableVertexBuffer(mesh.vboId[2]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); } @@ -1716,9 +1716,9 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // Bind mesh VBO data: vertex colors (shader-location = 3, if available) if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) { - if (mesh.vboId[3] != 0) + if (mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] != 0) { - rlEnableVertexBuffer(mesh.vboId[3]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); } @@ -1735,7 +1735,7 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) { - rlEnableVertexBuffer(mesh.vboId[4]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); } @@ -1743,12 +1743,12 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) { - rlEnableVertexBuffer(mesh.vboId[5]); + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2]); rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } - if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); } int eyeCount = 1; From d02fcc5262f5b359b3645614a90d1e613f7e7f53 Mon Sep 17 00:00:00 2001 From: base Date: Sun, 15 Sep 2024 07:51:20 -0300 Subject: [PATCH 071/107] chore: GetApplicationDirectory definition for FreeBSD (#4318) --- src/rcore.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/rcore.c b/src/rcore.c index 972d5860074c..44dfd746f07f 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -165,6 +165,10 @@ unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #elif defined(__linux__) #include +#elif defined(__FreeBSD__) + #include + #include + #include #elif defined(__APPLE__) #include #include @@ -2161,6 +2165,28 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } +#elif defined(__FreeBSD__) + size_t size = sizeof(appDir); + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + + if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) + { + int len = strlen(appDir); + for (int i = len; i >= 0; --i) + { + if (appDir[i] == '/') + { + appDir[i + 1] = '\0'; + break; + } + } + } + else + { + appDir[0] = '.'; + appDir[1] = '/'; + } + #endif return appDir; From ddc523ffbe8a0af2e4783d947cdff6c9c53f7048 Mon Sep 17 00:00:00 2001 From: SusgUY446 <129160115+SusgUY446@users.noreply.github.com> Date: Sun, 15 Sep 2024 12:55:45 +0200 Subject: [PATCH 072/107] [rtextures] add MixColors. a function to mix 2 colors together (#4310) * added MixColors function to mix 2 colors together (Line 1428 raylib.h and Line 4995 in rtextures.c) * renamed MixColors to ColorLerp (https://github.com/raysan5/raylib/pull/4310#issuecomment-2340121038) * changed ColorLerp to be more like other functions --------- Co-authored-by: CI <-ci@not-real.com> --- src/raylib.h | 1 + src/rtextures.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/raylib.h b/src/raylib.h index 30ba8bd2aaac..a9cdbe94b8cb 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1435,6 +1435,7 @@ RLAPI Color GetColor(unsigned int hexValue); // G RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format +RLAPI Color ColorLerp(Color color1, Color color2, float d); // Mix 2 Colors Together //------------------------------------------------------------------------------------ // Font Loading and Text Drawing Functions (Module: text) diff --git a/src/rtextures.c b/src/rtextures.c index 867add38e3c9..f391b0d917f6 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -4985,6 +4985,8 @@ Vector3 ColorToHSV(Color color) return hsv; } + + // Get a Color from HSV values // Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion // NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors @@ -5422,6 +5424,24 @@ int GetPixelDataSize(int width, int height, int format) return dataSize; } + +// Mix 2 Colors togehter. +// d = dominance. 0.5 for equal +Color ColorLerp(Color color1, Color color2, float d) +{ + Color newColor = { 0, 0, 0, 0 }; + if (d < 0) {d=0.0f;} + else if(d>1) {d=1.0f;} + + newColor.r = (unsigned char)((1.0f-d) * color1.r + d * color2.r); + newColor.g = (unsigned char)((1.0f-d) * color1.g + d * color2.g); + newColor.b = (unsigned char)((1.0f-d) * color1.b + d * color2.b); + newColor.a = (unsigned char)((1.0f-d) * color1.a + d * color2.a); + + return newColor; +} + + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- From 68e7cadabcc4966c38251f59cb1bddb66f72bbeb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Sep 2024 10:56:01 +0000 Subject: [PATCH 073/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 21 +- parser/output/raylib_api.lua | 12 +- parser/output/raylib_api.txt | 393 +++++++++++++++++----------------- parser/output/raylib_api.xml | 9 +- 4 files changed, 238 insertions(+), 197 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 03b19325a35a..48096a951f2d 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -8844,6 +8844,25 @@ } ] }, + { + "name": "ColorLerp", + "description": "Mix 2 Colors Together", + "returnType": "Color", + "params": [ + { + "type": "Color", + "name": "color1" + }, + { + "type": "Color", + "name": "color2" + }, + { + "type": "float", + "name": "d" + } + ] + }, { "name": "GetFontDefault", "description": "Get the default Font", @@ -8862,7 +8881,7 @@ }, { "name": "LoadFontEx", - "description": "Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character setFont", + "description": "Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height", "returnType": "Font", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 7d74c9a41361..66095be61dcf 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -6414,6 +6414,16 @@ return { {type = "int", name = "format"} } }, + { + name = "ColorLerp", + description = "Mix 2 Colors Together", + returnType = "Color", + params = { + {type = "Color", name = "color1"}, + {type = "Color", name = "color2"}, + {type = "float", name = "d"} + } + }, { name = "GetFontDefault", description = "Get the default Font", @@ -6429,7 +6439,7 @@ return { }, { name = "LoadFontEx", - description = "Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character setFont", + description = "Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height", returnType = "Font", params = { {type = "const char *", name = "fileName"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index fb30ebe5f228..6c489498b8bf 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -984,7 +984,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 575 +Functions found: 576 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -3393,32 +3393,39 @@ Function 384: GetPixelDataSize() (3 input parameters) Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: format (type: int) -Function 385: GetFontDefault() (0 input parameters) +Function 385: ColorLerp() (3 input parameters) + Name: ColorLerp + Return type: Color + Description: Mix 2 Colors Together + Param[1]: color1 (type: Color) + Param[2]: color2 (type: Color) + Param[3]: d (type: float) +Function 386: GetFontDefault() (0 input parameters) Name: GetFontDefault Return type: Font Description: Get the default Font No input parameters -Function 386: LoadFont() (1 input parameters) +Function 387: LoadFont() (1 input parameters) Name: LoadFont Return type: Font Description: Load font from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 387: LoadFontEx() (4 input parameters) +Function 388: LoadFontEx() (4 input parameters) Name: LoadFontEx Return type: Font - Description: Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character setFont + Description: Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height Param[1]: fileName (type: const char *) Param[2]: fontSize (type: int) Param[3]: codepoints (type: int *) Param[4]: codepointCount (type: int) -Function 388: LoadFontFromImage() (3 input parameters) +Function 389: LoadFontFromImage() (3 input parameters) Name: LoadFontFromImage Return type: Font Description: Load font from Image (XNA style) Param[1]: image (type: Image) Param[2]: key (type: Color) Param[3]: firstChar (type: int) -Function 389: LoadFontFromMemory() (6 input parameters) +Function 390: LoadFontFromMemory() (6 input parameters) Name: LoadFontFromMemory Return type: Font Description: Load font from memory buffer, fileType refers to extension: i.e. '.ttf' @@ -3428,12 +3435,12 @@ Function 389: LoadFontFromMemory() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: codepoints (type: int *) Param[6]: codepointCount (type: int) -Function 390: IsFontReady() (1 input parameters) +Function 391: IsFontReady() (1 input parameters) Name: IsFontReady Return type: bool Description: Check if a font is ready Param[1]: font (type: Font) -Function 391: LoadFontData() (6 input parameters) +Function 392: LoadFontData() (6 input parameters) Name: LoadFontData Return type: GlyphInfo * Description: Load font data for further use @@ -3443,7 +3450,7 @@ Function 391: LoadFontData() (6 input parameters) Param[4]: codepoints (type: int *) Param[5]: codepointCount (type: int) Param[6]: type (type: int) -Function 392: GenImageFontAtlas() (6 input parameters) +Function 393: GenImageFontAtlas() (6 input parameters) Name: GenImageFontAtlas Return type: Image Description: Generate image font atlas using chars info @@ -3453,30 +3460,30 @@ Function 392: GenImageFontAtlas() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: padding (type: int) Param[6]: packMethod (type: int) -Function 393: UnloadFontData() (2 input parameters) +Function 394: UnloadFontData() (2 input parameters) Name: UnloadFontData Return type: void Description: Unload font chars info data (RAM) Param[1]: glyphs (type: GlyphInfo *) Param[2]: glyphCount (type: int) -Function 394: UnloadFont() (1 input parameters) +Function 395: UnloadFont() (1 input parameters) Name: UnloadFont Return type: void Description: Unload font from GPU memory (VRAM) Param[1]: font (type: Font) -Function 395: ExportFontAsCode() (2 input parameters) +Function 396: ExportFontAsCode() (2 input parameters) Name: ExportFontAsCode Return type: bool Description: Export font as code file, returns true on success Param[1]: font (type: Font) Param[2]: fileName (type: const char *) -Function 396: DrawFPS() (2 input parameters) +Function 397: DrawFPS() (2 input parameters) Name: DrawFPS Return type: void Description: Draw current FPS Param[1]: posX (type: int) Param[2]: posY (type: int) -Function 397: DrawText() (5 input parameters) +Function 398: DrawText() (5 input parameters) Name: DrawText Return type: void Description: Draw text (using default font) @@ -3485,7 +3492,7 @@ Function 397: DrawText() (5 input parameters) Param[3]: posY (type: int) Param[4]: fontSize (type: int) Param[5]: color (type: Color) -Function 398: DrawTextEx() (6 input parameters) +Function 399: DrawTextEx() (6 input parameters) Name: DrawTextEx Return type: void Description: Draw text using font and additional parameters @@ -3495,7 +3502,7 @@ Function 398: DrawTextEx() (6 input parameters) Param[4]: fontSize (type: float) Param[5]: spacing (type: float) Param[6]: tint (type: Color) -Function 399: DrawTextPro() (8 input parameters) +Function 400: DrawTextPro() (8 input parameters) Name: DrawTextPro Return type: void Description: Draw text using Font and pro parameters (rotation) @@ -3507,7 +3514,7 @@ Function 399: DrawTextPro() (8 input parameters) Param[6]: fontSize (type: float) Param[7]: spacing (type: float) Param[8]: tint (type: Color) -Function 400: DrawTextCodepoint() (5 input parameters) +Function 401: DrawTextCodepoint() (5 input parameters) Name: DrawTextCodepoint Return type: void Description: Draw one character (codepoint) @@ -3516,7 +3523,7 @@ Function 400: DrawTextCodepoint() (5 input parameters) Param[3]: position (type: Vector2) Param[4]: fontSize (type: float) Param[5]: tint (type: Color) -Function 401: DrawTextCodepoints() (7 input parameters) +Function 402: DrawTextCodepoints() (7 input parameters) Name: DrawTextCodepoints Return type: void Description: Draw multiple character (codepoint) @@ -3527,18 +3534,18 @@ Function 401: DrawTextCodepoints() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 402: SetTextLineSpacing() (1 input parameters) +Function 403: SetTextLineSpacing() (1 input parameters) Name: SetTextLineSpacing Return type: void Description: Set vertical line spacing when drawing with line-breaks Param[1]: spacing (type: int) -Function 403: MeasureText() (2 input parameters) +Function 404: MeasureText() (2 input parameters) Name: MeasureText Return type: int Description: Measure string width for default font Param[1]: text (type: const char *) Param[2]: fontSize (type: int) -Function 404: MeasureTextEx() (4 input parameters) +Function 405: MeasureTextEx() (4 input parameters) Name: MeasureTextEx Return type: Vector2 Description: Measure string size for Font @@ -3546,195 +3553,195 @@ Function 404: MeasureTextEx() (4 input parameters) Param[2]: text (type: const char *) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) -Function 405: GetGlyphIndex() (2 input parameters) +Function 406: GetGlyphIndex() (2 input parameters) Name: GetGlyphIndex Return type: int Description: Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 406: GetGlyphInfo() (2 input parameters) +Function 407: GetGlyphInfo() (2 input parameters) Name: GetGlyphInfo Return type: GlyphInfo Description: Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 407: GetGlyphAtlasRec() (2 input parameters) +Function 408: GetGlyphAtlasRec() (2 input parameters) Name: GetGlyphAtlasRec Return type: Rectangle Description: Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 408: LoadUTF8() (2 input parameters) +Function 409: LoadUTF8() (2 input parameters) Name: LoadUTF8 Return type: char * Description: Load UTF-8 text encoded from codepoints array Param[1]: codepoints (type: const int *) Param[2]: length (type: int) -Function 409: UnloadUTF8() (1 input parameters) +Function 410: UnloadUTF8() (1 input parameters) Name: UnloadUTF8 Return type: void Description: Unload UTF-8 text encoded from codepoints array Param[1]: text (type: char *) -Function 410: LoadCodepoints() (2 input parameters) +Function 411: LoadCodepoints() (2 input parameters) Name: LoadCodepoints Return type: int * Description: Load all codepoints from a UTF-8 text string, codepoints count returned by parameter Param[1]: text (type: const char *) Param[2]: count (type: int *) -Function 411: UnloadCodepoints() (1 input parameters) +Function 412: UnloadCodepoints() (1 input parameters) Name: UnloadCodepoints Return type: void Description: Unload codepoints data from memory Param[1]: codepoints (type: int *) -Function 412: GetCodepointCount() (1 input parameters) +Function 413: GetCodepointCount() (1 input parameters) Name: GetCodepointCount Return type: int Description: Get total number of codepoints in a UTF-8 encoded string Param[1]: text (type: const char *) -Function 413: GetCodepoint() (2 input parameters) +Function 414: GetCodepoint() (2 input parameters) Name: GetCodepoint Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 414: GetCodepointNext() (2 input parameters) +Function 415: GetCodepointNext() (2 input parameters) Name: GetCodepointNext Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 415: GetCodepointPrevious() (2 input parameters) +Function 416: GetCodepointPrevious() (2 input parameters) Name: GetCodepointPrevious Return type: int Description: Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 416: CodepointToUTF8() (2 input parameters) +Function 417: CodepointToUTF8() (2 input parameters) Name: CodepointToUTF8 Return type: const char * Description: Encode one codepoint into UTF-8 byte array (array length returned as parameter) Param[1]: codepoint (type: int) Param[2]: utf8Size (type: int *) -Function 417: TextCopy() (2 input parameters) +Function 418: TextCopy() (2 input parameters) Name: TextCopy Return type: int Description: Copy one string to another, returns bytes copied Param[1]: dst (type: char *) Param[2]: src (type: const char *) -Function 418: TextIsEqual() (2 input parameters) +Function 419: TextIsEqual() (2 input parameters) Name: TextIsEqual Return type: bool Description: Check if two text string are equal Param[1]: text1 (type: const char *) Param[2]: text2 (type: const char *) -Function 419: TextLength() (1 input parameters) +Function 420: TextLength() (1 input parameters) Name: TextLength Return type: unsigned int Description: Get text length, checks for '\0' ending Param[1]: text (type: const char *) -Function 420: TextFormat() (2 input parameters) +Function 421: TextFormat() (2 input parameters) Name: TextFormat Return type: const char * Description: Text formatting with variables (sprintf() style) Param[1]: text (type: const char *) Param[2]: args (type: ...) -Function 421: TextSubtext() (3 input parameters) +Function 422: TextSubtext() (3 input parameters) Name: TextSubtext Return type: const char * Description: Get a piece of a text string Param[1]: text (type: const char *) Param[2]: position (type: int) Param[3]: length (type: int) -Function 422: TextReplace() (3 input parameters) +Function 423: TextReplace() (3 input parameters) Name: TextReplace Return type: char * Description: Replace text string (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: replace (type: const char *) Param[3]: by (type: const char *) -Function 423: TextInsert() (3 input parameters) +Function 424: TextInsert() (3 input parameters) Name: TextInsert Return type: char * Description: Insert text in a position (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: insert (type: const char *) Param[3]: position (type: int) -Function 424: TextJoin() (3 input parameters) +Function 425: TextJoin() (3 input parameters) Name: TextJoin Return type: const char * Description: Join text strings with delimiter Param[1]: textList (type: const char **) Param[2]: count (type: int) Param[3]: delimiter (type: const char *) -Function 425: TextSplit() (3 input parameters) +Function 426: TextSplit() (3 input parameters) Name: TextSplit Return type: const char ** Description: Split text into multiple strings Param[1]: text (type: const char *) Param[2]: delimiter (type: char) Param[3]: count (type: int *) -Function 426: TextAppend() (3 input parameters) +Function 427: TextAppend() (3 input parameters) Name: TextAppend Return type: void Description: Append text at specific position and move cursor! Param[1]: text (type: char *) Param[2]: append (type: const char *) Param[3]: position (type: int *) -Function 427: TextFindIndex() (2 input parameters) +Function 428: TextFindIndex() (2 input parameters) Name: TextFindIndex Return type: int Description: Find first text occurrence within a string Param[1]: text (type: const char *) Param[2]: find (type: const char *) -Function 428: TextToUpper() (1 input parameters) +Function 429: TextToUpper() (1 input parameters) Name: TextToUpper Return type: const char * Description: Get upper case version of provided string Param[1]: text (type: const char *) -Function 429: TextToLower() (1 input parameters) +Function 430: TextToLower() (1 input parameters) Name: TextToLower Return type: const char * Description: Get lower case version of provided string Param[1]: text (type: const char *) -Function 430: TextToPascal() (1 input parameters) +Function 431: TextToPascal() (1 input parameters) Name: TextToPascal Return type: const char * Description: Get Pascal case notation version of provided string Param[1]: text (type: const char *) -Function 431: TextToSnake() (1 input parameters) +Function 432: TextToSnake() (1 input parameters) Name: TextToSnake Return type: const char * Description: Get Snake case notation version of provided string Param[1]: text (type: const char *) -Function 432: TextToCamel() (1 input parameters) +Function 433: TextToCamel() (1 input parameters) Name: TextToCamel Return type: const char * Description: Get Camel case notation version of provided string Param[1]: text (type: const char *) -Function 433: TextToInteger() (1 input parameters) +Function 434: TextToInteger() (1 input parameters) Name: TextToInteger Return type: int Description: Get integer value from text (negative values not supported) Param[1]: text (type: const char *) -Function 434: TextToFloat() (1 input parameters) +Function 435: TextToFloat() (1 input parameters) Name: TextToFloat Return type: float Description: Get float value from text (negative values not supported) Param[1]: text (type: const char *) -Function 435: DrawLine3D() (3 input parameters) +Function 436: DrawLine3D() (3 input parameters) Name: DrawLine3D Return type: void Description: Draw a line in 3D world space Param[1]: startPos (type: Vector3) Param[2]: endPos (type: Vector3) Param[3]: color (type: Color) -Function 436: DrawPoint3D() (2 input parameters) +Function 437: DrawPoint3D() (2 input parameters) Name: DrawPoint3D Return type: void Description: Draw a point in 3D space, actually a small line Param[1]: position (type: Vector3) Param[2]: color (type: Color) -Function 437: DrawCircle3D() (5 input parameters) +Function 438: DrawCircle3D() (5 input parameters) Name: DrawCircle3D Return type: void Description: Draw a circle in 3D world space @@ -3743,7 +3750,7 @@ Function 437: DrawCircle3D() (5 input parameters) Param[3]: rotationAxis (type: Vector3) Param[4]: rotationAngle (type: float) Param[5]: color (type: Color) -Function 438: DrawTriangle3D() (4 input parameters) +Function 439: DrawTriangle3D() (4 input parameters) Name: DrawTriangle3D Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -3751,14 +3758,14 @@ Function 438: DrawTriangle3D() (4 input parameters) Param[2]: v2 (type: Vector3) Param[3]: v3 (type: Vector3) Param[4]: color (type: Color) -Function 439: DrawTriangleStrip3D() (3 input parameters) +Function 440: DrawTriangleStrip3D() (3 input parameters) Name: DrawTriangleStrip3D Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector3 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 440: DrawCube() (5 input parameters) +Function 441: DrawCube() (5 input parameters) Name: DrawCube Return type: void Description: Draw cube @@ -3767,14 +3774,14 @@ Function 440: DrawCube() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 441: DrawCubeV() (3 input parameters) +Function 442: DrawCubeV() (3 input parameters) Name: DrawCubeV Return type: void Description: Draw cube (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 442: DrawCubeWires() (5 input parameters) +Function 443: DrawCubeWires() (5 input parameters) Name: DrawCubeWires Return type: void Description: Draw cube wires @@ -3783,21 +3790,21 @@ Function 442: DrawCubeWires() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 443: DrawCubeWiresV() (3 input parameters) +Function 444: DrawCubeWiresV() (3 input parameters) Name: DrawCubeWiresV Return type: void Description: Draw cube wires (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 444: DrawSphere() (3 input parameters) +Function 445: DrawSphere() (3 input parameters) Name: DrawSphere Return type: void Description: Draw sphere Param[1]: centerPos (type: Vector3) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 445: DrawSphereEx() (5 input parameters) +Function 446: DrawSphereEx() (5 input parameters) Name: DrawSphereEx Return type: void Description: Draw sphere with extended parameters @@ -3806,7 +3813,7 @@ Function 445: DrawSphereEx() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 446: DrawSphereWires() (5 input parameters) +Function 447: DrawSphereWires() (5 input parameters) Name: DrawSphereWires Return type: void Description: Draw sphere wires @@ -3815,7 +3822,7 @@ Function 446: DrawSphereWires() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 447: DrawCylinder() (6 input parameters) +Function 448: DrawCylinder() (6 input parameters) Name: DrawCylinder Return type: void Description: Draw a cylinder/cone @@ -3825,7 +3832,7 @@ Function 447: DrawCylinder() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 448: DrawCylinderEx() (6 input parameters) +Function 449: DrawCylinderEx() (6 input parameters) Name: DrawCylinderEx Return type: void Description: Draw a cylinder with base at startPos and top at endPos @@ -3835,7 +3842,7 @@ Function 448: DrawCylinderEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 449: DrawCylinderWires() (6 input parameters) +Function 450: DrawCylinderWires() (6 input parameters) Name: DrawCylinderWires Return type: void Description: Draw a cylinder/cone wires @@ -3845,7 +3852,7 @@ Function 449: DrawCylinderWires() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 450: DrawCylinderWiresEx() (6 input parameters) +Function 451: DrawCylinderWiresEx() (6 input parameters) Name: DrawCylinderWiresEx Return type: void Description: Draw a cylinder wires with base at startPos and top at endPos @@ -3855,7 +3862,7 @@ Function 450: DrawCylinderWiresEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 451: DrawCapsule() (6 input parameters) +Function 452: DrawCapsule() (6 input parameters) Name: DrawCapsule Return type: void Description: Draw a capsule with the center of its sphere caps at startPos and endPos @@ -3865,7 +3872,7 @@ Function 451: DrawCapsule() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 452: DrawCapsuleWires() (6 input parameters) +Function 453: DrawCapsuleWires() (6 input parameters) Name: DrawCapsuleWires Return type: void Description: Draw capsule wireframe with the center of its sphere caps at startPos and endPos @@ -3875,51 +3882,51 @@ Function 452: DrawCapsuleWires() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 453: DrawPlane() (3 input parameters) +Function 454: DrawPlane() (3 input parameters) Name: DrawPlane Return type: void Description: Draw a plane XZ Param[1]: centerPos (type: Vector3) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 454: DrawRay() (2 input parameters) +Function 455: DrawRay() (2 input parameters) Name: DrawRay Return type: void Description: Draw a ray line Param[1]: ray (type: Ray) Param[2]: color (type: Color) -Function 455: DrawGrid() (2 input parameters) +Function 456: DrawGrid() (2 input parameters) Name: DrawGrid Return type: void Description: Draw a grid (centered at (0, 0, 0)) Param[1]: slices (type: int) Param[2]: spacing (type: float) -Function 456: LoadModel() (1 input parameters) +Function 457: LoadModel() (1 input parameters) Name: LoadModel Return type: Model Description: Load model from files (meshes and materials) Param[1]: fileName (type: const char *) -Function 457: LoadModelFromMesh() (1 input parameters) +Function 458: LoadModelFromMesh() (1 input parameters) Name: LoadModelFromMesh Return type: Model Description: Load model from generated mesh (default material) Param[1]: mesh (type: Mesh) -Function 458: IsModelReady() (1 input parameters) +Function 459: IsModelReady() (1 input parameters) Name: IsModelReady Return type: bool Description: Check if a model is ready Param[1]: model (type: Model) -Function 459: UnloadModel() (1 input parameters) +Function 460: UnloadModel() (1 input parameters) Name: UnloadModel Return type: void Description: Unload model (including meshes) from memory (RAM and/or VRAM) Param[1]: model (type: Model) -Function 460: GetModelBoundingBox() (1 input parameters) +Function 461: GetModelBoundingBox() (1 input parameters) Name: GetModelBoundingBox Return type: BoundingBox Description: Compute model bounding box limits (considers all meshes) Param[1]: model (type: Model) -Function 461: DrawModel() (4 input parameters) +Function 462: DrawModel() (4 input parameters) Name: DrawModel Return type: void Description: Draw a model (with texture if set) @@ -3927,7 +3934,7 @@ Function 461: DrawModel() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 462: DrawModelEx() (6 input parameters) +Function 463: DrawModelEx() (6 input parameters) Name: DrawModelEx Return type: void Description: Draw a model with extended parameters @@ -3937,7 +3944,7 @@ Function 462: DrawModelEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 463: DrawModelWires() (4 input parameters) +Function 464: DrawModelWires() (4 input parameters) Name: DrawModelWires Return type: void Description: Draw a model wires (with texture if set) @@ -3945,7 +3952,7 @@ Function 463: DrawModelWires() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 464: DrawModelWiresEx() (6 input parameters) +Function 465: DrawModelWiresEx() (6 input parameters) Name: DrawModelWiresEx Return type: void Description: Draw a model wires (with texture if set) with extended parameters @@ -3955,7 +3962,7 @@ Function 464: DrawModelWiresEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 465: DrawModelPoints() (4 input parameters) +Function 466: DrawModelPoints() (4 input parameters) Name: DrawModelPoints Return type: void Description: Draw a model as points @@ -3963,7 +3970,7 @@ Function 465: DrawModelPoints() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 466: DrawModelPointsEx() (6 input parameters) +Function 467: DrawModelPointsEx() (6 input parameters) Name: DrawModelPointsEx Return type: void Description: Draw a model as points with extended parameters @@ -3973,13 +3980,13 @@ Function 466: DrawModelPointsEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 467: DrawBoundingBox() (2 input parameters) +Function 468: DrawBoundingBox() (2 input parameters) Name: DrawBoundingBox Return type: void Description: Draw bounding box (wires) Param[1]: box (type: BoundingBox) Param[2]: color (type: Color) -Function 468: DrawBillboard() (5 input parameters) +Function 469: DrawBillboard() (5 input parameters) Name: DrawBillboard Return type: void Description: Draw a billboard texture @@ -3988,7 +3995,7 @@ Function 468: DrawBillboard() (5 input parameters) Param[3]: position (type: Vector3) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 469: DrawBillboardRec() (6 input parameters) +Function 470: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec Return type: void Description: Draw a billboard texture defined by source @@ -3998,7 +4005,7 @@ Function 469: DrawBillboardRec() (6 input parameters) Param[4]: position (type: Vector3) Param[5]: size (type: Vector2) Param[6]: tint (type: Color) -Function 470: DrawBillboardPro() (9 input parameters) +Function 471: DrawBillboardPro() (9 input parameters) Name: DrawBillboardPro Return type: void Description: Draw a billboard texture defined by source and rotation @@ -4011,13 +4018,13 @@ Function 470: DrawBillboardPro() (9 input parameters) Param[7]: origin (type: Vector2) Param[8]: rotation (type: float) Param[9]: tint (type: Color) -Function 471: UploadMesh() (2 input parameters) +Function 472: UploadMesh() (2 input parameters) Name: UploadMesh Return type: void Description: Upload mesh vertex data in GPU and provide VAO/VBO ids Param[1]: mesh (type: Mesh *) Param[2]: dynamic (type: bool) -Function 472: UpdateMeshBuffer() (5 input parameters) +Function 473: UpdateMeshBuffer() (5 input parameters) Name: UpdateMeshBuffer Return type: void Description: Update mesh vertex data in GPU for a specific buffer index @@ -4026,19 +4033,19 @@ Function 472: UpdateMeshBuffer() (5 input parameters) Param[3]: data (type: const void *) Param[4]: dataSize (type: int) Param[5]: offset (type: int) -Function 473: UnloadMesh() (1 input parameters) +Function 474: UnloadMesh() (1 input parameters) Name: UnloadMesh Return type: void Description: Unload mesh data from CPU and GPU Param[1]: mesh (type: Mesh) -Function 474: DrawMesh() (3 input parameters) +Function 475: DrawMesh() (3 input parameters) Name: DrawMesh Return type: void Description: Draw a 3d mesh with material and transform Param[1]: mesh (type: Mesh) Param[2]: material (type: Material) Param[3]: transform (type: Matrix) -Function 475: DrawMeshInstanced() (4 input parameters) +Function 476: DrawMeshInstanced() (4 input parameters) Name: DrawMeshInstanced Return type: void Description: Draw multiple mesh instances with material and different transforms @@ -4046,35 +4053,35 @@ Function 475: DrawMeshInstanced() (4 input parameters) Param[2]: material (type: Material) Param[3]: transforms (type: const Matrix *) Param[4]: instances (type: int) -Function 476: GetMeshBoundingBox() (1 input parameters) +Function 477: GetMeshBoundingBox() (1 input parameters) Name: GetMeshBoundingBox Return type: BoundingBox Description: Compute mesh bounding box limits Param[1]: mesh (type: Mesh) -Function 477: GenMeshTangents() (1 input parameters) +Function 478: GenMeshTangents() (1 input parameters) Name: GenMeshTangents Return type: void Description: Compute mesh tangents Param[1]: mesh (type: Mesh *) -Function 478: ExportMesh() (2 input parameters) +Function 479: ExportMesh() (2 input parameters) Name: ExportMesh Return type: bool Description: Export mesh data to file, returns true on success Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 479: ExportMeshAsCode() (2 input parameters) +Function 480: ExportMeshAsCode() (2 input parameters) Name: ExportMeshAsCode Return type: bool Description: Export mesh as code file (.h) defining multiple arrays of vertex attributes Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 480: GenMeshPoly() (2 input parameters) +Function 481: GenMeshPoly() (2 input parameters) Name: GenMeshPoly Return type: Mesh Description: Generate polygonal mesh Param[1]: sides (type: int) Param[2]: radius (type: float) -Function 481: GenMeshPlane() (4 input parameters) +Function 482: GenMeshPlane() (4 input parameters) Name: GenMeshPlane Return type: Mesh Description: Generate plane mesh (with subdivisions) @@ -4082,42 +4089,42 @@ Function 481: GenMeshPlane() (4 input parameters) Param[2]: length (type: float) Param[3]: resX (type: int) Param[4]: resZ (type: int) -Function 482: GenMeshCube() (3 input parameters) +Function 483: GenMeshCube() (3 input parameters) Name: GenMeshCube Return type: Mesh Description: Generate cuboid mesh Param[1]: width (type: float) Param[2]: height (type: float) Param[3]: length (type: float) -Function 483: GenMeshSphere() (3 input parameters) +Function 484: GenMeshSphere() (3 input parameters) Name: GenMeshSphere Return type: Mesh Description: Generate sphere mesh (standard sphere) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 484: GenMeshHemiSphere() (3 input parameters) +Function 485: GenMeshHemiSphere() (3 input parameters) Name: GenMeshHemiSphere Return type: Mesh Description: Generate half-sphere mesh (no bottom cap) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 485: GenMeshCylinder() (3 input parameters) +Function 486: GenMeshCylinder() (3 input parameters) Name: GenMeshCylinder Return type: Mesh Description: Generate cylinder mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 486: GenMeshCone() (3 input parameters) +Function 487: GenMeshCone() (3 input parameters) Name: GenMeshCone Return type: Mesh Description: Generate cone/pyramid mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 487: GenMeshTorus() (4 input parameters) +Function 488: GenMeshTorus() (4 input parameters) Name: GenMeshTorus Return type: Mesh Description: Generate torus mesh @@ -4125,7 +4132,7 @@ Function 487: GenMeshTorus() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 488: GenMeshKnot() (4 input parameters) +Function 489: GenMeshKnot() (4 input parameters) Name: GenMeshKnot Return type: Mesh Description: Generate trefoil knot mesh @@ -4133,84 +4140,84 @@ Function 488: GenMeshKnot() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 489: GenMeshHeightmap() (2 input parameters) +Function 490: GenMeshHeightmap() (2 input parameters) Name: GenMeshHeightmap Return type: Mesh Description: Generate heightmap mesh from image data Param[1]: heightmap (type: Image) Param[2]: size (type: Vector3) -Function 490: GenMeshCubicmap() (2 input parameters) +Function 491: GenMeshCubicmap() (2 input parameters) Name: GenMeshCubicmap Return type: Mesh Description: Generate cubes-based map mesh from image data Param[1]: cubicmap (type: Image) Param[2]: cubeSize (type: Vector3) -Function 491: LoadMaterials() (2 input parameters) +Function 492: LoadMaterials() (2 input parameters) Name: LoadMaterials Return type: Material * Description: Load materials from model file Param[1]: fileName (type: const char *) Param[2]: materialCount (type: int *) -Function 492: LoadMaterialDefault() (0 input parameters) +Function 493: LoadMaterialDefault() (0 input parameters) Name: LoadMaterialDefault Return type: Material Description: Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) No input parameters -Function 493: IsMaterialReady() (1 input parameters) +Function 494: IsMaterialReady() (1 input parameters) Name: IsMaterialReady Return type: bool Description: Check if a material is ready Param[1]: material (type: Material) -Function 494: UnloadMaterial() (1 input parameters) +Function 495: UnloadMaterial() (1 input parameters) Name: UnloadMaterial Return type: void Description: Unload material from GPU memory (VRAM) Param[1]: material (type: Material) -Function 495: SetMaterialTexture() (3 input parameters) +Function 496: SetMaterialTexture() (3 input parameters) Name: SetMaterialTexture Return type: void Description: Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) Param[1]: material (type: Material *) Param[2]: mapType (type: int) Param[3]: texture (type: Texture2D) -Function 496: SetModelMeshMaterial() (3 input parameters) +Function 497: SetModelMeshMaterial() (3 input parameters) Name: SetModelMeshMaterial Return type: void Description: Set material for a mesh Param[1]: model (type: Model *) Param[2]: meshId (type: int) Param[3]: materialId (type: int) -Function 497: LoadModelAnimations() (2 input parameters) +Function 498: LoadModelAnimations() (2 input parameters) Name: LoadModelAnimations Return type: ModelAnimation * Description: Load model animations from file Param[1]: fileName (type: const char *) Param[2]: animCount (type: int *) -Function 498: UpdateModelAnimation() (3 input parameters) +Function 499: UpdateModelAnimation() (3 input parameters) Name: UpdateModelAnimation Return type: void Description: Update model animation pose Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 499: UnloadModelAnimation() (1 input parameters) +Function 500: UnloadModelAnimation() (1 input parameters) Name: UnloadModelAnimation Return type: void Description: Unload animation data Param[1]: anim (type: ModelAnimation) -Function 500: UnloadModelAnimations() (2 input parameters) +Function 501: UnloadModelAnimations() (2 input parameters) Name: UnloadModelAnimations Return type: void Description: Unload animation array data Param[1]: animations (type: ModelAnimation *) Param[2]: animCount (type: int) -Function 501: IsModelAnimationValid() (2 input parameters) +Function 502: IsModelAnimationValid() (2 input parameters) Name: IsModelAnimationValid Return type: bool Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 502: CheckCollisionSpheres() (4 input parameters) +Function 503: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4218,40 +4225,40 @@ Function 502: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 503: CheckCollisionBoxes() (2 input parameters) +Function 504: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 504: CheckCollisionBoxSphere() (3 input parameters) +Function 505: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 505: GetRayCollisionSphere() (3 input parameters) +Function 506: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 506: GetRayCollisionBox() (2 input parameters) +Function 507: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 507: GetRayCollisionMesh() (3 input parameters) +Function 508: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 508: GetRayCollisionTriangle() (4 input parameters) +Function 509: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4259,7 +4266,7 @@ Function 508: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 509: GetRayCollisionQuad() (5 input parameters) +Function 510: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4268,158 +4275,158 @@ Function 509: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 510: InitAudioDevice() (0 input parameters) +Function 511: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 511: CloseAudioDevice() (0 input parameters) +Function 512: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 512: IsAudioDeviceReady() (0 input parameters) +Function 513: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 513: SetMasterVolume() (1 input parameters) +Function 514: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 514: GetMasterVolume() (0 input parameters) +Function 515: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 515: LoadWave() (1 input parameters) +Function 516: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 516: LoadWaveFromMemory() (3 input parameters) +Function 517: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 517: IsWaveReady() (1 input parameters) +Function 518: IsWaveReady() (1 input parameters) Name: IsWaveReady Return type: bool Description: Checks if wave data is ready Param[1]: wave (type: Wave) -Function 518: LoadSound() (1 input parameters) +Function 519: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 519: LoadSoundFromWave() (1 input parameters) +Function 520: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 520: LoadSoundAlias() (1 input parameters) +Function 521: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 521: IsSoundReady() (1 input parameters) +Function 522: IsSoundReady() (1 input parameters) Name: IsSoundReady Return type: bool Description: Checks if a sound is ready Param[1]: sound (type: Sound) -Function 522: UpdateSound() (3 input parameters) +Function 523: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 523: UnloadWave() (1 input parameters) +Function 524: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 524: UnloadSound() (1 input parameters) +Function 525: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 525: UnloadSoundAlias() (1 input parameters) +Function 526: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 526: ExportWave() (2 input parameters) +Function 527: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 527: ExportWaveAsCode() (2 input parameters) +Function 528: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 528: PlaySound() (1 input parameters) +Function 529: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 529: StopSound() (1 input parameters) +Function 530: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 530: PauseSound() (1 input parameters) +Function 531: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 531: ResumeSound() (1 input parameters) +Function 532: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 532: IsSoundPlaying() (1 input parameters) +Function 533: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 533: SetSoundVolume() (2 input parameters) +Function 534: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 534: SetSoundPitch() (2 input parameters) +Function 535: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 535: SetSoundPan() (2 input parameters) +Function 536: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (0.5 is center) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 536: WaveCopy() (1 input parameters) +Function 537: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 537: WaveCrop() (3 input parameters) +Function 538: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 538: WaveFormat() (4 input parameters) +Function 539: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4427,203 +4434,203 @@ Function 538: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 539: LoadWaveSamples() (1 input parameters) +Function 540: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 540: UnloadWaveSamples() (1 input parameters) +Function 541: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 541: LoadMusicStream() (1 input parameters) +Function 542: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 542: LoadMusicStreamFromMemory() (3 input parameters) +Function 543: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 543: IsMusicReady() (1 input parameters) +Function 544: IsMusicReady() (1 input parameters) Name: IsMusicReady Return type: bool Description: Checks if a music stream is ready Param[1]: music (type: Music) -Function 544: UnloadMusicStream() (1 input parameters) +Function 545: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 545: PlayMusicStream() (1 input parameters) +Function 546: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 546: IsMusicStreamPlaying() (1 input parameters) +Function 547: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 547: UpdateMusicStream() (1 input parameters) +Function 548: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 548: StopMusicStream() (1 input parameters) +Function 549: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 549: PauseMusicStream() (1 input parameters) +Function 550: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 550: ResumeMusicStream() (1 input parameters) +Function 551: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 551: SeekMusicStream() (2 input parameters) +Function 552: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 552: SetMusicVolume() (2 input parameters) +Function 553: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 553: SetMusicPitch() (2 input parameters) +Function 554: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 554: SetMusicPan() (2 input parameters) +Function 555: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (0.5 is center) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 555: GetMusicTimeLength() (1 input parameters) +Function 556: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 556: GetMusicTimePlayed() (1 input parameters) +Function 557: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 557: LoadAudioStream() (3 input parameters) +Function 558: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 558: IsAudioStreamReady() (1 input parameters) +Function 559: IsAudioStreamReady() (1 input parameters) Name: IsAudioStreamReady Return type: bool Description: Checks if an audio stream is ready Param[1]: stream (type: AudioStream) -Function 559: UnloadAudioStream() (1 input parameters) +Function 560: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 560: UpdateAudioStream() (3 input parameters) +Function 561: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 561: IsAudioStreamProcessed() (1 input parameters) +Function 562: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 562: PlayAudioStream() (1 input parameters) +Function 563: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 563: PauseAudioStream() (1 input parameters) +Function 564: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 564: ResumeAudioStream() (1 input parameters) +Function 565: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 565: IsAudioStreamPlaying() (1 input parameters) +Function 566: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 566: StopAudioStream() (1 input parameters) +Function 567: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 567: SetAudioStreamVolume() (2 input parameters) +Function 568: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 568: SetAudioStreamPitch() (2 input parameters) +Function 569: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 569: SetAudioStreamPan() (2 input parameters) +Function 570: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 570: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 571: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 571: SetAudioStreamCallback() (2 input parameters) +Function 572: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 572: AttachAudioStreamProcessor() (2 input parameters) +Function 573: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives the samples as 'float' Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 573: DetachAudioStreamProcessor() (2 input parameters) +Function 574: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 574: AttachAudioMixedProcessor() (1 input parameters) +Function 575: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' Param[1]: processor (type: AudioCallback) -Function 575: DetachAudioMixedProcessor() (1 input parameters) +Function 576: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 532b9e3b9292..1cd6456513e7 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -670,7 +670,7 @@ - + @@ -2236,12 +2236,17 @@ + + + + + - + From b807f633d95d8aa762565362b9df1034287fd1b1 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 15 Sep 2024 13:01:59 +0200 Subject: [PATCH 074/107] REVIEWED: `ColorLerp()` formatting #4310 --- src/raylib.h | 2 +- src/rtextures.c | 34 ++++++++++++++++------------------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index a9cdbe94b8cb..84f1363554da 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1431,11 +1431,11 @@ RLAPI Color ColorBrightness(Color color, float factor); // G RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint +RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format -RLAPI Color ColorLerp(Color color1, Color color2, float d); // Mix 2 Colors Together //------------------------------------------------------------------------------------ // Font Loading and Text Drawing Functions (Module: text) diff --git a/src/rtextures.c b/src/rtextures.c index f391b0d917f6..0a5ab793e72e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5185,6 +5185,22 @@ Color ColorAlphaBlend(Color dst, Color src, Color tint) return out; } +// Get color lerp interpolation between two colors, factor [0.0f..1.0f] +Color ColorLerp(Color color1, Color color2, float factor) +{ + Color color = { 0 }; + + if (d < 0) d = 0.0f; + else if (d > 1) d = 1.0f; + + color.r = (unsigned char)((1.0f - factor)*color1.r + factor*color2.r); + color.g = (unsigned char)((1.0f - factor)*color1.g + factor*color2.g); + color.b = (unsigned char)((1.0f - factor)*color1.b + factor*color2.b); + color.a = (unsigned char)((1.0f - factor)*color1.a + factor*color2.a); + + return color; +} + // Get a Color struct from hexadecimal value Color GetColor(unsigned int hexValue) { @@ -5424,24 +5440,6 @@ int GetPixelDataSize(int width, int height, int format) return dataSize; } - -// Mix 2 Colors togehter. -// d = dominance. 0.5 for equal -Color ColorLerp(Color color1, Color color2, float d) -{ - Color newColor = { 0, 0, 0, 0 }; - if (d < 0) {d=0.0f;} - else if(d>1) {d=1.0f;} - - newColor.r = (unsigned char)((1.0f-d) * color1.r + d * color2.r); - newColor.g = (unsigned char)((1.0f-d) * color1.g + d * color2.g); - newColor.b = (unsigned char)((1.0f-d) * color1.b + d * color2.b); - newColor.a = (unsigned char)((1.0f-d) * color1.a + d * color2.a); - - return newColor; -} - - //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- From 8d92e65773019fa48fe1fe56cf3a247e4e11ac14 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Sep 2024 11:02:18 +0000 Subject: [PATCH 075/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 38 +++++++++++++++++------------------ parser/output/raylib_api.lua | 20 +++++++++--------- parser/output/raylib_api.txt | 22 ++++++++++---------- parser/output/raylib_api.xml | 10 ++++----- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 48096a951f2d..423b552821cc 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -8780,6 +8780,25 @@ } ] }, + { + "name": "ColorLerp", + "description": "Get color lerp interpolation between two colors, factor [0.0f..1.0f]", + "returnType": "Color", + "params": [ + { + "type": "Color", + "name": "color1" + }, + { + "type": "Color", + "name": "color2" + }, + { + "type": "float", + "name": "factor" + } + ] + }, { "name": "GetColor", "description": "Get Color structure from hexadecimal value", @@ -8844,25 +8863,6 @@ } ] }, - { - "name": "ColorLerp", - "description": "Mix 2 Colors Together", - "returnType": "Color", - "params": [ - { - "type": "Color", - "name": "color1" - }, - { - "type": "Color", - "name": "color2" - }, - { - "type": "float", - "name": "d" - } - ] - }, { "name": "GetFontDefault", "description": "Get the default Font", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 66095be61dcf..0aad16644f4b 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -6377,6 +6377,16 @@ return { {type = "Color", name = "tint"} } }, + { + name = "ColorLerp", + description = "Get color lerp interpolation between two colors, factor [0.0f..1.0f]", + returnType = "Color", + params = { + {type = "Color", name = "color1"}, + {type = "Color", name = "color2"}, + {type = "float", name = "factor"} + } + }, { name = "GetColor", description = "Get Color structure from hexadecimal value", @@ -6414,16 +6424,6 @@ return { {type = "int", name = "format"} } }, - { - name = "ColorLerp", - description = "Mix 2 Colors Together", - returnType = "Color", - params = { - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"}, - {type = "float", name = "d"} - } - }, { name = "GetFontDefault", description = "Get the default Font", diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 6c489498b8bf..55420d158825 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -3368,38 +3368,38 @@ Function 380: ColorAlphaBlend() (3 input parameters) Param[1]: dst (type: Color) Param[2]: src (type: Color) Param[3]: tint (type: Color) -Function 381: GetColor() (1 input parameters) +Function 381: ColorLerp() (3 input parameters) + Name: ColorLerp + Return type: Color + Description: Get color lerp interpolation between two colors, factor [0.0f..1.0f] + Param[1]: color1 (type: Color) + Param[2]: color2 (type: Color) + Param[3]: factor (type: float) +Function 382: GetColor() (1 input parameters) Name: GetColor Return type: Color Description: Get Color structure from hexadecimal value Param[1]: hexValue (type: unsigned int) -Function 382: GetPixelColor() (2 input parameters) +Function 383: GetPixelColor() (2 input parameters) Name: GetPixelColor Return type: Color Description: Get Color from a source pixel pointer of certain format Param[1]: srcPtr (type: void *) Param[2]: format (type: int) -Function 383: SetPixelColor() (3 input parameters) +Function 384: SetPixelColor() (3 input parameters) Name: SetPixelColor Return type: void Description: Set color formatted into destination pixel pointer Param[1]: dstPtr (type: void *) Param[2]: color (type: Color) Param[3]: format (type: int) -Function 384: GetPixelDataSize() (3 input parameters) +Function 385: GetPixelDataSize() (3 input parameters) Name: GetPixelDataSize Return type: int Description: Get pixel data size in bytes for certain format Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: format (type: int) -Function 385: ColorLerp() (3 input parameters) - Name: ColorLerp - Return type: Color - Description: Mix 2 Colors Together - Param[1]: color1 (type: Color) - Param[2]: color2 (type: Color) - Param[3]: d (type: float) Function 386: GetFontDefault() (0 input parameters) Name: GetFontDefault Return type: Font diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 1cd6456513e7..3f8f65b13081 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -2219,6 +2219,11 @@ + + + + + @@ -2236,11 +2241,6 @@ - - - - - From e9bbf02b2b450269c08ba348d352159f4658024b Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 15 Sep 2024 13:03:12 +0200 Subject: [PATCH 076/107] Update rtextures.c --- src/rtextures.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rtextures.c b/src/rtextures.c index 0a5ab793e72e..8bda75ad8e08 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5190,8 +5190,8 @@ Color ColorLerp(Color color1, Color color2, float factor) { Color color = { 0 }; - if (d < 0) d = 0.0f; - else if (d > 1) d = 1.0f; + if (factor < 0.0f) factor = 0.0f; + else if (factor > 1.0f) factor = 1.0f; color.r = (unsigned char)((1.0f - factor)*color1.r + factor*color2.r); color.g = (unsigned char)((1.0f - factor)*color1.g + factor*color2.g); From 16e9317220bcede380873d2000c1ccd5062e3f07 Mon Sep 17 00:00:00 2001 From: foxblock Date: Sun, 15 Sep 2024 13:05:01 +0200 Subject: [PATCH 077/107] [rcore] Add filtering folders to `LoadDirectoryFilesEx()`/`ScanDirectoryFiles()` (#4302) * Add filtering folders in ScanDirectoryFiles and ScanDirectoryFilesRecursively Add define FILTER_FOLDER for that purpose Fix folder names matching filter being added to result * Move FILTER_FOLDER define to internals of rcore and document option in comment --- src/raylib.h | 2 +- src/rcore.c | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 84f1363554da..a1514e32c430 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1126,7 +1126,7 @@ RLAPI bool ChangeDirectory(const char *dir); // Change work RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths -RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths diff --git a/src/rcore.c b/src/rcore.c index 44dfd746f07f..50e26423b182 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -251,6 +251,10 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #define MAX_AUTOMATION_EVENTS 16384 // Maximum number of automation events to record #endif +#ifndef FILTER_FOLDER + #define FILTER_FOLDER "/DIR" // Filter string used in ScanDirectoryFiles, ScanDirectoryFilesRecursively and LoadDirectoryFilesEx to include directories in the result array +#endif + // Flags operation macros #define FLAG_SET(n, f) ((n) |= (f)) #define FLAG_CLEAR(n, f) ((n) &= ~(f)) @@ -3339,10 +3343,21 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const if (filter != NULL) { - if (IsFileExtension(path, filter)) + if (IsPathFile(path)) { - strcpy(files->paths[files->count], path); - files->count++; + if (IsFileExtension(path, filter)) + { + strcpy(files->paths[files->count], path); + files->count++; + } + } + else + { + if (TextFindIndex(filter, FILTER_FOLDER) >= 0) + { + strcpy(files->paths[files->count], path); + files->count++; + } } } else @@ -3402,7 +3417,22 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi break; } } - else ScanDirectoryFilesRecursively(path, files, filter); + else + { + if (filter != NULL && TextFindIndex(filter, FILTER_FOLDER) >= 0) + { + strcpy(files->paths[files->count], path); + files->count++; + } + + if (files->count >= files->capacity) + { + TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); + break; + } + + ScanDirectoryFilesRecursively(path, files, filter); + } } } From 7ec1c288beb633a1e3fade91b0c7e348a2c24431 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Sep 2024 11:05:15 +0000 Subject: [PATCH 078/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 2 +- parser/output/raylib_api.lua | 2 +- parser/output/raylib_api.txt | 2 +- parser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 423b552821cc..7e9ad9c1212b 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -4523,7 +4523,7 @@ }, { "name": "LoadDirectoryFilesEx", - "description": "Load directory filepaths with extension filtering and recursive directory scan", + "description": "Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result", "returnType": "FilePathList", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 0aad16644f4b..5a644a3ad42b 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4076,7 +4076,7 @@ return { }, { name = "LoadDirectoryFilesEx", - description = "Load directory filepaths with extension filtering and recursive directory scan", + description = "Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result", returnType = "FilePathList", params = { {type = "const char *", name = "basePath"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 55420d158825..19181328f78f 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -1722,7 +1722,7 @@ Function 137: LoadDirectoryFiles() (1 input parameters) Function 138: LoadDirectoryFilesEx() (3 input parameters) Name: LoadDirectoryFilesEx Return type: FilePathList - Description: Load directory filepaths with extension filtering and recursive directory scan + Description: Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result Param[1]: basePath (type: const char *) Param[2]: filter (type: const char *) Param[3]: scanSubdirs (type: bool) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 3f8f65b13081..f73e9241be2a 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1081,7 +1081,7 @@ - + From 0e4ebaf89df19a3cbf3fb8c9215e94dfb1344d9b Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 15 Sep 2024 13:20:43 +0200 Subject: [PATCH 079/107] REVIEWED: `DrawTexturePro()` to avoid negative dest rec #4316 --- src/rtextures.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rtextures.c b/src/rtextures.c index 8bda75ad8e08..6d3c21cf4c13 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -4565,6 +4565,9 @@ void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 if (source.width < 0) { flipX = true; source.width *= -1; } if (source.height < 0) source.y -= source.height; + + if (dest.width < 0) dest.width *= -1; + if (dest.height < 0) dest.height *= -1; Vector2 topLeft = { 0 }; Vector2 topRight = { 0 }; From 627e76cf7b82786d7e8a69b5bb1d5e7d023e7976 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 17 Sep 2024 10:26:32 +0200 Subject: [PATCH 080/107] REVIEWED: Directory filter tag #4323 --- src/rcore.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 50e26423b182..5de1fc721649 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -251,9 +251,9 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #define MAX_AUTOMATION_EVENTS 16384 // Maximum number of automation events to record #endif -#ifndef FILTER_FOLDER - #define FILTER_FOLDER "/DIR" // Filter string used in ScanDirectoryFiles, ScanDirectoryFilesRecursively and LoadDirectoryFilesEx to include directories in the result array -#endif +#ifndef DIRECTORY_FILTER_TAG + #define DIRECTORY_FILTER_TAG "DIR" // Name tag used to request directory inclusion on directory scan +#endif // NOTE: Used in ScanDirectoryFiles(), ScanDirectoryFilesRecursively() and LoadDirectoryFilesEx() // Flags operation macros #define FLAG_SET(n, f) ((n) |= (f)) @@ -3353,7 +3353,7 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const } else { - if (TextFindIndex(filter, FILTER_FOLDER) >= 0) + if (TextFindIndex(filter, DIRECTORY_FILTER_TAG) >= 0) { strcpy(files->paths[files->count], path); files->count++; @@ -3419,7 +3419,7 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi } else { - if (filter != NULL && TextFindIndex(filter, FILTER_FOLDER) >= 0) + if ((filter != NULL) && (TextFindIndex(filter, DIRECTORY_FILTER_TAG) >= 0)) { strcpy(files->paths[files->count], path); files->count++; From c2bd1845eb65dedcce4dff519b2c2dc20336a3cf Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 17 Sep 2024 10:30:26 +0200 Subject: [PATCH 081/107] Reviewed #4323 --- parser/raylib_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/raylib_parser.c b/parser/raylib_parser.c index 83991c003c75..94a715562a27 100644 --- a/parser/raylib_parser.c +++ b/parser/raylib_parser.c @@ -72,7 +72,7 @@ #define MAX_CALLBACKS_TO_PARSE 64 // Maximum number of callbacks to parse #define MAX_FUNCS_TO_PARSE 1024 // Maximum number of functions to parse -#define MAX_LINE_LENGTH 512 // Maximum length of one line (including comments) +#define MAX_LINE_LENGTH 1024 // Maximum length of one line (including comments) #define MAX_STRUCT_FIELDS 64 // Maximum number of struct fields #define MAX_ENUM_VALUES 512 // Maximum number of enum values @@ -139,7 +139,7 @@ typedef struct EnumInfo { // Function info data typedef struct FunctionInfo { char name[64]; // Function name - char desc[256]; // Function description (comment at the end) + char desc[512]; // Function description (comment at the end) char retType[32]; // Return value type int paramCount; // Number of function parameters char paramType[MAX_FUNCTION_PARAMETERS][32]; // Parameters type From 0a03ed913beb8a3d7555c5e9b804a0a327fceb9c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 17 Sep 2024 17:30:40 +0200 Subject: [PATCH 082/107] Update raylib.h --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raylib.h b/src/raylib.h index a1514e32c430..08474b5e5d1f 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1126,7 +1126,7 @@ RLAPI bool ChangeDirectory(const char *dir); // Change work RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths -RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths From c762a99bd57c4e19551967451b42b195ac71b4bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Sep 2024 15:30:59 +0000 Subject: [PATCH 083/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 2 +- parser/output/raylib_api.lua | 2 +- parser/output/raylib_api.txt | 2 +- parser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 7e9ad9c1212b..781454dc28ba 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -4523,7 +4523,7 @@ }, { "name": "LoadDirectoryFilesEx", - "description": "Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result", + "description": "Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result", "returnType": "FilePathList", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 5a644a3ad42b..9cf39855ecfe 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4076,7 +4076,7 @@ return { }, { name = "LoadDirectoryFilesEx", - description = "Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result", + description = "Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result", returnType = "FilePathList", params = { {type = "const char *", name = "basePath"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 19181328f78f..07b34c0cebbf 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -1722,7 +1722,7 @@ Function 137: LoadDirectoryFiles() (1 input parameters) Function 138: LoadDirectoryFilesEx() (3 input parameters) Name: LoadDirectoryFilesEx Return type: FilePathList - Description: Load directory filepaths with extension filtering and recursive directory scan. Use "/DIR" in the filter string to include directories in the result + Description: Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result Param[1]: basePath (type: const char *) Param[2]: filter (type: const char *) Param[3]: scanSubdirs (type: bool) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index f73e9241be2a..6bd5a45819db 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1081,7 +1081,7 @@ - + From 2f0cf8fbe1c389e99c1cc97bb163cbc8f92b9327 Mon Sep 17 00:00:00 2001 From: Brian E <72316548+Brian-ED@users.noreply.github.com> Date: Fri, 20 Sep 2024 09:01:15 +0200 Subject: [PATCH 084/107] [BINDINGS.md] Added raylib-bqn, moved rayed-bqn (#4331) * [BINDINGS.md] Added raylib-bqn, moved rayed-bqn rayed-bqn has had a lot of progress and I've realized it has diverged too much from raylib, and so I made raylib-bqn to be a simple binding, while rayed-bqn will be a utility wrapper. * removed accidental newline --- BINDINGS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index 2c986f236426..6ac54498e8a1 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -78,7 +78,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-zig-bindings](https://github.com/L-Briand/raylib-zig-bindings) | **5.0** | [Zig](https://ziglang.org) | Zlib | | [hare-raylib](https://git.sr.ht/~evantj/hare-raylib) | **auto** | [Hare](https://harelang.org) | Zlib | | [raylib-sunder](https://github.com/ashn-dot-dev/raylib-sunder) | **auto** | [Sunder](https://github.com/ashn-dot-dev/sunder) | 0BSD | -| [rayed-bqn](https://github.com/Brian-ED/rayed-bqn) | **auto** | [BQN](https://mlochbaum.github.io/BQN) | MIT | +| [raylib-bqn](https://github.com/Brian-ED/raylib-bqn) | **5.0** | [BQN](https://mlochbaum.github.io/BQN) | MIT | | [rayjs](https://github.com/mode777/rayjs) | 4.6-dev | [QuickJS](https://bellard.org/quickjs) | MIT | | [raylib-raku](https://github.com/vushu/raylib-raku) | **auto** | [Raku](https://www.raku.org) | Artistic License 2.0 | | [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) | 4.5 | [Lean4](https://lean-lang.org) | BSD-3-Clause | @@ -92,6 +92,7 @@ These are utility wrappers for specific languages, they are not required to use | ---------------------------------------------------- | :------------: | :------------------------------------------: | :-----: | | [raylib-cpp](https://github.com/robloach/raylib-cpp) | **5.0** | [C++](https://en.wikipedia.org/wiki/C%2B%2B) | Zlib | | [claylib](https://github.com/defun-games/claylib) | 4.5 | [Common Lisp](https://common-lisp.net) | Zlib | +| [rayed-bqn](https://github.com/Brian-ED/rayed-bqn) | **5.0** | [BQN](https://mlochbaum.github.io/BQN) | MIT | ### Older or Unmaintained Language Bindings From 86ead962633f5ee819829550dc8acdd9cd07f667 Mon Sep 17 00:00:00 2001 From: Daniel Holden Date: Fri, 20 Sep 2024 11:30:37 -0400 Subject: [PATCH 085/107] [rmodels] Optional GPU skinning (#4321) * Added optional GPU skinning * Added skinned bone matrices support for different file formats. * Moved new shader locations to end of enum to avoid breaking existing examples. Added gpu skinning on drawing of instanced meshes. * Added GPU skinning example. * Removed variable declaration to avoid shadowing warning. --- examples/Makefile | 3 +- examples/models/models_gpu_skinning.c | 117 +++++++++++++ .../resources/shaders/glsl100/skinning.fs | 17 ++ .../resources/shaders/glsl100/skinning.vs | 34 ++++ .../resources/shaders/glsl330/skinning.fs | 17 ++ .../resources/shaders/glsl330/skinning.vs | 34 ++++ src/config.h | 16 +- src/raylib.h | 12 +- src/rcore.c | 5 + src/rlgl.h | 41 ++++- src/rmodels.c | 164 +++++++++++++++++- 11 files changed, 437 insertions(+), 23 deletions(-) create mode 100644 examples/models/models_gpu_skinning.c create mode 100644 examples/models/resources/shaders/glsl100/skinning.fs create mode 100644 examples/models/resources/shaders/glsl100/skinning.vs create mode 100644 examples/models/resources/shaders/glsl330/skinning.fs create mode 100644 examples/models/resources/shaders/glsl330/skinning.vs diff --git a/examples/Makefile b/examples/Makefile index bdbb6e3510ad..c172584270cf 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -587,7 +587,8 @@ MODELS = \ models/models_rlgl_solar_system \ models/models_skybox \ models/models_waving_cubes \ - models/models_yaw_pitch_roll + models/models_yaw_pitch_roll \ + models/models_gpu_skinning SHADERS = \ shaders/shaders_basic_lighting \ diff --git a/examples/models/models_gpu_skinning.c b/examples/models/models_gpu_skinning.c new file mode 100644 index 000000000000..e9cd73f4b8d1 --- /dev/null +++ b/examples/models/models_gpu_skinning.c @@ -0,0 +1,117 @@ +/******************************************************************************************* +* +* raylib [core] example - Doing skinning on the gpu using a vertex shader +* +* Example originally created with raylib 4.5, last time updated with raylib 4.5 +* +* Example contributed by Daniel Holden (@orangeduck) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2024 Daniel Holden (@orangeduck) +* +********************************************************************************************/ + +#include "raylib.h" + +#include "raymath.h" + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - GPU skinning"); + + // Define the camera to look into our 3d world + Camera camera = { 0 }; + camera.position = (Vector3){ 5.0f, 5.0f, 5.0f }; // Camera position + 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 = 45.0f; // Camera field-of-view Y + camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + + // Load gltf model + Model characterModel = LoadModel("resources/models/gltf/greenman.glb"); // Load character model + + // Load skinning shader + Shader skinningShader = LoadShader(TextFormat("resources/shaders/glsl%i/skinning.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/skinning.fs", GLSL_VERSION)); + + characterModel.materials[1].shader = skinningShader; + + // Load gltf model animations + int animsCount = 0; + unsigned int animIndex = 0; + unsigned int animCurrentFrame = 0; + ModelAnimation *modelAnimations = LoadModelAnimations("resources/models/gltf/greenman.glb", &animsCount); + + Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position + + DisableCursor(); // Limit cursor to relative movement inside the window + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_THIRD_PERSON); + + // Select current animation + if (IsKeyPressed(KEY_T)) animIndex = (animIndex + 1)%animsCount; + else if (IsKeyPressed(KEY_G)) animIndex = (animIndex + animsCount - 1)%animsCount; + + // Update model animation + ModelAnimation anim = modelAnimations[animIndex]; + animCurrentFrame = (animCurrentFrame + 1)%anim.frameCount; + UpdateModelAnimationBoneMatrices(characterModel, anim, animCurrentFrame); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + // Draw character + characterModel.transform = MatrixTranslate(position.x, position.y, position.z); + UpdateModelAnimationBoneMatrices(characterModel, anim, animCurrentFrame); + DrawMesh(characterModel.meshes[0], characterModel.materials[1], characterModel.transform); + + DrawGrid(10, 1.0f); + EndMode3D(); + + DrawText("Use the T/G to switch animation", 10, 10, 20, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModelAnimations(modelAnimations, animsCount); + UnloadModel(characterModel); // Unload character model and meshes/material + UnloadShader(skinningShader); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/models/resources/shaders/glsl100/skinning.fs b/examples/models/resources/shaders/glsl100/skinning.fs new file mode 100644 index 000000000000..ef6568036845 --- /dev/null +++ b/examples/models/resources/shaders/glsl100/skinning.fs @@ -0,0 +1,17 @@ +#version 100 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Output fragment color +out vec4 finalColor; + +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +void main() +{ + vec4 texelColor = texture(texture0, fragTexCoord); + finalColor = texelColor*colDiffuse*fragColor; +} diff --git a/examples/models/resources/shaders/glsl100/skinning.vs b/examples/models/resources/shaders/glsl100/skinning.vs new file mode 100644 index 000000000000..9c92e2a8c8e2 --- /dev/null +++ b/examples/models/resources/shaders/glsl100/skinning.vs @@ -0,0 +1,34 @@ +#version 100 + +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec4 vertexColor; +in vec4 vertexBoneIds; +in vec4 vertexBoneWeights; + +#define MAX_BONE_NUM 128 +uniform mat4 boneMatrices[MAX_BONE_NUM]; + +uniform mat4 mvp; + +out vec2 fragTexCoord; +out vec4 fragColor; + +void main() +{ + int boneIndex0 = int(vertexBoneIds.x); + int boneIndex1 = int(vertexBoneIds.y); + int boneIndex2 = int(vertexBoneIds.z); + int boneIndex3 = int(vertexBoneIds.w); + + vec4 skinnedPosition = + vertexBoneWeights.x * (boneMatrices[boneIndex0] * vec4(vertexPosition, 1.0f)) + + vertexBoneWeights.y * (boneMatrices[boneIndex1] * vec4(vertexPosition, 1.0f)) + + vertexBoneWeights.z * (boneMatrices[boneIndex2] * vec4(vertexPosition, 1.0f)) + + vertexBoneWeights.w * (boneMatrices[boneIndex3] * vec4(vertexPosition, 1.0f)); + + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + gl_Position = mvp * skinnedPosition; +} \ No newline at end of file diff --git a/examples/models/resources/shaders/glsl330/skinning.fs b/examples/models/resources/shaders/glsl330/skinning.fs new file mode 100644 index 000000000000..d4311ffcc42c --- /dev/null +++ b/examples/models/resources/shaders/glsl330/skinning.fs @@ -0,0 +1,17 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Output fragment color +out vec4 finalColor; + +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +void main() +{ + vec4 texelColor = texture(texture0, fragTexCoord); + finalColor = texelColor*colDiffuse*fragColor; +} diff --git a/examples/models/resources/shaders/glsl330/skinning.vs b/examples/models/resources/shaders/glsl330/skinning.vs new file mode 100644 index 000000000000..2dc976c48cca --- /dev/null +++ b/examples/models/resources/shaders/glsl330/skinning.vs @@ -0,0 +1,34 @@ +#version 330 + +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec4 vertexColor; +in vec4 vertexBoneIds; +in vec4 vertexBoneWeights; + +#define MAX_BONE_NUM 128 +uniform mat4 boneMatrices[MAX_BONE_NUM]; + +uniform mat4 mvp; + +out vec2 fragTexCoord; +out vec4 fragColor; + +void main() +{ + int boneIndex0 = int(vertexBoneIds.x); + int boneIndex1 = int(vertexBoneIds.y); + int boneIndex2 = int(vertexBoneIds.z); + int boneIndex3 = int(vertexBoneIds.w); + + vec4 skinnedPosition = + vertexBoneWeights.x * (boneMatrices[boneIndex0] * vec4(vertexPosition, 1.0f)) + + vertexBoneWeights.y * (boneMatrices[boneIndex1] * vec4(vertexPosition, 1.0f)) + + vertexBoneWeights.z * (boneMatrices[boneIndex2] * vec4(vertexPosition, 1.0f)) + + vertexBoneWeights.w * (boneMatrices[boneIndex3] * vec4(vertexPosition, 1.0f)); + + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + gl_Position = mvp * skinnedPosition; +} \ No newline at end of file diff --git a/src/config.h b/src/config.h index 9cbe90e35afb..f6e3fe304408 100644 --- a/src/config.h +++ b/src/config.h @@ -113,13 +113,15 @@ #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance // Default shader vertex attribute locations -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 6 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 7 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 8 // Default shader vertex attribute names to set location points // NOTE: When a new shader is loaded, the following locations are tried to be set for convenience diff --git a/src/raylib.h b/src/raylib.h index 08474b5e5d1f..ee34b6441a88 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -352,8 +352,10 @@ typedef struct Mesh { // Animation vertex data float *animVertices; // Animated vertex positions (after bones transformations) float *animNormals; // Animated normals (after bones transformations) - unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) - float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) + unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + Matrix *boneMatrices; // Bones animated transformation matrices + int boneCount; // Number of bones // OpenGL identifiers unsigned int vaoId; // OpenGL Vertex Array Object id @@ -790,7 +792,10 @@ typedef enum { SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf + SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf + SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds + SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights + SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices } ShaderLocationIndex; #define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO @@ -1591,6 +1596,7 @@ RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match +RLAPI void UpdateModelAnimationBoneMatrices(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices // Collision detection functions RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres diff --git a/src/rcore.c b/src/rcore.c index 5de1fc721649..2fd79f5c9bee 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1303,6 +1303,8 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) // vertex color location = 3 // vertex tangent location = 4 // vertex texcoord2 location = 5 + // vertex boneIds location = 6 + // vertex boneWeights location = 7 // NOTE: If any location is not found, loc point becomes -1 @@ -1318,6 +1320,8 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + shader.locs[SHADER_LOC_VERTEX_BONEIDS] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); + shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); // Get handles to GLSL uniform locations (vertex shader) shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); @@ -1325,6 +1329,7 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION); shader.locs[SHADER_LOC_MATRIX_MODEL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL); shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL); + shader.locs[SHADER_LOC_BONE_MATRICES] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES); // Get handles to GLSL uniform locations (fragment shader) shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); diff --git a/src/rlgl.h b/src/rlgl.h index 9b94d402c6d2..b98934a115b8 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -68,12 +68,15 @@ * #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR * #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT * #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS * #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix * #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) * #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices * #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) * #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) * #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) @@ -324,22 +327,28 @@ // Default shader vertex attribute locations #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 6 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 7 #endif //---------------------------------------------------------------------------------- @@ -759,6 +768,7 @@ RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) @@ -977,6 +987,12 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 #endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS +#endif #ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix @@ -996,6 +1012,9 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) #endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES + #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +#endif #ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) #endif @@ -4148,6 +4167,8 @@ unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); // NOTE: If some attrib name is no found on the shader, it locations becomes -1 @@ -4283,6 +4304,14 @@ void rlSetUniformMatrix(int locIndex, Matrix mat) #endif } +// Set shader value uniform matrix +void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUniformMatrix4fv(locIndex, count, true, (const float*)matrices); +#endif +} + // Set shader value uniform sampler void rlSetUniformSampler(int locIndex, unsigned int textureId) { diff --git a/src/rmodels.c b/src/rmodels.c index 9994ddb7697b..055fb1b1c62e 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -130,7 +130,7 @@ #define MAX_MATERIAL_MAPS 12 // Maximum number of maps supported #endif #ifndef MAX_MESH_VERTEX_BUFFERS - #define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh + #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh #endif //---------------------------------------------------------------------------------- @@ -1248,11 +1248,13 @@ void UploadMesh(Mesh *mesh, bool dynamic) mesh->vaoId = 0; // Vertex Array Object mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION] = 0; // Vertex buffer: positions mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = 0; // Vertex buffer: texcoords - mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = 0; // Vertex buffer: normals - mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = 0; // Vertex buffer: colors - mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; // Vertex buffer: tangents - mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 - mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL] = 0; // Vertex buffer: normals + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = 0; // Vertex buffer: colors + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; // Vertex buffer: tangents + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS] = 0; // Vertex buffer: boneIds + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = 0; // Vertex buffer: boneWeights + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) mesh->vaoId = rlLoadVertexArray(); @@ -1338,6 +1340,38 @@ void UploadMesh(Mesh *mesh, bool dynamic) rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, value, SHADER_ATTRIB_VEC2, 2); rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2); } + + if (mesh->boneIds != NULL) + { + // Enable vertex attribute: boneIds (shader-location = 6) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS] = rlLoadVertexBuffer(mesh->boneIds, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, 4, RL_UNSIGNED_BYTE, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS); + } + else + { + // Default vertex attribute: boneIds + // WARNING: Default value provided to shader if location available + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS); + } + + if (mesh->boneWeights != NULL) + { + // Enable vertex attribute: boneWeights (shader-location = 7) + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = rlLoadVertexBuffer(mesh->boneWeights, mesh->vertexCount*4*sizeof(float), dynamic); + rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS); + } + else + { + // Default vertex attribute: boneWeights + // WARNING: Default value provided to shader if location available + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, value, SHADER_ATTRIB_VEC4, 2); + rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS); + } if (mesh->indices != NULL) { @@ -1451,6 +1485,13 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Upload model normal matrix (if locations available) if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); + + // Upload Bone Transforms + if (material.shader.locs[SHADER_LOC_BONE_MATRICES] != -1 && mesh.boneMatrices) + { + rlSetUniformMatrices(material.shader.locs[SHADER_LOC_BONE_MATRICES], mesh.boneMatrices, mesh.boneCount); + } + //----------------------------------------------------- // Bind active texture maps (if available) @@ -1529,6 +1570,22 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } + + // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS], 4, RL_UNSIGNED_BYTE, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS]); + } + + // Bind mesh VBO data: vertex bone weights (shader-location = 7, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]); + } if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); } @@ -1671,6 +1728,13 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // Upload model normal matrix (if locations available) if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); + + // Upload Bone Transforms + if (material.shader.locs[SHADER_LOC_BONE_MATRICES] != -1 && mesh.boneMatrices) + { + rlSetUniformMatrices(material.shader.locs[SHADER_LOC_BONE_MATRICES], mesh.boneMatrices, mesh.boneCount); + } + //----------------------------------------------------- // Bind active texture maps (if available) @@ -1747,6 +1811,22 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } + + // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS], 4, RL_UNSIGNED_BYTE, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEIDS]); + } + + // Bind mesh VBO data: vertex bone weights (shader-location = 7, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] != -1) + { + rlEnableVertexBuffer(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]); + } if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); } @@ -1825,6 +1905,7 @@ void UnloadMesh(Mesh mesh) RL_FREE(mesh.animNormals); RL_FREE(mesh.boneWeights); RL_FREE(mesh.boneIds); + RL_FREE(mesh.boneMatrices); } // Export mesh data to file @@ -2255,6 +2336,50 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) } } +void UpdateModelAnimationBoneMatrices(Model model, ModelAnimation anim, int frame) +{ + if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL)) + { + if (frame >= anim.frameCount) frame = frame%anim.frameCount; + + for (int i = 0; i < model.meshCount; i++) + { + if (model.meshes[i].boneMatrices) + { + assert(model.meshes[i].boneCount == anim.boneCount); + + for (int boneId = 0; boneId < model.meshes[i].boneCount; boneId++) + { + Vector3 inTranslation = model.bindPose[boneId].translation; + Quaternion inRotation = model.bindPose[boneId].rotation; + Vector3 inScale = model.bindPose[boneId].scale; + + Vector3 outTranslation = anim.framePoses[frame][boneId].translation; + Quaternion outRotation = anim.framePoses[frame][boneId].rotation; + Vector3 outScale = anim.framePoses[frame][boneId].scale; + + Vector3 invTranslation = Vector3RotateByQuaternion(Vector3Negate(inTranslation), QuaternionInvert(inRotation)); + Quaternion invRotation = QuaternionInvert(inRotation); + Vector3 invScale = Vector3Divide((Vector3){ 1.0f, 1.0f, 1.0f }, inScale); + + Vector3 boneTranslation = Vector3Add( + Vector3RotateByQuaternion(Vector3Multiply(outScale, invTranslation), + outRotation), outTranslation); + Quaternion boneRotation = QuaternionMultiply(outRotation, invRotation); + Vector3 boneScale = Vector3Multiply(outScale, invScale); + + Matrix boneMatrix = MatrixMultiply(MatrixMultiply( + QuaternionToMatrix(boneRotation), + MatrixTranslate(boneTranslation.x, boneTranslation.y, boneTranslation.z)), + MatrixScale(boneScale.x, boneScale.y, boneScale.z)); + + model.meshes[i].boneMatrices[boneId] = boneMatrix; + } + } + } + } +} + // Unload animation array data void UnloadModelAnimations(ModelAnimation *animations, int animCount) { @@ -4671,6 +4796,17 @@ static Model LoadIQM(const char *fileName) } BuildPoseFromParentJoints(model.bones, model.boneCount, model.bindPose); + + for (int i = 0; i < model.meshCount; i++) + { + model.meshes[i].boneCount = model.boneCount; + model.meshes[i].boneMatrices = RL_CALLOC(model.meshes[i].boneCount, sizeof(Matrix)); + + for (int j = 0; j < model.meshes[i].boneCount; j++) + { + model.meshes[i].boneMatrices[j] = MatrixIdentity(); + } + } UnloadFileData(fileData); @@ -5754,6 +5890,15 @@ static Model LoadGLTF(const char *fileName) { memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float)); } + + // Bone Transform Matrices + model.meshes[meshIndex].boneCount = model.boneCount; + model.meshes[meshIndex].boneMatrices = RL_CALLOC(model.meshes[meshIndex].boneCount, sizeof(Matrix)); + + for (int j = 0; j < model.meshes[meshIndex].boneCount; j++) + { + model.meshes[meshIndex].boneMatrices[j] = MatrixIdentity(); + } meshIndex++; // Move to next mesh } @@ -6522,6 +6667,13 @@ static Model LoadM3D(const char *fileName) { memcpy(model.meshes[i].animVertices, model.meshes[i].vertices, model.meshes[i].vertexCount*3*sizeof(float)); memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float)); + + model.meshes[i].boneCount = model.boneCount; + model.meshes[i].boneMatrices = RL_CALLOC(model.meshes[i].boneCount, sizeof(Matrix)); + for (j = 0; j < model.meshes[i].boneCount; j++) + { + model.meshes[i].boneMatrices[j] = MatrixIdentity(); + } } } From c09dadd9b5e347d6b6393a7274fef6db553dbd25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Sep 2024 15:30:53 +0000 Subject: [PATCH 086/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 48 +++++++++- parser/output/raylib_api.lua | 39 +++++++- parser/output/raylib_api.txt | 174 ++++++++++++++++++---------------- parser/output/raylib_api.xml | 20 +++- 4 files changed, 191 insertions(+), 90 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 781454dc28ba..d1c58757b4b8 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -850,12 +850,22 @@ { "type": "unsigned char *", "name": "boneIds", - "description": "Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning)" + "description": "Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6)" }, { "type": "float *", "name": "boneWeights", - "description": "Vertex bone weight, up to 4 bones influence by vertex (skinning)" + "description": "Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7)" + }, + { + "type": "Matrix *", + "name": "boneMatrices", + "description": "Bones animated transformation matrices" + }, + { + "type": "int", + "name": "boneCount", + "description": "Number of bones" }, { "type": "unsigned int", @@ -2518,6 +2528,21 @@ "name": "SHADER_LOC_MAP_BRDF", "value": 25, "description": "Shader location: sampler2d texture: brdf" + }, + { + "name": "SHADER_LOC_VERTEX_BONEIDS", + "value": 26, + "description": "Shader location: vertex attribute: boneIds" + }, + { + "name": "SHADER_LOC_VERTEX_BONEWEIGHTS", + "value": 27, + "description": "Shader location: vertex attribute: boneWeights" + }, + { + "name": "SHADER_LOC_BONE_MATRICES", + "value": 28, + "description": "Shader location: array of matrices uniform: boneMatrices" } ] }, @@ -11066,6 +11091,25 @@ } ] }, + { + "name": "UpdateModelAnimationBoneMatrices", + "description": "Update model animation mesh bone matrices", + "returnType": "void", + "params": [ + { + "type": "Model", + "name": "model" + }, + { + "type": "ModelAnimation", + "name": "anim" + }, + { + "type": "int", + "name": "frame" + } + ] + }, { "name": "CheckCollisionSpheres", "description": "Check collision between two spheres", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 9cf39855ecfe..0d99407b74ad 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -850,12 +850,22 @@ return { { type = "unsigned char *", name = "boneIds", - description = "Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning)" + description = "Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6)" }, { type = "float *", name = "boneWeights", - description = "Vertex bone weight, up to 4 bones influence by vertex (skinning)" + description = "Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7)" + }, + { + type = "Matrix *", + name = "boneMatrices", + description = "Bones animated transformation matrices" + }, + { + type = "int", + name = "boneCount", + description = "Number of bones" }, { type = "unsigned int", @@ -2518,6 +2528,21 @@ return { name = "SHADER_LOC_MAP_BRDF", value = 25, description = "Shader location: sampler2d texture: brdf" + }, + { + name = "SHADER_LOC_VERTEX_BONEIDS", + value = 26, + description = "Shader location: vertex attribute: boneIds" + }, + { + name = "SHADER_LOC_VERTEX_BONEWEIGHTS", + value = 27, + description = "Shader location: vertex attribute: boneWeights" + }, + { + name = "SHADER_LOC_BONE_MATRICES", + value = 28, + description = "Shader location: array of matrices uniform: boneMatrices" } } }, @@ -7586,6 +7611,16 @@ return { {type = "ModelAnimation", name = "anim"} } }, + { + name = "UpdateModelAnimationBoneMatrices", + description = "Update model animation mesh bone matrices", + returnType = "void", + params = { + {type = "Model", name = "model"}, + {type = "ModelAnimation", name = "anim"}, + {type = "int", name = "frame"} + } + }, { name = "CheckCollisionSpheres", description = "Check collision between two spheres", diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 07b34c0cebbf..9c92906e0493 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -403,7 +403,7 @@ Struct 14: Camera2D (4 fields) Field[2]: Vector2 target // Camera target (rotation and zoom origin) Field[3]: float rotation // Camera rotation in degrees Field[4]: float zoom // Camera zoom (scaling), should be 1.0f by default -Struct 15: Mesh (15 fields) +Struct 15: Mesh (17 fields) Name: Mesh Description: Mesh, vertex data and vao/vbo Field[1]: int vertexCount // Number of vertices stored in arrays @@ -417,10 +417,12 @@ Struct 15: Mesh (15 fields) Field[9]: unsigned short * indices // Vertex indices (in case vertex data comes indexed) Field[10]: float * animVertices // Animated vertex positions (after bones transformations) Field[11]: float * animNormals // Animated normals (after bones transformations) - Field[12]: unsigned char * boneIds // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) - Field[13]: float * boneWeights // Vertex bone weight, up to 4 bones influence by vertex (skinning) - Field[14]: unsigned int vaoId // OpenGL Vertex Array Object id - Field[15]: unsigned int * vboId // OpenGL Vertex Buffer Objects id (default vertex data) + Field[12]: unsigned char * boneIds // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) + Field[13]: float * boneWeights // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + Field[14]: Matrix * boneMatrices // Bones animated transformation matrices + Field[15]: int boneCount // Number of bones + Field[16]: unsigned int vaoId // OpenGL Vertex Array Object id + Field[17]: unsigned int * vboId // OpenGL Vertex Buffer Objects id (default vertex data) Struct 16: Shader (2 fields) Name: Shader Description: Shader @@ -793,7 +795,7 @@ Enum 08: MaterialMapIndex (11 values) Value[MATERIAL_MAP_IRRADIANCE]: 8 Value[MATERIAL_MAP_PREFILTER]: 9 Value[MATERIAL_MAP_BRDF]: 10 -Enum 09: ShaderLocationIndex (26 values) +Enum 09: ShaderLocationIndex (29 values) Name: ShaderLocationIndex Description: Shader location index Value[SHADER_LOC_VERTEX_POSITION]: 0 @@ -822,6 +824,9 @@ Enum 09: ShaderLocationIndex (26 values) Value[SHADER_LOC_MAP_IRRADIANCE]: 23 Value[SHADER_LOC_MAP_PREFILTER]: 24 Value[SHADER_LOC_MAP_BRDF]: 25 + Value[SHADER_LOC_VERTEX_BONEIDS]: 26 + Value[SHADER_LOC_VERTEX_BONEWEIGHTS]: 27 + Value[SHADER_LOC_BONE_MATRICES]: 28 Enum 10: ShaderUniformDataType (9 values) Name: ShaderUniformDataType Description: Shader uniform data type @@ -984,7 +989,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 576 +Functions found: 577 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -4217,7 +4222,14 @@ Function 502: IsModelAnimationValid() (2 input parameters) Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 503: CheckCollisionSpheres() (4 input parameters) +Function 503: UpdateModelAnimationBoneMatrices() (3 input parameters) + Name: UpdateModelAnimationBoneMatrices + Return type: void + Description: Update model animation mesh bone matrices + Param[1]: model (type: Model) + Param[2]: anim (type: ModelAnimation) + Param[3]: frame (type: int) +Function 504: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4225,40 +4237,40 @@ Function 503: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 504: CheckCollisionBoxes() (2 input parameters) +Function 505: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 505: CheckCollisionBoxSphere() (3 input parameters) +Function 506: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 506: GetRayCollisionSphere() (3 input parameters) +Function 507: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 507: GetRayCollisionBox() (2 input parameters) +Function 508: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 508: GetRayCollisionMesh() (3 input parameters) +Function 509: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 509: GetRayCollisionTriangle() (4 input parameters) +Function 510: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4266,7 +4278,7 @@ Function 509: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 510: GetRayCollisionQuad() (5 input parameters) +Function 511: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4275,158 +4287,158 @@ Function 510: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 511: InitAudioDevice() (0 input parameters) +Function 512: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 512: CloseAudioDevice() (0 input parameters) +Function 513: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 513: IsAudioDeviceReady() (0 input parameters) +Function 514: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 514: SetMasterVolume() (1 input parameters) +Function 515: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 515: GetMasterVolume() (0 input parameters) +Function 516: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 516: LoadWave() (1 input parameters) +Function 517: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 517: LoadWaveFromMemory() (3 input parameters) +Function 518: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 518: IsWaveReady() (1 input parameters) +Function 519: IsWaveReady() (1 input parameters) Name: IsWaveReady Return type: bool Description: Checks if wave data is ready Param[1]: wave (type: Wave) -Function 519: LoadSound() (1 input parameters) +Function 520: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 520: LoadSoundFromWave() (1 input parameters) +Function 521: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 521: LoadSoundAlias() (1 input parameters) +Function 522: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 522: IsSoundReady() (1 input parameters) +Function 523: IsSoundReady() (1 input parameters) Name: IsSoundReady Return type: bool Description: Checks if a sound is ready Param[1]: sound (type: Sound) -Function 523: UpdateSound() (3 input parameters) +Function 524: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 524: UnloadWave() (1 input parameters) +Function 525: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 525: UnloadSound() (1 input parameters) +Function 526: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 526: UnloadSoundAlias() (1 input parameters) +Function 527: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 527: ExportWave() (2 input parameters) +Function 528: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 528: ExportWaveAsCode() (2 input parameters) +Function 529: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 529: PlaySound() (1 input parameters) +Function 530: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 530: StopSound() (1 input parameters) +Function 531: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 531: PauseSound() (1 input parameters) +Function 532: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 532: ResumeSound() (1 input parameters) +Function 533: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 533: IsSoundPlaying() (1 input parameters) +Function 534: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 534: SetSoundVolume() (2 input parameters) +Function 535: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 535: SetSoundPitch() (2 input parameters) +Function 536: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 536: SetSoundPan() (2 input parameters) +Function 537: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (0.5 is center) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 537: WaveCopy() (1 input parameters) +Function 538: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 538: WaveCrop() (3 input parameters) +Function 539: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 539: WaveFormat() (4 input parameters) +Function 540: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4434,203 +4446,203 @@ Function 539: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 540: LoadWaveSamples() (1 input parameters) +Function 541: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 541: UnloadWaveSamples() (1 input parameters) +Function 542: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 542: LoadMusicStream() (1 input parameters) +Function 543: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 543: LoadMusicStreamFromMemory() (3 input parameters) +Function 544: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 544: IsMusicReady() (1 input parameters) +Function 545: IsMusicReady() (1 input parameters) Name: IsMusicReady Return type: bool Description: Checks if a music stream is ready Param[1]: music (type: Music) -Function 545: UnloadMusicStream() (1 input parameters) +Function 546: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 546: PlayMusicStream() (1 input parameters) +Function 547: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 547: IsMusicStreamPlaying() (1 input parameters) +Function 548: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 548: UpdateMusicStream() (1 input parameters) +Function 549: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 549: StopMusicStream() (1 input parameters) +Function 550: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 550: PauseMusicStream() (1 input parameters) +Function 551: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 551: ResumeMusicStream() (1 input parameters) +Function 552: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 552: SeekMusicStream() (2 input parameters) +Function 553: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 553: SetMusicVolume() (2 input parameters) +Function 554: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 554: SetMusicPitch() (2 input parameters) +Function 555: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 555: SetMusicPan() (2 input parameters) +Function 556: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (0.5 is center) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 556: GetMusicTimeLength() (1 input parameters) +Function 557: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 557: GetMusicTimePlayed() (1 input parameters) +Function 558: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 558: LoadAudioStream() (3 input parameters) +Function 559: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 559: IsAudioStreamReady() (1 input parameters) +Function 560: IsAudioStreamReady() (1 input parameters) Name: IsAudioStreamReady Return type: bool Description: Checks if an audio stream is ready Param[1]: stream (type: AudioStream) -Function 560: UnloadAudioStream() (1 input parameters) +Function 561: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 561: UpdateAudioStream() (3 input parameters) +Function 562: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 562: IsAudioStreamProcessed() (1 input parameters) +Function 563: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 563: PlayAudioStream() (1 input parameters) +Function 564: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 564: PauseAudioStream() (1 input parameters) +Function 565: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 565: ResumeAudioStream() (1 input parameters) +Function 566: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 566: IsAudioStreamPlaying() (1 input parameters) +Function 567: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 567: StopAudioStream() (1 input parameters) +Function 568: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 568: SetAudioStreamVolume() (2 input parameters) +Function 569: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 569: SetAudioStreamPitch() (2 input parameters) +Function 570: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 570: SetAudioStreamPan() (2 input parameters) +Function 571: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 571: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 572: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 572: SetAudioStreamCallback() (2 input parameters) +Function 573: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 573: AttachAudioStreamProcessor() (2 input parameters) +Function 574: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives the samples as 'float' Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 574: DetachAudioStreamProcessor() (2 input parameters) +Function 575: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 575: AttachAudioMixedProcessor() (1 input parameters) +Function 576: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' Param[1]: processor (type: AudioCallback) -Function 576: DetachAudioMixedProcessor() (1 input parameters) +Function 577: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 6bd5a45819db..13ea5b7ab6c6 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -160,7 +160,7 @@ - + @@ -172,8 +172,10 @@ - - + + + + @@ -505,7 +507,7 @@ - + @@ -532,6 +534,9 @@ + + + @@ -670,7 +675,7 @@ - + @@ -2822,6 +2827,11 @@ + + + + + From e5d0cc978ae933ccccd41758d50d1cd57e3474d1 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 20 Sep 2024 17:32:01 +0200 Subject: [PATCH 087/107] Some minor tweaks --- src/platforms/rcore_desktop_rgfw.c | 2 +- src/rlgl.h | 3 +++ src/rmodels.c | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 64ab432e6c3e..ef01820cb02c 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -940,7 +940,7 @@ void PollInputEvents(void) SetupViewport(platform.window->r.w, platform.window->r.h); CORE.Window.screen.width = platform.window->r.w; CORE.Window.screen.height = platform.window->r.h; - CORE.Window.currentFbo.width = platform.window->r.w;; + CORE.Window.currentFbo.width = platform.window->r.w; CORE.Window.currentFbo.height = platform.window->r.h; CORE.Window.resizedLastFrame = true; } break; diff --git a/src/rlgl.h b/src/rlgl.h index b98934a115b8..83ef966cea52 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -350,6 +350,9 @@ #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 7 #endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition diff --git a/src/rmodels.c b/src/rmodels.c index 055fb1b1c62e..ebdd178fdd38 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4377,7 +4377,7 @@ static Model LoadOBJ(const char *fileName) } // if this is a new material, we need to allocate a new mesh if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) newMesh = true; - lastMaterial = objAttributes.material_ids[faceId];; + lastMaterial = objAttributes.material_ids[faceId]; if (newMesh) { From 212b1e5fe7a898729a5b24d6f4c1720c508cb0be Mon Sep 17 00:00:00 2001 From: Asdqwe Date: Sat, 21 Sep 2024 18:11:40 -0300 Subject: [PATCH 088/107] Fix rlgl standalone defaults (#4334) --- src/rlgl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 83ef966cea52..875b568f5258 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -351,7 +351,7 @@ #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 7 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 8 #endif //---------------------------------------------------------------------------------- @@ -4272,7 +4272,7 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) #endif case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); - + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() } #endif From 93379137664b33f7245cccf9542e0a4a0b9e30a8 Mon Sep 17 00:00:00 2001 From: Ridge3Dproductions <78836162+Ridge3Dproductions@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:12:48 -0400 Subject: [PATCH 089/107] Projects: Fix CMake example project (#4332) * Update CMakeLists.txt Add missing link flags * Update README.md Remove unneeded flag because this flag is defined in the updated CMakeList file * Update CMakeLists.txt --- projects/CMake/CMakeLists.txt | 4 ++-- projects/CMake/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/CMake/CMakeLists.txt b/projects/CMake/CMakeLists.txt index a95d68a4f368..96e33f344205 100644 --- a/projects/CMake/CMakeLists.txt +++ b/projects/CMake/CMakeLists.txt @@ -30,8 +30,8 @@ target_link_libraries(${PROJECT_NAME} raylib) # Web Configurations if (${PLATFORM} STREQUAL "Web") - # Tell Emscripten to build an example.html file. - set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html") + set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html") # Tell Emscripten to build an example.html file. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s ASYNCIFY -s GL_ENABLE_GET_PROC_ADDRESS=1") endif() # Checks if OSX and links appropriate frameworks (Only required on MacOS) diff --git a/projects/CMake/README.md b/projects/CMake/README.md index f7873c30f930..fc4fe5542d05 100644 --- a/projects/CMake/README.md +++ b/projects/CMake/README.md @@ -22,6 +22,6 @@ Compiling for the web requires the [Emscripten SDK](https://emscripten.org/docs/ ``` bash mkdir build cd build -emcmake cmake .. -DPLATFORM=Web -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXE_LINKER_FLAGS="-s USE_GLFW=3" -DCMAKE_EXECUTABLE_SUFFIX=".html" +emcmake cmake .. -DPLATFORM=Web -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXECUTABLE_SUFFIX=".html" emmake make -``` \ No newline at end of file +``` From daaca40416f4d1006382ecd51457df89ace0e21d Mon Sep 17 00:00:00 2001 From: Daniil Kisel <56605335+KislyjKisel@users.noreply.github.com> Date: Sun, 22 Sep 2024 14:45:10 +0300 Subject: [PATCH 090/107] Update parser's readme to mirror fixed help text (#4336) --- parser/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/README.md b/parser/README.md index 45a2689a0cfd..6616cdc510f1 100644 --- a/parser/README.md +++ b/parser/README.md @@ -41,7 +41,7 @@ OPTIONS: -f, --format : Define output format for parser data. Supported types: DEFAULT, JSON, XML, LUA - -d, --define : Define functions specifiers (i.e. RLAPI for raylib.h, RMDEF for raymath.h, etc.) + -d, --define : Define functions specifiers (i.e. RLAPI for raylib.h, RMAPI for raymath.h, etc.) NOTE: If no specifier defined, defaults to: RLAPI -t, --truncate : Define string to truncate input after (i.e. "RLGL IMPLEMENTATION" for rlgl.h) @@ -56,7 +56,7 @@ EXAMPLES: > raylib_parser --output raylib_data.info --format XML Process to generate as XML text data - > raylib_parser --input raymath.h --output raymath_data.info --format XML + > raylib_parser --input raymath.h --output raymath_data.info --format XML --define RMAPI Process to generate as XML text data ``` From 7d3f04ca356c9a088e0901c7a179955683e66207 Mon Sep 17 00:00:00 2001 From: Daniil Kisel <56605335+KislyjKisel@users.noreply.github.com> Date: Sun, 22 Sep 2024 15:36:06 +0300 Subject: [PATCH 091/107] Update BINDINGS.md (#4337) Updated Lean4 bindings --- BINDINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index 6ac54498e8a1..e79d39c36e77 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -81,7 +81,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-bqn](https://github.com/Brian-ED/raylib-bqn) | **5.0** | [BQN](https://mlochbaum.github.io/BQN) | MIT | | [rayjs](https://github.com/mode777/rayjs) | 4.6-dev | [QuickJS](https://bellard.org/quickjs) | MIT | | [raylib-raku](https://github.com/vushu/raylib-raku) | **auto** | [Raku](https://www.raku.org) | Artistic License 2.0 | -| [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) | 4.5 | [Lean4](https://lean-lang.org) | BSD-3-Clause | +| [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) | **5.5-dev** | [Lean4](https://lean-lang.org) | BSD-3-Clause | | [raylib-cobol](https://codeberg.org/glowiak/raylib-cobol) | **auto** | [COBOL](https://gnucobol.sourceforge.io) | Public domain | | [raylib-apl](https://github.com/Brian-ED/raylib-apl) | **5.0** | [Dyalog APL](https://www.dyalog.com/) | MIT | From 1eb8ff5e54d73365997fec442103e686aaed1d90 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 18:05:58 +0200 Subject: [PATCH 092/107] `LoadFontDefault()`: Initialize glyphs and recs to zero #4319 --- src/rtext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 34b6fcedb6b9..e4c89d5f1013 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -260,8 +260,8 @@ extern void LoadFontDefault(void) // Allocate space for our characters info data // NOTE: This memory must be freed at end! --> Done by CloseWindow() - defaultFont.glyphs = (GlyphInfo *)RL_MALLOC(defaultFont.glyphCount*sizeof(GlyphInfo)); - defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.glyphCount*sizeof(Rectangle)); + defaultFont.glyphs = (GlyphInfo *)RL_CALLOC(defaultFont.glyphCount, sizeof(GlyphInfo)); + defaultFont.recs = (Rectangle *)RL_CALLOC(defaultFont.glyphCount, sizeof(Rectangle)); int currentLine = 0; int currentPosX = charsDivisor; From 55a25ac04bb448fcc17e9347314b56d5b6881ae0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 22:11:08 +0200 Subject: [PATCH 093/107] ADDED: `MakeDirectory()` --- src/raylib.h | 1 + src/rcore.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index ee34b6441a88..f298fd731985 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1127,6 +1127,7 @@ RLAPI const char *GetDirectoryPath(const char *filePath); // Get full pa RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) +RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS diff --git a/src/rcore.c b/src/rcore.c index 2fd79f5c9bee..1501aced1168 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -191,16 +191,25 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #endif #if defined(_WIN32) - #include // Required for: _getch(), _chdir() + #include // Required for: _access() [Used in FileExists()] + #include // Required for: _getch(), _chdir(), _mkdir() #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() #define CHDIR _chdir - #include // Required for: _access() [Used in FileExists()] #else #include // Required for: getch(), chdir() (POSIX), access() #define GETCWD getcwd #define CHDIR chdir #endif +#if defined(_MSC_VER) && ((defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__)) + #include // Required for: _mkdir() + #define MKDIR(dir) _mkdir(dir) +#elif defined __GNUC__ + #include + #include // Required for: mkdir() + #define MKDIR(dir) mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -2267,6 +2276,40 @@ void UnloadDirectoryFiles(FilePathList files) RL_FREE(files.paths); } +// Create directories (including full path requested), returns 0 on success +int MakeDirectory(const char *dirPath) +{ + if ((dirPath == NULL) || (dirPath[0] == '\0')) return 1; // Path is not valid + if (DirectoryExists(dirPath)) return 0; // Path already exists (is valid) + + // Copy path string to avoid modifying original + int len = (int)strlen(dirPath) + 1; + char *pathcpy = (char *)RL_CALLOC(len, 1); + memcpy(pathcpy, dirPath, len); + + // Iterate over pathcpy, create each subdirectory as needed + for (int i = 0; (i < len) && (pathcpy[i] != '\0'); i++) + { + if (pathcpy[i] == ':') i++; + else + { + if ((pathcpy[i] == '\\') || (pathcpy[i] == '/')) + { + pathcpy[i] = '\0'; + if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); + pathcpy[i] = '/'; + } + } + } + + // Create final directory + if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); + + RL_FREE(pathcpy); + + return 0; +} + // Change working directory, returns true on success bool ChangeDirectory(const char *dir) { From 1c7b18923ca05e67309f9d8732fcca9a0b1de8df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Sep 2024 20:11:24 +0000 Subject: [PATCH 094/107] Update raylib_api.* by CI --- parser/output/raylib_api.json | 11 + parser/output/raylib_api.lua | 8 + parser/output/raylib_api.txt | 895 +++++++++++++++++----------------- parser/output/raylib_api.xml | 5 +- 4 files changed, 473 insertions(+), 446 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index d1c58757b4b8..2e599496d95c 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -4502,6 +4502,17 @@ "description": "Get the directory of the running application (uses static string)", "returnType": "const char *" }, + { + "name": "MakeDirectory", + "description": "Create directories (including full path requested), returns 0 on success", + "returnType": "int", + "params": [ + { + "type": "const char *", + "name": "dirPath" + } + ] + }, { "name": "ChangeDirectory", "description": "Change working directory, return true on success", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 0d99407b74ad..69ae07eda314 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4067,6 +4067,14 @@ return { description = "Get the directory of the running application (uses static string)", returnType = "const char *" }, + { + name = "MakeDirectory", + description = "Create directories (including full path requested), returns 0 on success", + returnType = "int", + params = { + {type = "const char *", name = "dirPath"} + } + }, { name = "ChangeDirectory", description = "Change working directory, return true on success", diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 9c92906e0493..458d12da0642 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -989,7 +989,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 577 +Functions found: 578 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -1704,373 +1704,378 @@ Function 133: GetApplicationDirectory() (0 input parameters) Return type: const char * Description: Get the directory of the running application (uses static string) No input parameters -Function 134: ChangeDirectory() (1 input parameters) +Function 134: MakeDirectory() (1 input parameters) + Name: MakeDirectory + Return type: int + Description: Create directories (including full path requested), returns 0 on success + Param[1]: dirPath (type: const char *) +Function 135: ChangeDirectory() (1 input parameters) Name: ChangeDirectory Return type: bool Description: Change working directory, return true on success Param[1]: dir (type: const char *) -Function 135: IsPathFile() (1 input parameters) +Function 136: IsPathFile() (1 input parameters) Name: IsPathFile Return type: bool Description: Check if a given path is a file or a directory Param[1]: path (type: const char *) -Function 136: IsFileNameValid() (1 input parameters) +Function 137: IsFileNameValid() (1 input parameters) Name: IsFileNameValid Return type: bool Description: Check if fileName is valid for the platform/OS Param[1]: fileName (type: const char *) -Function 137: LoadDirectoryFiles() (1 input parameters) +Function 138: LoadDirectoryFiles() (1 input parameters) Name: LoadDirectoryFiles Return type: FilePathList Description: Load directory filepaths Param[1]: dirPath (type: const char *) -Function 138: LoadDirectoryFilesEx() (3 input parameters) +Function 139: LoadDirectoryFilesEx() (3 input parameters) Name: LoadDirectoryFilesEx Return type: FilePathList Description: Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result Param[1]: basePath (type: const char *) Param[2]: filter (type: const char *) Param[3]: scanSubdirs (type: bool) -Function 139: UnloadDirectoryFiles() (1 input parameters) +Function 140: UnloadDirectoryFiles() (1 input parameters) Name: UnloadDirectoryFiles Return type: void Description: Unload filepaths Param[1]: files (type: FilePathList) -Function 140: IsFileDropped() (0 input parameters) +Function 141: IsFileDropped() (0 input parameters) Name: IsFileDropped Return type: bool Description: Check if a file has been dropped into window No input parameters -Function 141: LoadDroppedFiles() (0 input parameters) +Function 142: LoadDroppedFiles() (0 input parameters) Name: LoadDroppedFiles Return type: FilePathList Description: Load dropped filepaths No input parameters -Function 142: UnloadDroppedFiles() (1 input parameters) +Function 143: UnloadDroppedFiles() (1 input parameters) Name: UnloadDroppedFiles Return type: void Description: Unload dropped filepaths Param[1]: files (type: FilePathList) -Function 143: GetFileModTime() (1 input parameters) +Function 144: GetFileModTime() (1 input parameters) Name: GetFileModTime Return type: long Description: Get file modification time (last write time) Param[1]: fileName (type: const char *) -Function 144: CompressData() (3 input parameters) +Function 145: CompressData() (3 input parameters) Name: CompressData Return type: unsigned char * Description: Compress data (DEFLATE algorithm), memory must be MemFree() Param[1]: data (type: const unsigned char *) Param[2]: dataSize (type: int) Param[3]: compDataSize (type: int *) -Function 145: DecompressData() (3 input parameters) +Function 146: DecompressData() (3 input parameters) Name: DecompressData Return type: unsigned char * Description: Decompress data (DEFLATE algorithm), memory must be MemFree() Param[1]: compData (type: const unsigned char *) Param[2]: compDataSize (type: int) Param[3]: dataSize (type: int *) -Function 146: EncodeDataBase64() (3 input parameters) +Function 147: EncodeDataBase64() (3 input parameters) Name: EncodeDataBase64 Return type: char * Description: Encode data to Base64 string, memory must be MemFree() Param[1]: data (type: const unsigned char *) Param[2]: dataSize (type: int) Param[3]: outputSize (type: int *) -Function 147: DecodeDataBase64() (2 input parameters) +Function 148: DecodeDataBase64() (2 input parameters) Name: DecodeDataBase64 Return type: unsigned char * Description: Decode Base64 string data, memory must be MemFree() Param[1]: data (type: const unsigned char *) Param[2]: outputSize (type: int *) -Function 148: LoadAutomationEventList() (1 input parameters) +Function 149: LoadAutomationEventList() (1 input parameters) Name: LoadAutomationEventList Return type: AutomationEventList Description: Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS Param[1]: fileName (type: const char *) -Function 149: UnloadAutomationEventList() (1 input parameters) +Function 150: UnloadAutomationEventList() (1 input parameters) Name: UnloadAutomationEventList Return type: void Description: Unload automation events list from file Param[1]: list (type: AutomationEventList) -Function 150: ExportAutomationEventList() (2 input parameters) +Function 151: ExportAutomationEventList() (2 input parameters) Name: ExportAutomationEventList Return type: bool Description: Export automation events list as text file Param[1]: list (type: AutomationEventList) Param[2]: fileName (type: const char *) -Function 151: SetAutomationEventList() (1 input parameters) +Function 152: SetAutomationEventList() (1 input parameters) Name: SetAutomationEventList Return type: void Description: Set automation event list to record to Param[1]: list (type: AutomationEventList *) -Function 152: SetAutomationEventBaseFrame() (1 input parameters) +Function 153: SetAutomationEventBaseFrame() (1 input parameters) Name: SetAutomationEventBaseFrame Return type: void Description: Set automation event internal base frame to start recording Param[1]: frame (type: int) -Function 153: StartAutomationEventRecording() (0 input parameters) +Function 154: StartAutomationEventRecording() (0 input parameters) Name: StartAutomationEventRecording Return type: void Description: Start recording automation events (AutomationEventList must be set) No input parameters -Function 154: StopAutomationEventRecording() (0 input parameters) +Function 155: StopAutomationEventRecording() (0 input parameters) Name: StopAutomationEventRecording Return type: void Description: Stop recording automation events No input parameters -Function 155: PlayAutomationEvent() (1 input parameters) +Function 156: PlayAutomationEvent() (1 input parameters) Name: PlayAutomationEvent Return type: void Description: Play a recorded automation event Param[1]: event (type: AutomationEvent) -Function 156: IsKeyPressed() (1 input parameters) +Function 157: IsKeyPressed() (1 input parameters) Name: IsKeyPressed Return type: bool Description: Check if a key has been pressed once Param[1]: key (type: int) -Function 157: IsKeyPressedRepeat() (1 input parameters) +Function 158: IsKeyPressedRepeat() (1 input parameters) Name: IsKeyPressedRepeat Return type: bool Description: Check if a key has been pressed again (Only PLATFORM_DESKTOP) Param[1]: key (type: int) -Function 158: IsKeyDown() (1 input parameters) +Function 159: IsKeyDown() (1 input parameters) Name: IsKeyDown Return type: bool Description: Check if a key is being pressed Param[1]: key (type: int) -Function 159: IsKeyReleased() (1 input parameters) +Function 160: IsKeyReleased() (1 input parameters) Name: IsKeyReleased Return type: bool Description: Check if a key has been released once Param[1]: key (type: int) -Function 160: IsKeyUp() (1 input parameters) +Function 161: IsKeyUp() (1 input parameters) Name: IsKeyUp Return type: bool Description: Check if a key is NOT being pressed Param[1]: key (type: int) -Function 161: GetKeyPressed() (0 input parameters) +Function 162: GetKeyPressed() (0 input parameters) Name: GetKeyPressed Return type: int Description: Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty No input parameters -Function 162: GetCharPressed() (0 input parameters) +Function 163: GetCharPressed() (0 input parameters) Name: GetCharPressed Return type: int Description: Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty No input parameters -Function 163: SetExitKey() (1 input parameters) +Function 164: SetExitKey() (1 input parameters) Name: SetExitKey Return type: void Description: Set a custom key to exit program (default is ESC) Param[1]: key (type: int) -Function 164: IsGamepadAvailable() (1 input parameters) +Function 165: IsGamepadAvailable() (1 input parameters) Name: IsGamepadAvailable Return type: bool Description: Check if a gamepad is available Param[1]: gamepad (type: int) -Function 165: GetGamepadName() (1 input parameters) +Function 166: GetGamepadName() (1 input parameters) Name: GetGamepadName Return type: const char * Description: Get gamepad internal name id Param[1]: gamepad (type: int) -Function 166: IsGamepadButtonPressed() (2 input parameters) +Function 167: IsGamepadButtonPressed() (2 input parameters) Name: IsGamepadButtonPressed Return type: bool Description: Check if a gamepad button has been pressed once Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 167: IsGamepadButtonDown() (2 input parameters) +Function 168: IsGamepadButtonDown() (2 input parameters) Name: IsGamepadButtonDown Return type: bool Description: Check if a gamepad button is being pressed Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 168: IsGamepadButtonReleased() (2 input parameters) +Function 169: IsGamepadButtonReleased() (2 input parameters) Name: IsGamepadButtonReleased Return type: bool Description: Check if a gamepad button has been released once Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 169: IsGamepadButtonUp() (2 input parameters) +Function 170: IsGamepadButtonUp() (2 input parameters) Name: IsGamepadButtonUp Return type: bool Description: Check if a gamepad button is NOT being pressed Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 170: GetGamepadButtonPressed() (0 input parameters) +Function 171: GetGamepadButtonPressed() (0 input parameters) Name: GetGamepadButtonPressed Return type: int Description: Get the last gamepad button pressed No input parameters -Function 171: GetGamepadAxisCount() (1 input parameters) +Function 172: GetGamepadAxisCount() (1 input parameters) Name: GetGamepadAxisCount Return type: int Description: Get gamepad axis count for a gamepad Param[1]: gamepad (type: int) -Function 172: GetGamepadAxisMovement() (2 input parameters) +Function 173: GetGamepadAxisMovement() (2 input parameters) Name: GetGamepadAxisMovement Return type: float Description: Get axis movement value for a gamepad axis Param[1]: gamepad (type: int) Param[2]: axis (type: int) -Function 173: SetGamepadMappings() (1 input parameters) +Function 174: SetGamepadMappings() (1 input parameters) Name: SetGamepadMappings Return type: int Description: Set internal gamepad mappings (SDL_GameControllerDB) Param[1]: mappings (type: const char *) -Function 174: SetGamepadVibration() (3 input parameters) +Function 175: SetGamepadVibration() (3 input parameters) Name: SetGamepadVibration Return type: void Description: Set gamepad vibration for both motors Param[1]: gamepad (type: int) Param[2]: leftMotor (type: float) Param[3]: rightMotor (type: float) -Function 175: IsMouseButtonPressed() (1 input parameters) +Function 176: IsMouseButtonPressed() (1 input parameters) Name: IsMouseButtonPressed Return type: bool Description: Check if a mouse button has been pressed once Param[1]: button (type: int) -Function 176: IsMouseButtonDown() (1 input parameters) +Function 177: IsMouseButtonDown() (1 input parameters) Name: IsMouseButtonDown Return type: bool Description: Check if a mouse button is being pressed Param[1]: button (type: int) -Function 177: IsMouseButtonReleased() (1 input parameters) +Function 178: IsMouseButtonReleased() (1 input parameters) Name: IsMouseButtonReleased Return type: bool Description: Check if a mouse button has been released once Param[1]: button (type: int) -Function 178: IsMouseButtonUp() (1 input parameters) +Function 179: IsMouseButtonUp() (1 input parameters) Name: IsMouseButtonUp Return type: bool Description: Check if a mouse button is NOT being pressed Param[1]: button (type: int) -Function 179: GetMouseX() (0 input parameters) +Function 180: GetMouseX() (0 input parameters) Name: GetMouseX Return type: int Description: Get mouse position X No input parameters -Function 180: GetMouseY() (0 input parameters) +Function 181: GetMouseY() (0 input parameters) Name: GetMouseY Return type: int Description: Get mouse position Y No input parameters -Function 181: GetMousePosition() (0 input parameters) +Function 182: GetMousePosition() (0 input parameters) Name: GetMousePosition Return type: Vector2 Description: Get mouse position XY No input parameters -Function 182: GetMouseDelta() (0 input parameters) +Function 183: GetMouseDelta() (0 input parameters) Name: GetMouseDelta Return type: Vector2 Description: Get mouse delta between frames No input parameters -Function 183: SetMousePosition() (2 input parameters) +Function 184: SetMousePosition() (2 input parameters) Name: SetMousePosition Return type: void Description: Set mouse position XY Param[1]: x (type: int) Param[2]: y (type: int) -Function 184: SetMouseOffset() (2 input parameters) +Function 185: SetMouseOffset() (2 input parameters) Name: SetMouseOffset Return type: void Description: Set mouse offset Param[1]: offsetX (type: int) Param[2]: offsetY (type: int) -Function 185: SetMouseScale() (2 input parameters) +Function 186: SetMouseScale() (2 input parameters) Name: SetMouseScale Return type: void Description: Set mouse scaling Param[1]: scaleX (type: float) Param[2]: scaleY (type: float) -Function 186: GetMouseWheelMove() (0 input parameters) +Function 187: GetMouseWheelMove() (0 input parameters) Name: GetMouseWheelMove Return type: float Description: Get mouse wheel movement for X or Y, whichever is larger No input parameters -Function 187: GetMouseWheelMoveV() (0 input parameters) +Function 188: GetMouseWheelMoveV() (0 input parameters) Name: GetMouseWheelMoveV Return type: Vector2 Description: Get mouse wheel movement for both X and Y No input parameters -Function 188: SetMouseCursor() (1 input parameters) +Function 189: SetMouseCursor() (1 input parameters) Name: SetMouseCursor Return type: void Description: Set mouse cursor Param[1]: cursor (type: int) -Function 189: GetTouchX() (0 input parameters) +Function 190: GetTouchX() (0 input parameters) Name: GetTouchX Return type: int Description: Get touch position X for touch point 0 (relative to screen size) No input parameters -Function 190: GetTouchY() (0 input parameters) +Function 191: GetTouchY() (0 input parameters) Name: GetTouchY Return type: int Description: Get touch position Y for touch point 0 (relative to screen size) No input parameters -Function 191: GetTouchPosition() (1 input parameters) +Function 192: GetTouchPosition() (1 input parameters) Name: GetTouchPosition Return type: Vector2 Description: Get touch position XY for a touch point index (relative to screen size) Param[1]: index (type: int) -Function 192: GetTouchPointId() (1 input parameters) +Function 193: GetTouchPointId() (1 input parameters) Name: GetTouchPointId Return type: int Description: Get touch point identifier for given index Param[1]: index (type: int) -Function 193: GetTouchPointCount() (0 input parameters) +Function 194: GetTouchPointCount() (0 input parameters) Name: GetTouchPointCount Return type: int Description: Get number of touch points No input parameters -Function 194: SetGesturesEnabled() (1 input parameters) +Function 195: SetGesturesEnabled() (1 input parameters) Name: SetGesturesEnabled Return type: void Description: Enable a set of gestures using flags Param[1]: flags (type: unsigned int) -Function 195: IsGestureDetected() (1 input parameters) +Function 196: IsGestureDetected() (1 input parameters) Name: IsGestureDetected Return type: bool Description: Check if a gesture have been detected Param[1]: gesture (type: unsigned int) -Function 196: GetGestureDetected() (0 input parameters) +Function 197: GetGestureDetected() (0 input parameters) Name: GetGestureDetected Return type: int Description: Get latest detected gesture No input parameters -Function 197: GetGestureHoldDuration() (0 input parameters) +Function 198: GetGestureHoldDuration() (0 input parameters) Name: GetGestureHoldDuration Return type: float Description: Get gesture hold time in milliseconds No input parameters -Function 198: GetGestureDragVector() (0 input parameters) +Function 199: GetGestureDragVector() (0 input parameters) Name: GetGestureDragVector Return type: Vector2 Description: Get gesture drag vector No input parameters -Function 199: GetGestureDragAngle() (0 input parameters) +Function 200: GetGestureDragAngle() (0 input parameters) Name: GetGestureDragAngle Return type: float Description: Get gesture drag angle No input parameters -Function 200: GetGesturePinchVector() (0 input parameters) +Function 201: GetGesturePinchVector() (0 input parameters) Name: GetGesturePinchVector Return type: Vector2 Description: Get gesture pinch delta No input parameters -Function 201: GetGesturePinchAngle() (0 input parameters) +Function 202: GetGesturePinchAngle() (0 input parameters) Name: GetGesturePinchAngle Return type: float Description: Get gesture pinch angle No input parameters -Function 202: UpdateCamera() (2 input parameters) +Function 203: UpdateCamera() (2 input parameters) Name: UpdateCamera Return type: void Description: Update camera position for selected mode Param[1]: camera (type: Camera *) Param[2]: mode (type: int) -Function 203: UpdateCameraPro() (4 input parameters) +Function 204: UpdateCameraPro() (4 input parameters) Name: UpdateCameraPro Return type: void Description: Update camera movement/rotation @@ -2078,36 +2083,36 @@ Function 203: UpdateCameraPro() (4 input parameters) Param[2]: movement (type: Vector3) Param[3]: rotation (type: Vector3) Param[4]: zoom (type: float) -Function 204: SetShapesTexture() (2 input parameters) +Function 205: SetShapesTexture() (2 input parameters) Name: SetShapesTexture Return type: void Description: Set texture and rectangle to be used on shapes drawing Param[1]: texture (type: Texture2D) Param[2]: source (type: Rectangle) -Function 205: GetShapesTexture() (0 input parameters) +Function 206: GetShapesTexture() (0 input parameters) Name: GetShapesTexture Return type: Texture2D Description: Get texture that is used for shapes drawing No input parameters -Function 206: GetShapesTextureRectangle() (0 input parameters) +Function 207: GetShapesTextureRectangle() (0 input parameters) Name: GetShapesTextureRectangle Return type: Rectangle Description: Get texture source rectangle that is used for shapes drawing No input parameters -Function 207: DrawPixel() (3 input parameters) +Function 208: DrawPixel() (3 input parameters) Name: DrawPixel Return type: void Description: Draw a pixel Param[1]: posX (type: int) Param[2]: posY (type: int) Param[3]: color (type: Color) -Function 208: DrawPixelV() (2 input parameters) +Function 209: DrawPixelV() (2 input parameters) Name: DrawPixelV Return type: void Description: Draw a pixel (Vector version) Param[1]: position (type: Vector2) Param[2]: color (type: Color) -Function 209: DrawLine() (5 input parameters) +Function 210: DrawLine() (5 input parameters) Name: DrawLine Return type: void Description: Draw a line @@ -2116,14 +2121,14 @@ Function 209: DrawLine() (5 input parameters) Param[3]: endPosX (type: int) Param[4]: endPosY (type: int) Param[5]: color (type: Color) -Function 210: DrawLineV() (3 input parameters) +Function 211: DrawLineV() (3 input parameters) Name: DrawLineV Return type: void Description: Draw a line (using gl lines) Param[1]: startPos (type: Vector2) Param[2]: endPos (type: Vector2) Param[3]: color (type: Color) -Function 211: DrawLineEx() (4 input parameters) +Function 212: DrawLineEx() (4 input parameters) Name: DrawLineEx Return type: void Description: Draw a line (using triangles/quads) @@ -2131,14 +2136,14 @@ Function 211: DrawLineEx() (4 input parameters) Param[2]: endPos (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 212: DrawLineStrip() (3 input parameters) +Function 213: DrawLineStrip() (3 input parameters) Name: DrawLineStrip Return type: void Description: Draw lines sequence (using gl lines) Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 213: DrawLineBezier() (4 input parameters) +Function 214: DrawLineBezier() (4 input parameters) Name: DrawLineBezier Return type: void Description: Draw line segment cubic-bezier in-out interpolation @@ -2146,7 +2151,7 @@ Function 213: DrawLineBezier() (4 input parameters) Param[2]: endPos (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 214: DrawCircle() (4 input parameters) +Function 215: DrawCircle() (4 input parameters) Name: DrawCircle Return type: void Description: Draw a color-filled circle @@ -2154,7 +2159,7 @@ Function 214: DrawCircle() (4 input parameters) Param[2]: centerY (type: int) Param[3]: radius (type: float) Param[4]: color (type: Color) -Function 215: DrawCircleSector() (6 input parameters) +Function 216: DrawCircleSector() (6 input parameters) Name: DrawCircleSector Return type: void Description: Draw a piece of a circle @@ -2164,7 +2169,7 @@ Function 215: DrawCircleSector() (6 input parameters) Param[4]: endAngle (type: float) Param[5]: segments (type: int) Param[6]: color (type: Color) -Function 216: DrawCircleSectorLines() (6 input parameters) +Function 217: DrawCircleSectorLines() (6 input parameters) Name: DrawCircleSectorLines Return type: void Description: Draw circle sector outline @@ -2174,7 +2179,7 @@ Function 216: DrawCircleSectorLines() (6 input parameters) Param[4]: endAngle (type: float) Param[5]: segments (type: int) Param[6]: color (type: Color) -Function 217: DrawCircleGradient() (5 input parameters) +Function 218: DrawCircleGradient() (5 input parameters) Name: DrawCircleGradient Return type: void Description: Draw a gradient-filled circle @@ -2183,14 +2188,14 @@ Function 217: DrawCircleGradient() (5 input parameters) Param[3]: radius (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 218: DrawCircleV() (3 input parameters) +Function 219: DrawCircleV() (3 input parameters) Name: DrawCircleV Return type: void Description: Draw a color-filled circle (Vector version) Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 219: DrawCircleLines() (4 input parameters) +Function 220: DrawCircleLines() (4 input parameters) Name: DrawCircleLines Return type: void Description: Draw circle outline @@ -2198,14 +2203,14 @@ Function 219: DrawCircleLines() (4 input parameters) Param[2]: centerY (type: int) Param[3]: radius (type: float) Param[4]: color (type: Color) -Function 220: DrawCircleLinesV() (3 input parameters) +Function 221: DrawCircleLinesV() (3 input parameters) Name: DrawCircleLinesV Return type: void Description: Draw circle outline (Vector version) Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 221: DrawEllipse() (5 input parameters) +Function 222: DrawEllipse() (5 input parameters) Name: DrawEllipse Return type: void Description: Draw ellipse @@ -2214,7 +2219,7 @@ Function 221: DrawEllipse() (5 input parameters) Param[3]: radiusH (type: float) Param[4]: radiusV (type: float) Param[5]: color (type: Color) -Function 222: DrawEllipseLines() (5 input parameters) +Function 223: DrawEllipseLines() (5 input parameters) Name: DrawEllipseLines Return type: void Description: Draw ellipse outline @@ -2223,7 +2228,7 @@ Function 222: DrawEllipseLines() (5 input parameters) Param[3]: radiusH (type: float) Param[4]: radiusV (type: float) Param[5]: color (type: Color) -Function 223: DrawRing() (7 input parameters) +Function 224: DrawRing() (7 input parameters) Name: DrawRing Return type: void Description: Draw ring @@ -2234,7 +2239,7 @@ Function 223: DrawRing() (7 input parameters) Param[5]: endAngle (type: float) Param[6]: segments (type: int) Param[7]: color (type: Color) -Function 224: DrawRingLines() (7 input parameters) +Function 225: DrawRingLines() (7 input parameters) Name: DrawRingLines Return type: void Description: Draw ring outline @@ -2245,7 +2250,7 @@ Function 224: DrawRingLines() (7 input parameters) Param[5]: endAngle (type: float) Param[6]: segments (type: int) Param[7]: color (type: Color) -Function 225: DrawRectangle() (5 input parameters) +Function 226: DrawRectangle() (5 input parameters) Name: DrawRectangle Return type: void Description: Draw a color-filled rectangle @@ -2254,20 +2259,20 @@ Function 225: DrawRectangle() (5 input parameters) Param[3]: width (type: int) Param[4]: height (type: int) Param[5]: color (type: Color) -Function 226: DrawRectangleV() (3 input parameters) +Function 227: DrawRectangleV() (3 input parameters) Name: DrawRectangleV Return type: void Description: Draw a color-filled rectangle (Vector version) Param[1]: position (type: Vector2) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 227: DrawRectangleRec() (2 input parameters) +Function 228: DrawRectangleRec() (2 input parameters) Name: DrawRectangleRec Return type: void Description: Draw a color-filled rectangle Param[1]: rec (type: Rectangle) Param[2]: color (type: Color) -Function 228: DrawRectanglePro() (4 input parameters) +Function 229: DrawRectanglePro() (4 input parameters) Name: DrawRectanglePro Return type: void Description: Draw a color-filled rectangle with pro parameters @@ -2275,7 +2280,7 @@ Function 228: DrawRectanglePro() (4 input parameters) Param[2]: origin (type: Vector2) Param[3]: rotation (type: float) Param[4]: color (type: Color) -Function 229: DrawRectangleGradientV() (6 input parameters) +Function 230: DrawRectangleGradientV() (6 input parameters) Name: DrawRectangleGradientV Return type: void Description: Draw a vertical-gradient-filled rectangle @@ -2285,7 +2290,7 @@ Function 229: DrawRectangleGradientV() (6 input parameters) Param[4]: height (type: int) Param[5]: top (type: Color) Param[6]: bottom (type: Color) -Function 230: DrawRectangleGradientH() (6 input parameters) +Function 231: DrawRectangleGradientH() (6 input parameters) Name: DrawRectangleGradientH Return type: void Description: Draw a horizontal-gradient-filled rectangle @@ -2295,7 +2300,7 @@ Function 230: DrawRectangleGradientH() (6 input parameters) Param[4]: height (type: int) Param[5]: left (type: Color) Param[6]: right (type: Color) -Function 231: DrawRectangleGradientEx() (5 input parameters) +Function 232: DrawRectangleGradientEx() (5 input parameters) Name: DrawRectangleGradientEx Return type: void Description: Draw a gradient-filled rectangle with custom vertex colors @@ -2304,7 +2309,7 @@ Function 231: DrawRectangleGradientEx() (5 input parameters) Param[3]: bottomLeft (type: Color) Param[4]: topRight (type: Color) Param[5]: bottomRight (type: Color) -Function 232: DrawRectangleLines() (5 input parameters) +Function 233: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void Description: Draw rectangle outline @@ -2313,14 +2318,14 @@ Function 232: DrawRectangleLines() (5 input parameters) Param[3]: width (type: int) Param[4]: height (type: int) Param[5]: color (type: Color) -Function 233: DrawRectangleLinesEx() (3 input parameters) +Function 234: DrawRectangleLinesEx() (3 input parameters) Name: DrawRectangleLinesEx Return type: void Description: Draw rectangle outline with extended parameters Param[1]: rec (type: Rectangle) Param[2]: lineThick (type: float) Param[3]: color (type: Color) -Function 234: DrawRectangleRounded() (4 input parameters) +Function 235: DrawRectangleRounded() (4 input parameters) Name: DrawRectangleRounded Return type: void Description: Draw rectangle with rounded edges @@ -2328,7 +2333,7 @@ Function 234: DrawRectangleRounded() (4 input parameters) Param[2]: roundness (type: float) Param[3]: segments (type: int) Param[4]: color (type: Color) -Function 235: DrawRectangleRoundedLines() (4 input parameters) +Function 236: DrawRectangleRoundedLines() (4 input parameters) Name: DrawRectangleRoundedLines Return type: void Description: Draw rectangle lines with rounded edges @@ -2336,7 +2341,7 @@ Function 235: DrawRectangleRoundedLines() (4 input parameters) Param[2]: roundness (type: float) Param[3]: segments (type: int) Param[4]: color (type: Color) -Function 236: DrawRectangleRoundedLinesEx() (5 input parameters) +Function 237: DrawRectangleRoundedLinesEx() (5 input parameters) Name: DrawRectangleRoundedLinesEx Return type: void Description: Draw rectangle with rounded edges outline @@ -2345,7 +2350,7 @@ Function 236: DrawRectangleRoundedLinesEx() (5 input parameters) Param[3]: segments (type: int) Param[4]: lineThick (type: float) Param[5]: color (type: Color) -Function 237: DrawTriangle() (4 input parameters) +Function 238: DrawTriangle() (4 input parameters) Name: DrawTriangle Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -2353,7 +2358,7 @@ Function 237: DrawTriangle() (4 input parameters) Param[2]: v2 (type: Vector2) Param[3]: v3 (type: Vector2) Param[4]: color (type: Color) -Function 238: DrawTriangleLines() (4 input parameters) +Function 239: DrawTriangleLines() (4 input parameters) Name: DrawTriangleLines Return type: void Description: Draw triangle outline (vertex in counter-clockwise order!) @@ -2361,21 +2366,21 @@ Function 238: DrawTriangleLines() (4 input parameters) Param[2]: v2 (type: Vector2) Param[3]: v3 (type: Vector2) Param[4]: color (type: Color) -Function 239: DrawTriangleFan() (3 input parameters) +Function 240: DrawTriangleFan() (3 input parameters) Name: DrawTriangleFan Return type: void Description: Draw a triangle fan defined by points (first vertex is the center) Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 240: DrawTriangleStrip() (3 input parameters) +Function 241: DrawTriangleStrip() (3 input parameters) Name: DrawTriangleStrip Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 241: DrawPoly() (5 input parameters) +Function 242: DrawPoly() (5 input parameters) Name: DrawPoly Return type: void Description: Draw a regular polygon (Vector version) @@ -2384,7 +2389,7 @@ Function 241: DrawPoly() (5 input parameters) Param[3]: radius (type: float) Param[4]: rotation (type: float) Param[5]: color (type: Color) -Function 242: DrawPolyLines() (5 input parameters) +Function 243: DrawPolyLines() (5 input parameters) Name: DrawPolyLines Return type: void Description: Draw a polygon outline of n sides @@ -2393,7 +2398,7 @@ Function 242: DrawPolyLines() (5 input parameters) Param[3]: radius (type: float) Param[4]: rotation (type: float) Param[5]: color (type: Color) -Function 243: DrawPolyLinesEx() (6 input parameters) +Function 244: DrawPolyLinesEx() (6 input parameters) Name: DrawPolyLinesEx Return type: void Description: Draw a polygon outline of n sides with extended parameters @@ -2403,7 +2408,7 @@ Function 243: DrawPolyLinesEx() (6 input parameters) Param[4]: rotation (type: float) Param[5]: lineThick (type: float) Param[6]: color (type: Color) -Function 244: DrawSplineLinear() (4 input parameters) +Function 245: DrawSplineLinear() (4 input parameters) Name: DrawSplineLinear Return type: void Description: Draw spline: Linear, minimum 2 points @@ -2411,7 +2416,7 @@ Function 244: DrawSplineLinear() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 245: DrawSplineBasis() (4 input parameters) +Function 246: DrawSplineBasis() (4 input parameters) Name: DrawSplineBasis Return type: void Description: Draw spline: B-Spline, minimum 4 points @@ -2419,7 +2424,7 @@ Function 245: DrawSplineBasis() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 246: DrawSplineCatmullRom() (4 input parameters) +Function 247: DrawSplineCatmullRom() (4 input parameters) Name: DrawSplineCatmullRom Return type: void Description: Draw spline: Catmull-Rom, minimum 4 points @@ -2427,7 +2432,7 @@ Function 246: DrawSplineCatmullRom() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 247: DrawSplineBezierQuadratic() (4 input parameters) +Function 248: DrawSplineBezierQuadratic() (4 input parameters) Name: DrawSplineBezierQuadratic Return type: void Description: Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] @@ -2435,7 +2440,7 @@ Function 247: DrawSplineBezierQuadratic() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 248: DrawSplineBezierCubic() (4 input parameters) +Function 249: DrawSplineBezierCubic() (4 input parameters) Name: DrawSplineBezierCubic Return type: void Description: Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] @@ -2443,7 +2448,7 @@ Function 248: DrawSplineBezierCubic() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 249: DrawSplineSegmentLinear() (4 input parameters) +Function 250: DrawSplineSegmentLinear() (4 input parameters) Name: DrawSplineSegmentLinear Return type: void Description: Draw spline segment: Linear, 2 points @@ -2451,7 +2456,7 @@ Function 249: DrawSplineSegmentLinear() (4 input parameters) Param[2]: p2 (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 250: DrawSplineSegmentBasis() (6 input parameters) +Function 251: DrawSplineSegmentBasis() (6 input parameters) Name: DrawSplineSegmentBasis Return type: void Description: Draw spline segment: B-Spline, 4 points @@ -2461,7 +2466,7 @@ Function 250: DrawSplineSegmentBasis() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 251: DrawSplineSegmentCatmullRom() (6 input parameters) +Function 252: DrawSplineSegmentCatmullRom() (6 input parameters) Name: DrawSplineSegmentCatmullRom Return type: void Description: Draw spline segment: Catmull-Rom, 4 points @@ -2471,7 +2476,7 @@ Function 251: DrawSplineSegmentCatmullRom() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 252: DrawSplineSegmentBezierQuadratic() (5 input parameters) +Function 253: DrawSplineSegmentBezierQuadratic() (5 input parameters) Name: DrawSplineSegmentBezierQuadratic Return type: void Description: Draw spline segment: Quadratic Bezier, 2 points, 1 control point @@ -2480,7 +2485,7 @@ Function 252: DrawSplineSegmentBezierQuadratic() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: thick (type: float) Param[5]: color (type: Color) -Function 253: DrawSplineSegmentBezierCubic() (6 input parameters) +Function 254: DrawSplineSegmentBezierCubic() (6 input parameters) Name: DrawSplineSegmentBezierCubic Return type: void Description: Draw spline segment: Cubic Bezier, 2 points, 2 control points @@ -2490,14 +2495,14 @@ Function 253: DrawSplineSegmentBezierCubic() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 254: GetSplinePointLinear() (3 input parameters) +Function 255: GetSplinePointLinear() (3 input parameters) Name: GetSplinePointLinear Return type: Vector2 Description: Get (evaluate) spline point: Linear Param[1]: startPos (type: Vector2) Param[2]: endPos (type: Vector2) Param[3]: t (type: float) -Function 255: GetSplinePointBasis() (5 input parameters) +Function 256: GetSplinePointBasis() (5 input parameters) Name: GetSplinePointBasis Return type: Vector2 Description: Get (evaluate) spline point: B-Spline @@ -2506,7 +2511,7 @@ Function 255: GetSplinePointBasis() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 256: GetSplinePointCatmullRom() (5 input parameters) +Function 257: GetSplinePointCatmullRom() (5 input parameters) Name: GetSplinePointCatmullRom Return type: Vector2 Description: Get (evaluate) spline point: Catmull-Rom @@ -2515,7 +2520,7 @@ Function 256: GetSplinePointCatmullRom() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 257: GetSplinePointBezierQuad() (4 input parameters) +Function 258: GetSplinePointBezierQuad() (4 input parameters) Name: GetSplinePointBezierQuad Return type: Vector2 Description: Get (evaluate) spline point: Quadratic Bezier @@ -2523,7 +2528,7 @@ Function 257: GetSplinePointBezierQuad() (4 input parameters) Param[2]: c2 (type: Vector2) Param[3]: p3 (type: Vector2) Param[4]: t (type: float) -Function 258: GetSplinePointBezierCubic() (5 input parameters) +Function 259: GetSplinePointBezierCubic() (5 input parameters) Name: GetSplinePointBezierCubic Return type: Vector2 Description: Get (evaluate) spline point: Cubic Bezier @@ -2532,13 +2537,13 @@ Function 258: GetSplinePointBezierCubic() (5 input parameters) Param[3]: c3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 259: CheckCollisionRecs() (2 input parameters) +Function 260: CheckCollisionRecs() (2 input parameters) Name: CheckCollisionRecs Return type: bool Description: Check collision between two rectangles Param[1]: rec1 (type: Rectangle) Param[2]: rec2 (type: Rectangle) -Function 260: CheckCollisionCircles() (4 input parameters) +Function 261: CheckCollisionCircles() (4 input parameters) Name: CheckCollisionCircles Return type: bool Description: Check collision between two circles @@ -2546,27 +2551,27 @@ Function 260: CheckCollisionCircles() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector2) Param[4]: radius2 (type: float) -Function 261: CheckCollisionCircleRec() (3 input parameters) +Function 262: CheckCollisionCircleRec() (3 input parameters) Name: CheckCollisionCircleRec Return type: bool Description: Check collision between circle and rectangle Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: rec (type: Rectangle) -Function 262: CheckCollisionPointRec() (2 input parameters) +Function 263: CheckCollisionPointRec() (2 input parameters) Name: CheckCollisionPointRec Return type: bool Description: Check if point is inside rectangle Param[1]: point (type: Vector2) Param[2]: rec (type: Rectangle) -Function 263: CheckCollisionPointCircle() (3 input parameters) +Function 264: CheckCollisionPointCircle() (3 input parameters) Name: CheckCollisionPointCircle Return type: bool Description: Check if point is inside circle Param[1]: point (type: Vector2) Param[2]: center (type: Vector2) Param[3]: radius (type: float) -Function 264: CheckCollisionPointTriangle() (4 input parameters) +Function 265: CheckCollisionPointTriangle() (4 input parameters) Name: CheckCollisionPointTriangle Return type: bool Description: Check if point is inside a triangle @@ -2574,14 +2579,14 @@ Function 264: CheckCollisionPointTriangle() (4 input parameters) Param[2]: p1 (type: Vector2) Param[3]: p2 (type: Vector2) Param[4]: p3 (type: Vector2) -Function 265: CheckCollisionPointPoly() (3 input parameters) +Function 266: CheckCollisionPointPoly() (3 input parameters) Name: CheckCollisionPointPoly Return type: bool Description: Check if point is within a polygon described by array of vertices Param[1]: point (type: Vector2) Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) -Function 266: CheckCollisionLines() (5 input parameters) +Function 267: CheckCollisionLines() (5 input parameters) Name: CheckCollisionLines Return type: bool Description: Check the collision between two lines defined by two points each, returns collision point by reference @@ -2590,7 +2595,7 @@ Function 266: CheckCollisionLines() (5 input parameters) Param[3]: startPos2 (type: Vector2) Param[4]: endPos2 (type: Vector2) Param[5]: collisionPoint (type: Vector2 *) -Function 267: CheckCollisionPointLine() (4 input parameters) +Function 268: CheckCollisionPointLine() (4 input parameters) Name: CheckCollisionPointLine Return type: bool Description: Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] @@ -2598,7 +2603,7 @@ Function 267: CheckCollisionPointLine() (4 input parameters) Param[2]: p1 (type: Vector2) Param[3]: p2 (type: Vector2) Param[4]: threshold (type: int) -Function 268: CheckCollisionCircleLine() (4 input parameters) +Function 269: CheckCollisionCircleLine() (4 input parameters) Name: CheckCollisionCircleLine Return type: bool Description: Check if circle collides with a line created betweeen two points [p1] and [p2] @@ -2606,18 +2611,18 @@ Function 268: CheckCollisionCircleLine() (4 input parameters) Param[2]: radius (type: float) Param[3]: p1 (type: Vector2) Param[4]: p2 (type: Vector2) -Function 269: GetCollisionRec() (2 input parameters) +Function 270: GetCollisionRec() (2 input parameters) Name: GetCollisionRec Return type: Rectangle Description: Get collision rectangle for two rectangles collision Param[1]: rec1 (type: Rectangle) Param[2]: rec2 (type: Rectangle) -Function 270: LoadImage() (1 input parameters) +Function 271: LoadImage() (1 input parameters) Name: LoadImage Return type: Image Description: Load image from file into CPU memory (RAM) Param[1]: fileName (type: const char *) -Function 271: LoadImageRaw() (5 input parameters) +Function 272: LoadImageRaw() (5 input parameters) Name: LoadImageRaw Return type: Image Description: Load image from RAW file data @@ -2626,20 +2631,20 @@ Function 271: LoadImageRaw() (5 input parameters) Param[3]: height (type: int) Param[4]: format (type: int) Param[5]: headerSize (type: int) -Function 272: LoadImageSvg() (3 input parameters) +Function 273: LoadImageSvg() (3 input parameters) Name: LoadImageSvg Return type: Image Description: Load image from SVG file data or string with specified size Param[1]: fileNameOrString (type: const char *) Param[2]: width (type: int) Param[3]: height (type: int) -Function 273: LoadImageAnim() (2 input parameters) +Function 274: LoadImageAnim() (2 input parameters) Name: LoadImageAnim Return type: Image Description: Load image sequence from file (frames appended to image.data) Param[1]: fileName (type: const char *) Param[2]: frames (type: int *) -Function 274: LoadImageAnimFromMemory() (4 input parameters) +Function 275: LoadImageAnimFromMemory() (4 input parameters) Name: LoadImageAnimFromMemory Return type: Image Description: Load image sequence from memory buffer @@ -2647,60 +2652,60 @@ Function 274: LoadImageAnimFromMemory() (4 input parameters) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) Param[4]: frames (type: int *) -Function 275: LoadImageFromMemory() (3 input parameters) +Function 276: LoadImageFromMemory() (3 input parameters) Name: LoadImageFromMemory Return type: Image Description: Load image from memory buffer, fileType refers to extension: i.e. '.png' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 276: LoadImageFromTexture() (1 input parameters) +Function 277: LoadImageFromTexture() (1 input parameters) Name: LoadImageFromTexture Return type: Image Description: Load image from GPU texture data Param[1]: texture (type: Texture2D) -Function 277: LoadImageFromScreen() (0 input parameters) +Function 278: LoadImageFromScreen() (0 input parameters) Name: LoadImageFromScreen Return type: Image Description: Load image from screen buffer and (screenshot) No input parameters -Function 278: IsImageReady() (1 input parameters) +Function 279: IsImageReady() (1 input parameters) Name: IsImageReady Return type: bool Description: Check if an image is ready Param[1]: image (type: Image) -Function 279: UnloadImage() (1 input parameters) +Function 280: UnloadImage() (1 input parameters) Name: UnloadImage Return type: void Description: Unload image from CPU memory (RAM) Param[1]: image (type: Image) -Function 280: ExportImage() (2 input parameters) +Function 281: ExportImage() (2 input parameters) Name: ExportImage Return type: bool Description: Export image data to file, returns true on success Param[1]: image (type: Image) Param[2]: fileName (type: const char *) -Function 281: ExportImageToMemory() (3 input parameters) +Function 282: ExportImageToMemory() (3 input parameters) Name: ExportImageToMemory Return type: unsigned char * Description: Export image to memory buffer Param[1]: image (type: Image) Param[2]: fileType (type: const char *) Param[3]: fileSize (type: int *) -Function 282: ExportImageAsCode() (2 input parameters) +Function 283: ExportImageAsCode() (2 input parameters) Name: ExportImageAsCode Return type: bool Description: Export image as code file defining an array of bytes, returns true on success Param[1]: image (type: Image) Param[2]: fileName (type: const char *) -Function 283: GenImageColor() (3 input parameters) +Function 284: GenImageColor() (3 input parameters) Name: GenImageColor Return type: Image Description: Generate image: plain color Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: color (type: Color) -Function 284: GenImageGradientLinear() (5 input parameters) +Function 285: GenImageGradientLinear() (5 input parameters) Name: GenImageGradientLinear Return type: Image Description: Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient @@ -2709,7 +2714,7 @@ Function 284: GenImageGradientLinear() (5 input parameters) Param[3]: direction (type: int) Param[4]: start (type: Color) Param[5]: end (type: Color) -Function 285: GenImageGradientRadial() (5 input parameters) +Function 286: GenImageGradientRadial() (5 input parameters) Name: GenImageGradientRadial Return type: Image Description: Generate image: radial gradient @@ -2718,7 +2723,7 @@ Function 285: GenImageGradientRadial() (5 input parameters) Param[3]: density (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 286: GenImageGradientSquare() (5 input parameters) +Function 287: GenImageGradientSquare() (5 input parameters) Name: GenImageGradientSquare Return type: Image Description: Generate image: square gradient @@ -2727,7 +2732,7 @@ Function 286: GenImageGradientSquare() (5 input parameters) Param[3]: density (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 287: GenImageChecked() (6 input parameters) +Function 288: GenImageChecked() (6 input parameters) Name: GenImageChecked Return type: Image Description: Generate image: checked @@ -2737,14 +2742,14 @@ Function 287: GenImageChecked() (6 input parameters) Param[4]: checksY (type: int) Param[5]: col1 (type: Color) Param[6]: col2 (type: Color) -Function 288: GenImageWhiteNoise() (3 input parameters) +Function 289: GenImageWhiteNoise() (3 input parameters) Name: GenImageWhiteNoise Return type: Image Description: Generate image: white noise Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: factor (type: float) -Function 289: GenImagePerlinNoise() (5 input parameters) +Function 290: GenImagePerlinNoise() (5 input parameters) Name: GenImagePerlinNoise Return type: Image Description: Generate image: perlin noise @@ -2753,45 +2758,45 @@ Function 289: GenImagePerlinNoise() (5 input parameters) Param[3]: offsetX (type: int) Param[4]: offsetY (type: int) Param[5]: scale (type: float) -Function 290: GenImageCellular() (3 input parameters) +Function 291: GenImageCellular() (3 input parameters) Name: GenImageCellular Return type: Image Description: Generate image: cellular algorithm, bigger tileSize means bigger cells Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: tileSize (type: int) -Function 291: GenImageText() (3 input parameters) +Function 292: GenImageText() (3 input parameters) Name: GenImageText Return type: Image Description: Generate image: grayscale image from text data Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: text (type: const char *) -Function 292: ImageCopy() (1 input parameters) +Function 293: ImageCopy() (1 input parameters) Name: ImageCopy Return type: Image Description: Create an image duplicate (useful for transformations) Param[1]: image (type: Image) -Function 293: ImageFromImage() (2 input parameters) +Function 294: ImageFromImage() (2 input parameters) Name: ImageFromImage Return type: Image Description: Create an image from another image piece Param[1]: image (type: Image) Param[2]: rec (type: Rectangle) -Function 294: ImageFromChannel() (2 input parameters) +Function 295: ImageFromChannel() (2 input parameters) Name: ImageFromChannel Return type: Image Description: Create an image from a selected channel of another image (GRAYSCALE) Param[1]: image (type: Image) Param[2]: selectedChannel (type: int) -Function 295: ImageText() (3 input parameters) +Function 296: ImageText() (3 input parameters) Name: ImageText Return type: Image Description: Create an image from text (default font) Param[1]: text (type: const char *) Param[2]: fontSize (type: int) Param[3]: color (type: Color) -Function 296: ImageTextEx() (5 input parameters) +Function 297: ImageTextEx() (5 input parameters) Name: ImageTextEx Return type: Image Description: Create an image from text (custom sprite font) @@ -2800,76 +2805,76 @@ Function 296: ImageTextEx() (5 input parameters) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) Param[5]: tint (type: Color) -Function 297: ImageFormat() (2 input parameters) +Function 298: ImageFormat() (2 input parameters) Name: ImageFormat Return type: void Description: Convert image data to desired format Param[1]: image (type: Image *) Param[2]: newFormat (type: int) -Function 298: ImageToPOT() (2 input parameters) +Function 299: ImageToPOT() (2 input parameters) Name: ImageToPOT Return type: void Description: Convert image to POT (power-of-two) Param[1]: image (type: Image *) Param[2]: fill (type: Color) -Function 299: ImageCrop() (2 input parameters) +Function 300: ImageCrop() (2 input parameters) Name: ImageCrop Return type: void Description: Crop an image to a defined rectangle Param[1]: image (type: Image *) Param[2]: crop (type: Rectangle) -Function 300: ImageAlphaCrop() (2 input parameters) +Function 301: ImageAlphaCrop() (2 input parameters) Name: ImageAlphaCrop Return type: void Description: Crop image depending on alpha value Param[1]: image (type: Image *) Param[2]: threshold (type: float) -Function 301: ImageAlphaClear() (3 input parameters) +Function 302: ImageAlphaClear() (3 input parameters) Name: ImageAlphaClear Return type: void Description: Clear alpha channel to desired color Param[1]: image (type: Image *) Param[2]: color (type: Color) Param[3]: threshold (type: float) -Function 302: ImageAlphaMask() (2 input parameters) +Function 303: ImageAlphaMask() (2 input parameters) Name: ImageAlphaMask Return type: void Description: Apply alpha mask to image Param[1]: image (type: Image *) Param[2]: alphaMask (type: Image) -Function 303: ImageAlphaPremultiply() (1 input parameters) +Function 304: ImageAlphaPremultiply() (1 input parameters) Name: ImageAlphaPremultiply Return type: void Description: Premultiply alpha channel Param[1]: image (type: Image *) -Function 304: ImageBlurGaussian() (2 input parameters) +Function 305: ImageBlurGaussian() (2 input parameters) Name: ImageBlurGaussian Return type: void Description: Apply Gaussian blur using a box blur approximation Param[1]: image (type: Image *) Param[2]: blurSize (type: int) -Function 305: ImageKernelConvolution() (3 input parameters) +Function 306: ImageKernelConvolution() (3 input parameters) Name: ImageKernelConvolution Return type: void Description: Apply custom square convolution kernel to image Param[1]: image (type: Image *) Param[2]: kernel (type: const float *) Param[3]: kernelSize (type: int) -Function 306: ImageResize() (3 input parameters) +Function 307: ImageResize() (3 input parameters) Name: ImageResize Return type: void Description: Resize image (Bicubic scaling algorithm) Param[1]: image (type: Image *) Param[2]: newWidth (type: int) Param[3]: newHeight (type: int) -Function 307: ImageResizeNN() (3 input parameters) +Function 308: ImageResizeNN() (3 input parameters) Name: ImageResizeNN Return type: void Description: Resize image (Nearest-Neighbor scaling algorithm) Param[1]: image (type: Image *) Param[2]: newWidth (type: int) Param[3]: newHeight (type: int) -Function 308: ImageResizeCanvas() (6 input parameters) +Function 309: ImageResizeCanvas() (6 input parameters) Name: ImageResizeCanvas Return type: void Description: Resize canvas and fill with color @@ -2879,12 +2884,12 @@ Function 308: ImageResizeCanvas() (6 input parameters) Param[4]: offsetX (type: int) Param[5]: offsetY (type: int) Param[6]: fill (type: Color) -Function 309: ImageMipmaps() (1 input parameters) +Function 310: ImageMipmaps() (1 input parameters) Name: ImageMipmaps Return type: void Description: Compute all mipmap levels for a provided image Param[1]: image (type: Image *) -Function 310: ImageDither() (5 input parameters) +Function 311: ImageDither() (5 input parameters) Name: ImageDither Return type: void Description: Dither image data to 16bpp or lower (Floyd-Steinberg dithering) @@ -2893,109 +2898,109 @@ Function 310: ImageDither() (5 input parameters) Param[3]: gBpp (type: int) Param[4]: bBpp (type: int) Param[5]: aBpp (type: int) -Function 311: ImageFlipVertical() (1 input parameters) +Function 312: ImageFlipVertical() (1 input parameters) Name: ImageFlipVertical Return type: void Description: Flip image vertically Param[1]: image (type: Image *) -Function 312: ImageFlipHorizontal() (1 input parameters) +Function 313: ImageFlipHorizontal() (1 input parameters) Name: ImageFlipHorizontal Return type: void Description: Flip image horizontally Param[1]: image (type: Image *) -Function 313: ImageRotate() (2 input parameters) +Function 314: ImageRotate() (2 input parameters) Name: ImageRotate Return type: void Description: Rotate image by input angle in degrees (-359 to 359) Param[1]: image (type: Image *) Param[2]: degrees (type: int) -Function 314: ImageRotateCW() (1 input parameters) +Function 315: ImageRotateCW() (1 input parameters) Name: ImageRotateCW Return type: void Description: Rotate image clockwise 90deg Param[1]: image (type: Image *) -Function 315: ImageRotateCCW() (1 input parameters) +Function 316: ImageRotateCCW() (1 input parameters) Name: ImageRotateCCW Return type: void Description: Rotate image counter-clockwise 90deg Param[1]: image (type: Image *) -Function 316: ImageColorTint() (2 input parameters) +Function 317: ImageColorTint() (2 input parameters) Name: ImageColorTint Return type: void Description: Modify image color: tint Param[1]: image (type: Image *) Param[2]: color (type: Color) -Function 317: ImageColorInvert() (1 input parameters) +Function 318: ImageColorInvert() (1 input parameters) Name: ImageColorInvert Return type: void Description: Modify image color: invert Param[1]: image (type: Image *) -Function 318: ImageColorGrayscale() (1 input parameters) +Function 319: ImageColorGrayscale() (1 input parameters) Name: ImageColorGrayscale Return type: void Description: Modify image color: grayscale Param[1]: image (type: Image *) -Function 319: ImageColorContrast() (2 input parameters) +Function 320: ImageColorContrast() (2 input parameters) Name: ImageColorContrast Return type: void Description: Modify image color: contrast (-100 to 100) Param[1]: image (type: Image *) Param[2]: contrast (type: float) -Function 320: ImageColorBrightness() (2 input parameters) +Function 321: ImageColorBrightness() (2 input parameters) Name: ImageColorBrightness Return type: void Description: Modify image color: brightness (-255 to 255) Param[1]: image (type: Image *) Param[2]: brightness (type: int) -Function 321: ImageColorReplace() (3 input parameters) +Function 322: ImageColorReplace() (3 input parameters) Name: ImageColorReplace Return type: void Description: Modify image color: replace color Param[1]: image (type: Image *) Param[2]: color (type: Color) Param[3]: replace (type: Color) -Function 322: LoadImageColors() (1 input parameters) +Function 323: LoadImageColors() (1 input parameters) Name: LoadImageColors Return type: Color * Description: Load color data from image as a Color array (RGBA - 32bit) Param[1]: image (type: Image) -Function 323: LoadImagePalette() (3 input parameters) +Function 324: LoadImagePalette() (3 input parameters) Name: LoadImagePalette Return type: Color * Description: Load colors palette from image as a Color array (RGBA - 32bit) Param[1]: image (type: Image) Param[2]: maxPaletteSize (type: int) Param[3]: colorCount (type: int *) -Function 324: UnloadImageColors() (1 input parameters) +Function 325: UnloadImageColors() (1 input parameters) Name: UnloadImageColors Return type: void Description: Unload color data loaded with LoadImageColors() Param[1]: colors (type: Color *) -Function 325: UnloadImagePalette() (1 input parameters) +Function 326: UnloadImagePalette() (1 input parameters) Name: UnloadImagePalette Return type: void Description: Unload colors palette loaded with LoadImagePalette() Param[1]: colors (type: Color *) -Function 326: GetImageAlphaBorder() (2 input parameters) +Function 327: GetImageAlphaBorder() (2 input parameters) Name: GetImageAlphaBorder Return type: Rectangle Description: Get image alpha border rectangle Param[1]: image (type: Image) Param[2]: threshold (type: float) -Function 327: GetImageColor() (3 input parameters) +Function 328: GetImageColor() (3 input parameters) Name: GetImageColor Return type: Color Description: Get image pixel color at (x, y) position Param[1]: image (type: Image) Param[2]: x (type: int) Param[3]: y (type: int) -Function 328: ImageClearBackground() (2 input parameters) +Function 329: ImageClearBackground() (2 input parameters) Name: ImageClearBackground Return type: void Description: Clear image background with given color Param[1]: dst (type: Image *) Param[2]: color (type: Color) -Function 329: ImageDrawPixel() (4 input parameters) +Function 330: ImageDrawPixel() (4 input parameters) Name: ImageDrawPixel Return type: void Description: Draw pixel within an image @@ -3003,14 +3008,14 @@ Function 329: ImageDrawPixel() (4 input parameters) Param[2]: posX (type: int) Param[3]: posY (type: int) Param[4]: color (type: Color) -Function 330: ImageDrawPixelV() (3 input parameters) +Function 331: ImageDrawPixelV() (3 input parameters) Name: ImageDrawPixelV Return type: void Description: Draw pixel within an image (Vector version) Param[1]: dst (type: Image *) Param[2]: position (type: Vector2) Param[3]: color (type: Color) -Function 331: ImageDrawLine() (6 input parameters) +Function 332: ImageDrawLine() (6 input parameters) Name: ImageDrawLine Return type: void Description: Draw line within an image @@ -3020,7 +3025,7 @@ Function 331: ImageDrawLine() (6 input parameters) Param[4]: endPosX (type: int) Param[5]: endPosY (type: int) Param[6]: color (type: Color) -Function 332: ImageDrawLineV() (4 input parameters) +Function 333: ImageDrawLineV() (4 input parameters) Name: ImageDrawLineV Return type: void Description: Draw line within an image (Vector version) @@ -3028,7 +3033,7 @@ Function 332: ImageDrawLineV() (4 input parameters) Param[2]: start (type: Vector2) Param[3]: end (type: Vector2) Param[4]: color (type: Color) -Function 333: ImageDrawLineEx() (5 input parameters) +Function 334: ImageDrawLineEx() (5 input parameters) Name: ImageDrawLineEx Return type: void Description: Draw a line defining thickness within an image @@ -3037,7 +3042,7 @@ Function 333: ImageDrawLineEx() (5 input parameters) Param[3]: end (type: Vector2) Param[4]: thick (type: int) Param[5]: color (type: Color) -Function 334: ImageDrawCircle() (5 input parameters) +Function 335: ImageDrawCircle() (5 input parameters) Name: ImageDrawCircle Return type: void Description: Draw a filled circle within an image @@ -3046,7 +3051,7 @@ Function 334: ImageDrawCircle() (5 input parameters) Param[3]: centerY (type: int) Param[4]: radius (type: int) Param[5]: color (type: Color) -Function 335: ImageDrawCircleV() (4 input parameters) +Function 336: ImageDrawCircleV() (4 input parameters) Name: ImageDrawCircleV Return type: void Description: Draw a filled circle within an image (Vector version) @@ -3054,7 +3059,7 @@ Function 335: ImageDrawCircleV() (4 input parameters) Param[2]: center (type: Vector2) Param[3]: radius (type: int) Param[4]: color (type: Color) -Function 336: ImageDrawCircleLines() (5 input parameters) +Function 337: ImageDrawCircleLines() (5 input parameters) Name: ImageDrawCircleLines Return type: void Description: Draw circle outline within an image @@ -3063,7 +3068,7 @@ Function 336: ImageDrawCircleLines() (5 input parameters) Param[3]: centerY (type: int) Param[4]: radius (type: int) Param[5]: color (type: Color) -Function 337: ImageDrawCircleLinesV() (4 input parameters) +Function 338: ImageDrawCircleLinesV() (4 input parameters) Name: ImageDrawCircleLinesV Return type: void Description: Draw circle outline within an image (Vector version) @@ -3071,7 +3076,7 @@ Function 337: ImageDrawCircleLinesV() (4 input parameters) Param[2]: center (type: Vector2) Param[3]: radius (type: int) Param[4]: color (type: Color) -Function 338: ImageDrawRectangle() (6 input parameters) +Function 339: ImageDrawRectangle() (6 input parameters) Name: ImageDrawRectangle Return type: void Description: Draw rectangle within an image @@ -3081,7 +3086,7 @@ Function 338: ImageDrawRectangle() (6 input parameters) Param[4]: width (type: int) Param[5]: height (type: int) Param[6]: color (type: Color) -Function 339: ImageDrawRectangleV() (4 input parameters) +Function 340: ImageDrawRectangleV() (4 input parameters) Name: ImageDrawRectangleV Return type: void Description: Draw rectangle within an image (Vector version) @@ -3089,14 +3094,14 @@ Function 339: ImageDrawRectangleV() (4 input parameters) Param[2]: position (type: Vector2) Param[3]: size (type: Vector2) Param[4]: color (type: Color) -Function 340: ImageDrawRectangleRec() (3 input parameters) +Function 341: ImageDrawRectangleRec() (3 input parameters) Name: ImageDrawRectangleRec Return type: void Description: Draw rectangle within an image Param[1]: dst (type: Image *) Param[2]: rec (type: Rectangle) Param[3]: color (type: Color) -Function 341: ImageDrawRectangleLines() (4 input parameters) +Function 342: ImageDrawRectangleLines() (4 input parameters) Name: ImageDrawRectangleLines Return type: void Description: Draw rectangle lines within an image @@ -3104,7 +3109,7 @@ Function 341: ImageDrawRectangleLines() (4 input parameters) Param[2]: rec (type: Rectangle) Param[3]: thick (type: int) Param[4]: color (type: Color) -Function 342: ImageDrawTriangle() (5 input parameters) +Function 343: ImageDrawTriangle() (5 input parameters) Name: ImageDrawTriangle Return type: void Description: Draw triangle within an image @@ -3113,7 +3118,7 @@ Function 342: ImageDrawTriangle() (5 input parameters) Param[3]: v2 (type: Vector2) Param[4]: v3 (type: Vector2) Param[5]: color (type: Color) -Function 343: ImageDrawTriangleEx() (7 input parameters) +Function 344: ImageDrawTriangleEx() (7 input parameters) Name: ImageDrawTriangleEx Return type: void Description: Draw triangle with interpolated colors within an image @@ -3124,7 +3129,7 @@ Function 343: ImageDrawTriangleEx() (7 input parameters) Param[5]: c1 (type: Color) Param[6]: c2 (type: Color) Param[7]: c3 (type: Color) -Function 344: ImageDrawTriangleLines() (5 input parameters) +Function 345: ImageDrawTriangleLines() (5 input parameters) Name: ImageDrawTriangleLines Return type: void Description: Draw triangle outline within an image @@ -3133,7 +3138,7 @@ Function 344: ImageDrawTriangleLines() (5 input parameters) Param[3]: v2 (type: Vector2) Param[4]: v3 (type: Vector2) Param[5]: color (type: Color) -Function 345: ImageDrawTriangleFan() (4 input parameters) +Function 346: ImageDrawTriangleFan() (4 input parameters) Name: ImageDrawTriangleFan Return type: void Description: Draw a triangle fan defined by points within an image (first vertex is the center) @@ -3141,7 +3146,7 @@ Function 345: ImageDrawTriangleFan() (4 input parameters) Param[2]: points (type: Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) -Function 346: ImageDrawTriangleStrip() (4 input parameters) +Function 347: ImageDrawTriangleStrip() (4 input parameters) Name: ImageDrawTriangleStrip Return type: void Description: Draw a triangle strip defined by points within an image @@ -3149,7 +3154,7 @@ Function 346: ImageDrawTriangleStrip() (4 input parameters) Param[2]: points (type: Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) -Function 347: ImageDraw() (5 input parameters) +Function 348: ImageDraw() (5 input parameters) Name: ImageDraw Return type: void Description: Draw a source image within a destination image (tint applied to source) @@ -3158,7 +3163,7 @@ Function 347: ImageDraw() (5 input parameters) Param[3]: srcRec (type: Rectangle) Param[4]: dstRec (type: Rectangle) Param[5]: tint (type: Color) -Function 348: ImageDrawText() (6 input parameters) +Function 349: ImageDrawText() (6 input parameters) Name: ImageDrawText Return type: void Description: Draw text (using default font) within an image (destination) @@ -3168,7 +3173,7 @@ Function 348: ImageDrawText() (6 input parameters) Param[4]: posY (type: int) Param[5]: fontSize (type: int) Param[6]: color (type: Color) -Function 349: ImageDrawTextEx() (7 input parameters) +Function 350: ImageDrawTextEx() (7 input parameters) Name: ImageDrawTextEx Return type: void Description: Draw text (custom sprite font) within an image (destination) @@ -3179,79 +3184,79 @@ Function 349: ImageDrawTextEx() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 350: LoadTexture() (1 input parameters) +Function 351: LoadTexture() (1 input parameters) Name: LoadTexture Return type: Texture2D Description: Load texture from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 351: LoadTextureFromImage() (1 input parameters) +Function 352: LoadTextureFromImage() (1 input parameters) Name: LoadTextureFromImage Return type: Texture2D Description: Load texture from image data Param[1]: image (type: Image) -Function 352: LoadTextureCubemap() (2 input parameters) +Function 353: LoadTextureCubemap() (2 input parameters) Name: LoadTextureCubemap Return type: TextureCubemap Description: Load cubemap from image, multiple image cubemap layouts supported Param[1]: image (type: Image) Param[2]: layout (type: int) -Function 353: LoadRenderTexture() (2 input parameters) +Function 354: LoadRenderTexture() (2 input parameters) Name: LoadRenderTexture Return type: RenderTexture2D Description: Load texture for rendering (framebuffer) Param[1]: width (type: int) Param[2]: height (type: int) -Function 354: IsTextureReady() (1 input parameters) +Function 355: IsTextureReady() (1 input parameters) Name: IsTextureReady Return type: bool Description: Check if a texture is ready Param[1]: texture (type: Texture2D) -Function 355: UnloadTexture() (1 input parameters) +Function 356: UnloadTexture() (1 input parameters) Name: UnloadTexture Return type: void Description: Unload texture from GPU memory (VRAM) Param[1]: texture (type: Texture2D) -Function 356: IsRenderTextureReady() (1 input parameters) +Function 357: IsRenderTextureReady() (1 input parameters) Name: IsRenderTextureReady Return type: bool Description: Check if a render texture is ready Param[1]: target (type: RenderTexture2D) -Function 357: UnloadRenderTexture() (1 input parameters) +Function 358: UnloadRenderTexture() (1 input parameters) Name: UnloadRenderTexture Return type: void Description: Unload render texture from GPU memory (VRAM) Param[1]: target (type: RenderTexture2D) -Function 358: UpdateTexture() (2 input parameters) +Function 359: UpdateTexture() (2 input parameters) Name: UpdateTexture Return type: void Description: Update GPU texture with new data Param[1]: texture (type: Texture2D) Param[2]: pixels (type: const void *) -Function 359: UpdateTextureRec() (3 input parameters) +Function 360: UpdateTextureRec() (3 input parameters) Name: UpdateTextureRec Return type: void Description: Update GPU texture rectangle with new data Param[1]: texture (type: Texture2D) Param[2]: rec (type: Rectangle) Param[3]: pixels (type: const void *) -Function 360: GenTextureMipmaps() (1 input parameters) +Function 361: GenTextureMipmaps() (1 input parameters) Name: GenTextureMipmaps Return type: void Description: Generate GPU mipmaps for a texture Param[1]: texture (type: Texture2D *) -Function 361: SetTextureFilter() (2 input parameters) +Function 362: SetTextureFilter() (2 input parameters) Name: SetTextureFilter Return type: void Description: Set texture scaling filter mode Param[1]: texture (type: Texture2D) Param[2]: filter (type: int) -Function 362: SetTextureWrap() (2 input parameters) +Function 363: SetTextureWrap() (2 input parameters) Name: SetTextureWrap Return type: void Description: Set texture wrapping mode Param[1]: texture (type: Texture2D) Param[2]: wrap (type: int) -Function 363: DrawTexture() (4 input parameters) +Function 364: DrawTexture() (4 input parameters) Name: DrawTexture Return type: void Description: Draw a Texture2D @@ -3259,14 +3264,14 @@ Function 363: DrawTexture() (4 input parameters) Param[2]: posX (type: int) Param[3]: posY (type: int) Param[4]: tint (type: Color) -Function 364: DrawTextureV() (3 input parameters) +Function 365: DrawTextureV() (3 input parameters) Name: DrawTextureV Return type: void Description: Draw a Texture2D with position defined as Vector2 Param[1]: texture (type: Texture2D) Param[2]: position (type: Vector2) Param[3]: tint (type: Color) -Function 365: DrawTextureEx() (5 input parameters) +Function 366: DrawTextureEx() (5 input parameters) Name: DrawTextureEx Return type: void Description: Draw a Texture2D with extended parameters @@ -3275,7 +3280,7 @@ Function 365: DrawTextureEx() (5 input parameters) Param[3]: rotation (type: float) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 366: DrawTextureRec() (4 input parameters) +Function 367: DrawTextureRec() (4 input parameters) Name: DrawTextureRec Return type: void Description: Draw a part of a texture defined by a rectangle @@ -3283,7 +3288,7 @@ Function 366: DrawTextureRec() (4 input parameters) Param[2]: source (type: Rectangle) Param[3]: position (type: Vector2) Param[4]: tint (type: Color) -Function 367: DrawTexturePro() (6 input parameters) +Function 368: DrawTexturePro() (6 input parameters) Name: DrawTexturePro Return type: void Description: Draw a part of a texture defined by a rectangle with 'pro' parameters @@ -3293,7 +3298,7 @@ Function 367: DrawTexturePro() (6 input parameters) Param[4]: origin (type: Vector2) Param[5]: rotation (type: float) Param[6]: tint (type: Color) -Function 368: DrawTextureNPatch() (6 input parameters) +Function 369: DrawTextureNPatch() (6 input parameters) Name: DrawTextureNPatch Return type: void Description: Draws a texture (or part of it) that stretches or shrinks nicely @@ -3303,119 +3308,119 @@ Function 368: DrawTextureNPatch() (6 input parameters) Param[4]: origin (type: Vector2) Param[5]: rotation (type: float) Param[6]: tint (type: Color) -Function 369: ColorIsEqual() (2 input parameters) +Function 370: ColorIsEqual() (2 input parameters) Name: ColorIsEqual Return type: bool Description: Check if two colors are equal Param[1]: col1 (type: Color) Param[2]: col2 (type: Color) -Function 370: Fade() (2 input parameters) +Function 371: Fade() (2 input parameters) Name: Fade Return type: Color Description: Get color with alpha applied, alpha goes from 0.0f to 1.0f Param[1]: color (type: Color) Param[2]: alpha (type: float) -Function 371: ColorToInt() (1 input parameters) +Function 372: ColorToInt() (1 input parameters) Name: ColorToInt Return type: int Description: Get hexadecimal value for a Color (0xRRGGBBAA) Param[1]: color (type: Color) -Function 372: ColorNormalize() (1 input parameters) +Function 373: ColorNormalize() (1 input parameters) Name: ColorNormalize Return type: Vector4 Description: Get Color normalized as float [0..1] Param[1]: color (type: Color) -Function 373: ColorFromNormalized() (1 input parameters) +Function 374: ColorFromNormalized() (1 input parameters) Name: ColorFromNormalized Return type: Color Description: Get Color from normalized values [0..1] Param[1]: normalized (type: Vector4) -Function 374: ColorToHSV() (1 input parameters) +Function 375: ColorToHSV() (1 input parameters) Name: ColorToHSV Return type: Vector3 Description: Get HSV values for a Color, hue [0..360], saturation/value [0..1] Param[1]: color (type: Color) -Function 375: ColorFromHSV() (3 input parameters) +Function 376: ColorFromHSV() (3 input parameters) Name: ColorFromHSV Return type: Color Description: Get a Color from HSV values, hue [0..360], saturation/value [0..1] Param[1]: hue (type: float) Param[2]: saturation (type: float) Param[3]: value (type: float) -Function 376: ColorTint() (2 input parameters) +Function 377: ColorTint() (2 input parameters) Name: ColorTint Return type: Color Description: Get color multiplied with another color Param[1]: color (type: Color) Param[2]: tint (type: Color) -Function 377: ColorBrightness() (2 input parameters) +Function 378: ColorBrightness() (2 input parameters) Name: ColorBrightness Return type: Color Description: Get color with brightness correction, brightness factor goes from -1.0f to 1.0f Param[1]: color (type: Color) Param[2]: factor (type: float) -Function 378: ColorContrast() (2 input parameters) +Function 379: ColorContrast() (2 input parameters) Name: ColorContrast Return type: Color Description: Get color with contrast correction, contrast values between -1.0f and 1.0f Param[1]: color (type: Color) Param[2]: contrast (type: float) -Function 379: ColorAlpha() (2 input parameters) +Function 380: ColorAlpha() (2 input parameters) Name: ColorAlpha Return type: Color Description: Get color with alpha applied, alpha goes from 0.0f to 1.0f Param[1]: color (type: Color) Param[2]: alpha (type: float) -Function 380: ColorAlphaBlend() (3 input parameters) +Function 381: ColorAlphaBlend() (3 input parameters) Name: ColorAlphaBlend Return type: Color Description: Get src alpha-blended into dst color with tint Param[1]: dst (type: Color) Param[2]: src (type: Color) Param[3]: tint (type: Color) -Function 381: ColorLerp() (3 input parameters) +Function 382: ColorLerp() (3 input parameters) Name: ColorLerp Return type: Color Description: Get color lerp interpolation between two colors, factor [0.0f..1.0f] Param[1]: color1 (type: Color) Param[2]: color2 (type: Color) Param[3]: factor (type: float) -Function 382: GetColor() (1 input parameters) +Function 383: GetColor() (1 input parameters) Name: GetColor Return type: Color Description: Get Color structure from hexadecimal value Param[1]: hexValue (type: unsigned int) -Function 383: GetPixelColor() (2 input parameters) +Function 384: GetPixelColor() (2 input parameters) Name: GetPixelColor Return type: Color Description: Get Color from a source pixel pointer of certain format Param[1]: srcPtr (type: void *) Param[2]: format (type: int) -Function 384: SetPixelColor() (3 input parameters) +Function 385: SetPixelColor() (3 input parameters) Name: SetPixelColor Return type: void Description: Set color formatted into destination pixel pointer Param[1]: dstPtr (type: void *) Param[2]: color (type: Color) Param[3]: format (type: int) -Function 385: GetPixelDataSize() (3 input parameters) +Function 386: GetPixelDataSize() (3 input parameters) Name: GetPixelDataSize Return type: int Description: Get pixel data size in bytes for certain format Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: format (type: int) -Function 386: GetFontDefault() (0 input parameters) +Function 387: GetFontDefault() (0 input parameters) Name: GetFontDefault Return type: Font Description: Get the default Font No input parameters -Function 387: LoadFont() (1 input parameters) +Function 388: LoadFont() (1 input parameters) Name: LoadFont Return type: Font Description: Load font from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 388: LoadFontEx() (4 input parameters) +Function 389: LoadFontEx() (4 input parameters) Name: LoadFontEx Return type: Font Description: Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height @@ -3423,14 +3428,14 @@ Function 388: LoadFontEx() (4 input parameters) Param[2]: fontSize (type: int) Param[3]: codepoints (type: int *) Param[4]: codepointCount (type: int) -Function 389: LoadFontFromImage() (3 input parameters) +Function 390: LoadFontFromImage() (3 input parameters) Name: LoadFontFromImage Return type: Font Description: Load font from Image (XNA style) Param[1]: image (type: Image) Param[2]: key (type: Color) Param[3]: firstChar (type: int) -Function 390: LoadFontFromMemory() (6 input parameters) +Function 391: LoadFontFromMemory() (6 input parameters) Name: LoadFontFromMemory Return type: Font Description: Load font from memory buffer, fileType refers to extension: i.e. '.ttf' @@ -3440,12 +3445,12 @@ Function 390: LoadFontFromMemory() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: codepoints (type: int *) Param[6]: codepointCount (type: int) -Function 391: IsFontReady() (1 input parameters) +Function 392: IsFontReady() (1 input parameters) Name: IsFontReady Return type: bool Description: Check if a font is ready Param[1]: font (type: Font) -Function 392: LoadFontData() (6 input parameters) +Function 393: LoadFontData() (6 input parameters) Name: LoadFontData Return type: GlyphInfo * Description: Load font data for further use @@ -3455,7 +3460,7 @@ Function 392: LoadFontData() (6 input parameters) Param[4]: codepoints (type: int *) Param[5]: codepointCount (type: int) Param[6]: type (type: int) -Function 393: GenImageFontAtlas() (6 input parameters) +Function 394: GenImageFontAtlas() (6 input parameters) Name: GenImageFontAtlas Return type: Image Description: Generate image font atlas using chars info @@ -3465,30 +3470,30 @@ Function 393: GenImageFontAtlas() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: padding (type: int) Param[6]: packMethod (type: int) -Function 394: UnloadFontData() (2 input parameters) +Function 395: UnloadFontData() (2 input parameters) Name: UnloadFontData Return type: void Description: Unload font chars info data (RAM) Param[1]: glyphs (type: GlyphInfo *) Param[2]: glyphCount (type: int) -Function 395: UnloadFont() (1 input parameters) +Function 396: UnloadFont() (1 input parameters) Name: UnloadFont Return type: void Description: Unload font from GPU memory (VRAM) Param[1]: font (type: Font) -Function 396: ExportFontAsCode() (2 input parameters) +Function 397: ExportFontAsCode() (2 input parameters) Name: ExportFontAsCode Return type: bool Description: Export font as code file, returns true on success Param[1]: font (type: Font) Param[2]: fileName (type: const char *) -Function 397: DrawFPS() (2 input parameters) +Function 398: DrawFPS() (2 input parameters) Name: DrawFPS Return type: void Description: Draw current FPS Param[1]: posX (type: int) Param[2]: posY (type: int) -Function 398: DrawText() (5 input parameters) +Function 399: DrawText() (5 input parameters) Name: DrawText Return type: void Description: Draw text (using default font) @@ -3497,7 +3502,7 @@ Function 398: DrawText() (5 input parameters) Param[3]: posY (type: int) Param[4]: fontSize (type: int) Param[5]: color (type: Color) -Function 399: DrawTextEx() (6 input parameters) +Function 400: DrawTextEx() (6 input parameters) Name: DrawTextEx Return type: void Description: Draw text using font and additional parameters @@ -3507,7 +3512,7 @@ Function 399: DrawTextEx() (6 input parameters) Param[4]: fontSize (type: float) Param[5]: spacing (type: float) Param[6]: tint (type: Color) -Function 400: DrawTextPro() (8 input parameters) +Function 401: DrawTextPro() (8 input parameters) Name: DrawTextPro Return type: void Description: Draw text using Font and pro parameters (rotation) @@ -3519,7 +3524,7 @@ Function 400: DrawTextPro() (8 input parameters) Param[6]: fontSize (type: float) Param[7]: spacing (type: float) Param[8]: tint (type: Color) -Function 401: DrawTextCodepoint() (5 input parameters) +Function 402: DrawTextCodepoint() (5 input parameters) Name: DrawTextCodepoint Return type: void Description: Draw one character (codepoint) @@ -3528,7 +3533,7 @@ Function 401: DrawTextCodepoint() (5 input parameters) Param[3]: position (type: Vector2) Param[4]: fontSize (type: float) Param[5]: tint (type: Color) -Function 402: DrawTextCodepoints() (7 input parameters) +Function 403: DrawTextCodepoints() (7 input parameters) Name: DrawTextCodepoints Return type: void Description: Draw multiple character (codepoint) @@ -3539,18 +3544,18 @@ Function 402: DrawTextCodepoints() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 403: SetTextLineSpacing() (1 input parameters) +Function 404: SetTextLineSpacing() (1 input parameters) Name: SetTextLineSpacing Return type: void Description: Set vertical line spacing when drawing with line-breaks Param[1]: spacing (type: int) -Function 404: MeasureText() (2 input parameters) +Function 405: MeasureText() (2 input parameters) Name: MeasureText Return type: int Description: Measure string width for default font Param[1]: text (type: const char *) Param[2]: fontSize (type: int) -Function 405: MeasureTextEx() (4 input parameters) +Function 406: MeasureTextEx() (4 input parameters) Name: MeasureTextEx Return type: Vector2 Description: Measure string size for Font @@ -3558,195 +3563,195 @@ Function 405: MeasureTextEx() (4 input parameters) Param[2]: text (type: const char *) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) -Function 406: GetGlyphIndex() (2 input parameters) +Function 407: GetGlyphIndex() (2 input parameters) Name: GetGlyphIndex Return type: int Description: Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 407: GetGlyphInfo() (2 input parameters) +Function 408: GetGlyphInfo() (2 input parameters) Name: GetGlyphInfo Return type: GlyphInfo Description: Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 408: GetGlyphAtlasRec() (2 input parameters) +Function 409: GetGlyphAtlasRec() (2 input parameters) Name: GetGlyphAtlasRec Return type: Rectangle Description: Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 409: LoadUTF8() (2 input parameters) +Function 410: LoadUTF8() (2 input parameters) Name: LoadUTF8 Return type: char * Description: Load UTF-8 text encoded from codepoints array Param[1]: codepoints (type: const int *) Param[2]: length (type: int) -Function 410: UnloadUTF8() (1 input parameters) +Function 411: UnloadUTF8() (1 input parameters) Name: UnloadUTF8 Return type: void Description: Unload UTF-8 text encoded from codepoints array Param[1]: text (type: char *) -Function 411: LoadCodepoints() (2 input parameters) +Function 412: LoadCodepoints() (2 input parameters) Name: LoadCodepoints Return type: int * Description: Load all codepoints from a UTF-8 text string, codepoints count returned by parameter Param[1]: text (type: const char *) Param[2]: count (type: int *) -Function 412: UnloadCodepoints() (1 input parameters) +Function 413: UnloadCodepoints() (1 input parameters) Name: UnloadCodepoints Return type: void Description: Unload codepoints data from memory Param[1]: codepoints (type: int *) -Function 413: GetCodepointCount() (1 input parameters) +Function 414: GetCodepointCount() (1 input parameters) Name: GetCodepointCount Return type: int Description: Get total number of codepoints in a UTF-8 encoded string Param[1]: text (type: const char *) -Function 414: GetCodepoint() (2 input parameters) +Function 415: GetCodepoint() (2 input parameters) Name: GetCodepoint Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 415: GetCodepointNext() (2 input parameters) +Function 416: GetCodepointNext() (2 input parameters) Name: GetCodepointNext Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 416: GetCodepointPrevious() (2 input parameters) +Function 417: GetCodepointPrevious() (2 input parameters) Name: GetCodepointPrevious Return type: int Description: Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 417: CodepointToUTF8() (2 input parameters) +Function 418: CodepointToUTF8() (2 input parameters) Name: CodepointToUTF8 Return type: const char * Description: Encode one codepoint into UTF-8 byte array (array length returned as parameter) Param[1]: codepoint (type: int) Param[2]: utf8Size (type: int *) -Function 418: TextCopy() (2 input parameters) +Function 419: TextCopy() (2 input parameters) Name: TextCopy Return type: int Description: Copy one string to another, returns bytes copied Param[1]: dst (type: char *) Param[2]: src (type: const char *) -Function 419: TextIsEqual() (2 input parameters) +Function 420: TextIsEqual() (2 input parameters) Name: TextIsEqual Return type: bool Description: Check if two text string are equal Param[1]: text1 (type: const char *) Param[2]: text2 (type: const char *) -Function 420: TextLength() (1 input parameters) +Function 421: TextLength() (1 input parameters) Name: TextLength Return type: unsigned int Description: Get text length, checks for '\0' ending Param[1]: text (type: const char *) -Function 421: TextFormat() (2 input parameters) +Function 422: TextFormat() (2 input parameters) Name: TextFormat Return type: const char * Description: Text formatting with variables (sprintf() style) Param[1]: text (type: const char *) Param[2]: args (type: ...) -Function 422: TextSubtext() (3 input parameters) +Function 423: TextSubtext() (3 input parameters) Name: TextSubtext Return type: const char * Description: Get a piece of a text string Param[1]: text (type: const char *) Param[2]: position (type: int) Param[3]: length (type: int) -Function 423: TextReplace() (3 input parameters) +Function 424: TextReplace() (3 input parameters) Name: TextReplace Return type: char * Description: Replace text string (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: replace (type: const char *) Param[3]: by (type: const char *) -Function 424: TextInsert() (3 input parameters) +Function 425: TextInsert() (3 input parameters) Name: TextInsert Return type: char * Description: Insert text in a position (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: insert (type: const char *) Param[3]: position (type: int) -Function 425: TextJoin() (3 input parameters) +Function 426: TextJoin() (3 input parameters) Name: TextJoin Return type: const char * Description: Join text strings with delimiter Param[1]: textList (type: const char **) Param[2]: count (type: int) Param[3]: delimiter (type: const char *) -Function 426: TextSplit() (3 input parameters) +Function 427: TextSplit() (3 input parameters) Name: TextSplit Return type: const char ** Description: Split text into multiple strings Param[1]: text (type: const char *) Param[2]: delimiter (type: char) Param[3]: count (type: int *) -Function 427: TextAppend() (3 input parameters) +Function 428: TextAppend() (3 input parameters) Name: TextAppend Return type: void Description: Append text at specific position and move cursor! Param[1]: text (type: char *) Param[2]: append (type: const char *) Param[3]: position (type: int *) -Function 428: TextFindIndex() (2 input parameters) +Function 429: TextFindIndex() (2 input parameters) Name: TextFindIndex Return type: int Description: Find first text occurrence within a string Param[1]: text (type: const char *) Param[2]: find (type: const char *) -Function 429: TextToUpper() (1 input parameters) +Function 430: TextToUpper() (1 input parameters) Name: TextToUpper Return type: const char * Description: Get upper case version of provided string Param[1]: text (type: const char *) -Function 430: TextToLower() (1 input parameters) +Function 431: TextToLower() (1 input parameters) Name: TextToLower Return type: const char * Description: Get lower case version of provided string Param[1]: text (type: const char *) -Function 431: TextToPascal() (1 input parameters) +Function 432: TextToPascal() (1 input parameters) Name: TextToPascal Return type: const char * Description: Get Pascal case notation version of provided string Param[1]: text (type: const char *) -Function 432: TextToSnake() (1 input parameters) +Function 433: TextToSnake() (1 input parameters) Name: TextToSnake Return type: const char * Description: Get Snake case notation version of provided string Param[1]: text (type: const char *) -Function 433: TextToCamel() (1 input parameters) +Function 434: TextToCamel() (1 input parameters) Name: TextToCamel Return type: const char * Description: Get Camel case notation version of provided string Param[1]: text (type: const char *) -Function 434: TextToInteger() (1 input parameters) +Function 435: TextToInteger() (1 input parameters) Name: TextToInteger Return type: int Description: Get integer value from text (negative values not supported) Param[1]: text (type: const char *) -Function 435: TextToFloat() (1 input parameters) +Function 436: TextToFloat() (1 input parameters) Name: TextToFloat Return type: float Description: Get float value from text (negative values not supported) Param[1]: text (type: const char *) -Function 436: DrawLine3D() (3 input parameters) +Function 437: DrawLine3D() (3 input parameters) Name: DrawLine3D Return type: void Description: Draw a line in 3D world space Param[1]: startPos (type: Vector3) Param[2]: endPos (type: Vector3) Param[3]: color (type: Color) -Function 437: DrawPoint3D() (2 input parameters) +Function 438: DrawPoint3D() (2 input parameters) Name: DrawPoint3D Return type: void Description: Draw a point in 3D space, actually a small line Param[1]: position (type: Vector3) Param[2]: color (type: Color) -Function 438: DrawCircle3D() (5 input parameters) +Function 439: DrawCircle3D() (5 input parameters) Name: DrawCircle3D Return type: void Description: Draw a circle in 3D world space @@ -3755,7 +3760,7 @@ Function 438: DrawCircle3D() (5 input parameters) Param[3]: rotationAxis (type: Vector3) Param[4]: rotationAngle (type: float) Param[5]: color (type: Color) -Function 439: DrawTriangle3D() (4 input parameters) +Function 440: DrawTriangle3D() (4 input parameters) Name: DrawTriangle3D Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -3763,14 +3768,14 @@ Function 439: DrawTriangle3D() (4 input parameters) Param[2]: v2 (type: Vector3) Param[3]: v3 (type: Vector3) Param[4]: color (type: Color) -Function 440: DrawTriangleStrip3D() (3 input parameters) +Function 441: DrawTriangleStrip3D() (3 input parameters) Name: DrawTriangleStrip3D Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector3 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 441: DrawCube() (5 input parameters) +Function 442: DrawCube() (5 input parameters) Name: DrawCube Return type: void Description: Draw cube @@ -3779,14 +3784,14 @@ Function 441: DrawCube() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 442: DrawCubeV() (3 input parameters) +Function 443: DrawCubeV() (3 input parameters) Name: DrawCubeV Return type: void Description: Draw cube (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 443: DrawCubeWires() (5 input parameters) +Function 444: DrawCubeWires() (5 input parameters) Name: DrawCubeWires Return type: void Description: Draw cube wires @@ -3795,21 +3800,21 @@ Function 443: DrawCubeWires() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 444: DrawCubeWiresV() (3 input parameters) +Function 445: DrawCubeWiresV() (3 input parameters) Name: DrawCubeWiresV Return type: void Description: Draw cube wires (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 445: DrawSphere() (3 input parameters) +Function 446: DrawSphere() (3 input parameters) Name: DrawSphere Return type: void Description: Draw sphere Param[1]: centerPos (type: Vector3) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 446: DrawSphereEx() (5 input parameters) +Function 447: DrawSphereEx() (5 input parameters) Name: DrawSphereEx Return type: void Description: Draw sphere with extended parameters @@ -3818,7 +3823,7 @@ Function 446: DrawSphereEx() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 447: DrawSphereWires() (5 input parameters) +Function 448: DrawSphereWires() (5 input parameters) Name: DrawSphereWires Return type: void Description: Draw sphere wires @@ -3827,7 +3832,7 @@ Function 447: DrawSphereWires() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 448: DrawCylinder() (6 input parameters) +Function 449: DrawCylinder() (6 input parameters) Name: DrawCylinder Return type: void Description: Draw a cylinder/cone @@ -3837,7 +3842,7 @@ Function 448: DrawCylinder() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 449: DrawCylinderEx() (6 input parameters) +Function 450: DrawCylinderEx() (6 input parameters) Name: DrawCylinderEx Return type: void Description: Draw a cylinder with base at startPos and top at endPos @@ -3847,7 +3852,7 @@ Function 449: DrawCylinderEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 450: DrawCylinderWires() (6 input parameters) +Function 451: DrawCylinderWires() (6 input parameters) Name: DrawCylinderWires Return type: void Description: Draw a cylinder/cone wires @@ -3857,7 +3862,7 @@ Function 450: DrawCylinderWires() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 451: DrawCylinderWiresEx() (6 input parameters) +Function 452: DrawCylinderWiresEx() (6 input parameters) Name: DrawCylinderWiresEx Return type: void Description: Draw a cylinder wires with base at startPos and top at endPos @@ -3867,7 +3872,7 @@ Function 451: DrawCylinderWiresEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 452: DrawCapsule() (6 input parameters) +Function 453: DrawCapsule() (6 input parameters) Name: DrawCapsule Return type: void Description: Draw a capsule with the center of its sphere caps at startPos and endPos @@ -3877,7 +3882,7 @@ Function 452: DrawCapsule() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 453: DrawCapsuleWires() (6 input parameters) +Function 454: DrawCapsuleWires() (6 input parameters) Name: DrawCapsuleWires Return type: void Description: Draw capsule wireframe with the center of its sphere caps at startPos and endPos @@ -3887,51 +3892,51 @@ Function 453: DrawCapsuleWires() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 454: DrawPlane() (3 input parameters) +Function 455: DrawPlane() (3 input parameters) Name: DrawPlane Return type: void Description: Draw a plane XZ Param[1]: centerPos (type: Vector3) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 455: DrawRay() (2 input parameters) +Function 456: DrawRay() (2 input parameters) Name: DrawRay Return type: void Description: Draw a ray line Param[1]: ray (type: Ray) Param[2]: color (type: Color) -Function 456: DrawGrid() (2 input parameters) +Function 457: DrawGrid() (2 input parameters) Name: DrawGrid Return type: void Description: Draw a grid (centered at (0, 0, 0)) Param[1]: slices (type: int) Param[2]: spacing (type: float) -Function 457: LoadModel() (1 input parameters) +Function 458: LoadModel() (1 input parameters) Name: LoadModel Return type: Model Description: Load model from files (meshes and materials) Param[1]: fileName (type: const char *) -Function 458: LoadModelFromMesh() (1 input parameters) +Function 459: LoadModelFromMesh() (1 input parameters) Name: LoadModelFromMesh Return type: Model Description: Load model from generated mesh (default material) Param[1]: mesh (type: Mesh) -Function 459: IsModelReady() (1 input parameters) +Function 460: IsModelReady() (1 input parameters) Name: IsModelReady Return type: bool Description: Check if a model is ready Param[1]: model (type: Model) -Function 460: UnloadModel() (1 input parameters) +Function 461: UnloadModel() (1 input parameters) Name: UnloadModel Return type: void Description: Unload model (including meshes) from memory (RAM and/or VRAM) Param[1]: model (type: Model) -Function 461: GetModelBoundingBox() (1 input parameters) +Function 462: GetModelBoundingBox() (1 input parameters) Name: GetModelBoundingBox Return type: BoundingBox Description: Compute model bounding box limits (considers all meshes) Param[1]: model (type: Model) -Function 462: DrawModel() (4 input parameters) +Function 463: DrawModel() (4 input parameters) Name: DrawModel Return type: void Description: Draw a model (with texture if set) @@ -3939,7 +3944,7 @@ Function 462: DrawModel() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 463: DrawModelEx() (6 input parameters) +Function 464: DrawModelEx() (6 input parameters) Name: DrawModelEx Return type: void Description: Draw a model with extended parameters @@ -3949,7 +3954,7 @@ Function 463: DrawModelEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 464: DrawModelWires() (4 input parameters) +Function 465: DrawModelWires() (4 input parameters) Name: DrawModelWires Return type: void Description: Draw a model wires (with texture if set) @@ -3957,7 +3962,7 @@ Function 464: DrawModelWires() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 465: DrawModelWiresEx() (6 input parameters) +Function 466: DrawModelWiresEx() (6 input parameters) Name: DrawModelWiresEx Return type: void Description: Draw a model wires (with texture if set) with extended parameters @@ -3967,7 +3972,7 @@ Function 465: DrawModelWiresEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 466: DrawModelPoints() (4 input parameters) +Function 467: DrawModelPoints() (4 input parameters) Name: DrawModelPoints Return type: void Description: Draw a model as points @@ -3975,7 +3980,7 @@ Function 466: DrawModelPoints() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 467: DrawModelPointsEx() (6 input parameters) +Function 468: DrawModelPointsEx() (6 input parameters) Name: DrawModelPointsEx Return type: void Description: Draw a model as points with extended parameters @@ -3985,13 +3990,13 @@ Function 467: DrawModelPointsEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 468: DrawBoundingBox() (2 input parameters) +Function 469: DrawBoundingBox() (2 input parameters) Name: DrawBoundingBox Return type: void Description: Draw bounding box (wires) Param[1]: box (type: BoundingBox) Param[2]: color (type: Color) -Function 469: DrawBillboard() (5 input parameters) +Function 470: DrawBillboard() (5 input parameters) Name: DrawBillboard Return type: void Description: Draw a billboard texture @@ -4000,7 +4005,7 @@ Function 469: DrawBillboard() (5 input parameters) Param[3]: position (type: Vector3) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 470: DrawBillboardRec() (6 input parameters) +Function 471: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec Return type: void Description: Draw a billboard texture defined by source @@ -4010,7 +4015,7 @@ Function 470: DrawBillboardRec() (6 input parameters) Param[4]: position (type: Vector3) Param[5]: size (type: Vector2) Param[6]: tint (type: Color) -Function 471: DrawBillboardPro() (9 input parameters) +Function 472: DrawBillboardPro() (9 input parameters) Name: DrawBillboardPro Return type: void Description: Draw a billboard texture defined by source and rotation @@ -4023,13 +4028,13 @@ Function 471: DrawBillboardPro() (9 input parameters) Param[7]: origin (type: Vector2) Param[8]: rotation (type: float) Param[9]: tint (type: Color) -Function 472: UploadMesh() (2 input parameters) +Function 473: UploadMesh() (2 input parameters) Name: UploadMesh Return type: void Description: Upload mesh vertex data in GPU and provide VAO/VBO ids Param[1]: mesh (type: Mesh *) Param[2]: dynamic (type: bool) -Function 473: UpdateMeshBuffer() (5 input parameters) +Function 474: UpdateMeshBuffer() (5 input parameters) Name: UpdateMeshBuffer Return type: void Description: Update mesh vertex data in GPU for a specific buffer index @@ -4038,19 +4043,19 @@ Function 473: UpdateMeshBuffer() (5 input parameters) Param[3]: data (type: const void *) Param[4]: dataSize (type: int) Param[5]: offset (type: int) -Function 474: UnloadMesh() (1 input parameters) +Function 475: UnloadMesh() (1 input parameters) Name: UnloadMesh Return type: void Description: Unload mesh data from CPU and GPU Param[1]: mesh (type: Mesh) -Function 475: DrawMesh() (3 input parameters) +Function 476: DrawMesh() (3 input parameters) Name: DrawMesh Return type: void Description: Draw a 3d mesh with material and transform Param[1]: mesh (type: Mesh) Param[2]: material (type: Material) Param[3]: transform (type: Matrix) -Function 476: DrawMeshInstanced() (4 input parameters) +Function 477: DrawMeshInstanced() (4 input parameters) Name: DrawMeshInstanced Return type: void Description: Draw multiple mesh instances with material and different transforms @@ -4058,35 +4063,35 @@ Function 476: DrawMeshInstanced() (4 input parameters) Param[2]: material (type: Material) Param[3]: transforms (type: const Matrix *) Param[4]: instances (type: int) -Function 477: GetMeshBoundingBox() (1 input parameters) +Function 478: GetMeshBoundingBox() (1 input parameters) Name: GetMeshBoundingBox Return type: BoundingBox Description: Compute mesh bounding box limits Param[1]: mesh (type: Mesh) -Function 478: GenMeshTangents() (1 input parameters) +Function 479: GenMeshTangents() (1 input parameters) Name: GenMeshTangents Return type: void Description: Compute mesh tangents Param[1]: mesh (type: Mesh *) -Function 479: ExportMesh() (2 input parameters) +Function 480: ExportMesh() (2 input parameters) Name: ExportMesh Return type: bool Description: Export mesh data to file, returns true on success Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 480: ExportMeshAsCode() (2 input parameters) +Function 481: ExportMeshAsCode() (2 input parameters) Name: ExportMeshAsCode Return type: bool Description: Export mesh as code file (.h) defining multiple arrays of vertex attributes Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 481: GenMeshPoly() (2 input parameters) +Function 482: GenMeshPoly() (2 input parameters) Name: GenMeshPoly Return type: Mesh Description: Generate polygonal mesh Param[1]: sides (type: int) Param[2]: radius (type: float) -Function 482: GenMeshPlane() (4 input parameters) +Function 483: GenMeshPlane() (4 input parameters) Name: GenMeshPlane Return type: Mesh Description: Generate plane mesh (with subdivisions) @@ -4094,42 +4099,42 @@ Function 482: GenMeshPlane() (4 input parameters) Param[2]: length (type: float) Param[3]: resX (type: int) Param[4]: resZ (type: int) -Function 483: GenMeshCube() (3 input parameters) +Function 484: GenMeshCube() (3 input parameters) Name: GenMeshCube Return type: Mesh Description: Generate cuboid mesh Param[1]: width (type: float) Param[2]: height (type: float) Param[3]: length (type: float) -Function 484: GenMeshSphere() (3 input parameters) +Function 485: GenMeshSphere() (3 input parameters) Name: GenMeshSphere Return type: Mesh Description: Generate sphere mesh (standard sphere) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 485: GenMeshHemiSphere() (3 input parameters) +Function 486: GenMeshHemiSphere() (3 input parameters) Name: GenMeshHemiSphere Return type: Mesh Description: Generate half-sphere mesh (no bottom cap) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 486: GenMeshCylinder() (3 input parameters) +Function 487: GenMeshCylinder() (3 input parameters) Name: GenMeshCylinder Return type: Mesh Description: Generate cylinder mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 487: GenMeshCone() (3 input parameters) +Function 488: GenMeshCone() (3 input parameters) Name: GenMeshCone Return type: Mesh Description: Generate cone/pyramid mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 488: GenMeshTorus() (4 input parameters) +Function 489: GenMeshTorus() (4 input parameters) Name: GenMeshTorus Return type: Mesh Description: Generate torus mesh @@ -4137,7 +4142,7 @@ Function 488: GenMeshTorus() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 489: GenMeshKnot() (4 input parameters) +Function 490: GenMeshKnot() (4 input parameters) Name: GenMeshKnot Return type: Mesh Description: Generate trefoil knot mesh @@ -4145,91 +4150,91 @@ Function 489: GenMeshKnot() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 490: GenMeshHeightmap() (2 input parameters) +Function 491: GenMeshHeightmap() (2 input parameters) Name: GenMeshHeightmap Return type: Mesh Description: Generate heightmap mesh from image data Param[1]: heightmap (type: Image) Param[2]: size (type: Vector3) -Function 491: GenMeshCubicmap() (2 input parameters) +Function 492: GenMeshCubicmap() (2 input parameters) Name: GenMeshCubicmap Return type: Mesh Description: Generate cubes-based map mesh from image data Param[1]: cubicmap (type: Image) Param[2]: cubeSize (type: Vector3) -Function 492: LoadMaterials() (2 input parameters) +Function 493: LoadMaterials() (2 input parameters) Name: LoadMaterials Return type: Material * Description: Load materials from model file Param[1]: fileName (type: const char *) Param[2]: materialCount (type: int *) -Function 493: LoadMaterialDefault() (0 input parameters) +Function 494: LoadMaterialDefault() (0 input parameters) Name: LoadMaterialDefault Return type: Material Description: Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) No input parameters -Function 494: IsMaterialReady() (1 input parameters) +Function 495: IsMaterialReady() (1 input parameters) Name: IsMaterialReady Return type: bool Description: Check if a material is ready Param[1]: material (type: Material) -Function 495: UnloadMaterial() (1 input parameters) +Function 496: UnloadMaterial() (1 input parameters) Name: UnloadMaterial Return type: void Description: Unload material from GPU memory (VRAM) Param[1]: material (type: Material) -Function 496: SetMaterialTexture() (3 input parameters) +Function 497: SetMaterialTexture() (3 input parameters) Name: SetMaterialTexture Return type: void Description: Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) Param[1]: material (type: Material *) Param[2]: mapType (type: int) Param[3]: texture (type: Texture2D) -Function 497: SetModelMeshMaterial() (3 input parameters) +Function 498: SetModelMeshMaterial() (3 input parameters) Name: SetModelMeshMaterial Return type: void Description: Set material for a mesh Param[1]: model (type: Model *) Param[2]: meshId (type: int) Param[3]: materialId (type: int) -Function 498: LoadModelAnimations() (2 input parameters) +Function 499: LoadModelAnimations() (2 input parameters) Name: LoadModelAnimations Return type: ModelAnimation * Description: Load model animations from file Param[1]: fileName (type: const char *) Param[2]: animCount (type: int *) -Function 499: UpdateModelAnimation() (3 input parameters) +Function 500: UpdateModelAnimation() (3 input parameters) Name: UpdateModelAnimation Return type: void Description: Update model animation pose Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 500: UnloadModelAnimation() (1 input parameters) +Function 501: UnloadModelAnimation() (1 input parameters) Name: UnloadModelAnimation Return type: void Description: Unload animation data Param[1]: anim (type: ModelAnimation) -Function 501: UnloadModelAnimations() (2 input parameters) +Function 502: UnloadModelAnimations() (2 input parameters) Name: UnloadModelAnimations Return type: void Description: Unload animation array data Param[1]: animations (type: ModelAnimation *) Param[2]: animCount (type: int) -Function 502: IsModelAnimationValid() (2 input parameters) +Function 503: IsModelAnimationValid() (2 input parameters) Name: IsModelAnimationValid Return type: bool Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 503: UpdateModelAnimationBoneMatrices() (3 input parameters) +Function 504: UpdateModelAnimationBoneMatrices() (3 input parameters) Name: UpdateModelAnimationBoneMatrices Return type: void Description: Update model animation mesh bone matrices Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 504: CheckCollisionSpheres() (4 input parameters) +Function 505: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4237,40 +4242,40 @@ Function 504: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 505: CheckCollisionBoxes() (2 input parameters) +Function 506: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 506: CheckCollisionBoxSphere() (3 input parameters) +Function 507: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 507: GetRayCollisionSphere() (3 input parameters) +Function 508: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 508: GetRayCollisionBox() (2 input parameters) +Function 509: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 509: GetRayCollisionMesh() (3 input parameters) +Function 510: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 510: GetRayCollisionTriangle() (4 input parameters) +Function 511: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4278,7 +4283,7 @@ Function 510: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 511: GetRayCollisionQuad() (5 input parameters) +Function 512: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4287,158 +4292,158 @@ Function 511: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 512: InitAudioDevice() (0 input parameters) +Function 513: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 513: CloseAudioDevice() (0 input parameters) +Function 514: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 514: IsAudioDeviceReady() (0 input parameters) +Function 515: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 515: SetMasterVolume() (1 input parameters) +Function 516: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 516: GetMasterVolume() (0 input parameters) +Function 517: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 517: LoadWave() (1 input parameters) +Function 518: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 518: LoadWaveFromMemory() (3 input parameters) +Function 519: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 519: IsWaveReady() (1 input parameters) +Function 520: IsWaveReady() (1 input parameters) Name: IsWaveReady Return type: bool Description: Checks if wave data is ready Param[1]: wave (type: Wave) -Function 520: LoadSound() (1 input parameters) +Function 521: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 521: LoadSoundFromWave() (1 input parameters) +Function 522: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 522: LoadSoundAlias() (1 input parameters) +Function 523: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 523: IsSoundReady() (1 input parameters) +Function 524: IsSoundReady() (1 input parameters) Name: IsSoundReady Return type: bool Description: Checks if a sound is ready Param[1]: sound (type: Sound) -Function 524: UpdateSound() (3 input parameters) +Function 525: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 525: UnloadWave() (1 input parameters) +Function 526: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 526: UnloadSound() (1 input parameters) +Function 527: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 527: UnloadSoundAlias() (1 input parameters) +Function 528: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 528: ExportWave() (2 input parameters) +Function 529: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 529: ExportWaveAsCode() (2 input parameters) +Function 530: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 530: PlaySound() (1 input parameters) +Function 531: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 531: StopSound() (1 input parameters) +Function 532: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 532: PauseSound() (1 input parameters) +Function 533: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 533: ResumeSound() (1 input parameters) +Function 534: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 534: IsSoundPlaying() (1 input parameters) +Function 535: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 535: SetSoundVolume() (2 input parameters) +Function 536: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 536: SetSoundPitch() (2 input parameters) +Function 537: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 537: SetSoundPan() (2 input parameters) +Function 538: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (0.5 is center) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 538: WaveCopy() (1 input parameters) +Function 539: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 539: WaveCrop() (3 input parameters) +Function 540: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 540: WaveFormat() (4 input parameters) +Function 541: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4446,203 +4451,203 @@ Function 540: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 541: LoadWaveSamples() (1 input parameters) +Function 542: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 542: UnloadWaveSamples() (1 input parameters) +Function 543: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 543: LoadMusicStream() (1 input parameters) +Function 544: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 544: LoadMusicStreamFromMemory() (3 input parameters) +Function 545: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 545: IsMusicReady() (1 input parameters) +Function 546: IsMusicReady() (1 input parameters) Name: IsMusicReady Return type: bool Description: Checks if a music stream is ready Param[1]: music (type: Music) -Function 546: UnloadMusicStream() (1 input parameters) +Function 547: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 547: PlayMusicStream() (1 input parameters) +Function 548: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 548: IsMusicStreamPlaying() (1 input parameters) +Function 549: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 549: UpdateMusicStream() (1 input parameters) +Function 550: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 550: StopMusicStream() (1 input parameters) +Function 551: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 551: PauseMusicStream() (1 input parameters) +Function 552: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 552: ResumeMusicStream() (1 input parameters) +Function 553: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 553: SeekMusicStream() (2 input parameters) +Function 554: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 554: SetMusicVolume() (2 input parameters) +Function 555: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 555: SetMusicPitch() (2 input parameters) +Function 556: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 556: SetMusicPan() (2 input parameters) +Function 557: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (0.5 is center) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 557: GetMusicTimeLength() (1 input parameters) +Function 558: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 558: GetMusicTimePlayed() (1 input parameters) +Function 559: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 559: LoadAudioStream() (3 input parameters) +Function 560: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 560: IsAudioStreamReady() (1 input parameters) +Function 561: IsAudioStreamReady() (1 input parameters) Name: IsAudioStreamReady Return type: bool Description: Checks if an audio stream is ready Param[1]: stream (type: AudioStream) -Function 561: UnloadAudioStream() (1 input parameters) +Function 562: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 562: UpdateAudioStream() (3 input parameters) +Function 563: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 563: IsAudioStreamProcessed() (1 input parameters) +Function 564: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 564: PlayAudioStream() (1 input parameters) +Function 565: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 565: PauseAudioStream() (1 input parameters) +Function 566: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 566: ResumeAudioStream() (1 input parameters) +Function 567: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 567: IsAudioStreamPlaying() (1 input parameters) +Function 568: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 568: StopAudioStream() (1 input parameters) +Function 569: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 569: SetAudioStreamVolume() (2 input parameters) +Function 570: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 570: SetAudioStreamPitch() (2 input parameters) +Function 571: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 571: SetAudioStreamPan() (2 input parameters) +Function 572: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 572: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 573: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 573: SetAudioStreamCallback() (2 input parameters) +Function 574: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 574: AttachAudioStreamProcessor() (2 input parameters) +Function 575: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives the samples as 'float' Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 575: DetachAudioStreamProcessor() (2 input parameters) +Function 576: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 576: AttachAudioMixedProcessor() (1 input parameters) +Function 577: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' Param[1]: processor (type: AudioCallback) -Function 577: DetachAudioMixedProcessor() (1 input parameters) +Function 578: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 13ea5b7ab6c6..140f4910d273 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -675,7 +675,7 @@ - + @@ -1074,6 +1074,9 @@ + + + From 100f6624d62d29638fc7174f41a6cba1548fb3e2 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 22:36:17 +0200 Subject: [PATCH 095/107] Update rcore.c --- src/rcore.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 1501aced1168..50814443c29f 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -205,9 +205,13 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #include // Required for: _mkdir() #define MKDIR(dir) _mkdir(dir) #elif defined __GNUC__ - #include - #include // Required for: mkdir() - #define MKDIR(dir) mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! + #if defined(__linux__) + #define MKDIR(dir) mkdir(dir, 0777); + #else + #include + #include // Required for: mkdir() + #define MKDIR(dir) _mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! + #endif #endif //---------------------------------------------------------------------------------- From 352c450ad02b9920110bbaa57171f3a184dc4a22 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 22:43:35 +0200 Subject: [PATCH 096/107] Update rcore.c --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 50814443c29f..03d3d1fb4cea 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -210,7 +210,7 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #else #include #include // Required for: mkdir() - #define MKDIR(dir) _mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! + #define MKDIR(dir) mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! #endif #endif From 24a1cd8c5b95116c207d8f9189fc6eba245ac40c Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 22:50:47 +0200 Subject: [PATCH 097/107] Update rcore.c --- src/rcore.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 03d3d1fb4cea..74659b6fd555 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -205,12 +205,14 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #include // Required for: _mkdir() #define MKDIR(dir) _mkdir(dir) #elif defined __GNUC__ - #if defined(__linux__) + #if defined(EMSCRIPTEN) + #define MKDIR(dir) mkdir(dir) + #elif defined(__linux__) #define MKDIR(dir) mkdir(dir, 0777); #else #include #include // Required for: mkdir() - #define MKDIR(dir) mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! + #define MKDIR(dir) _mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! #endif #endif From 291063c98fd9ebcff1e852f0eaeab1deb6e8f028 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 22:56:21 +0200 Subject: [PATCH 098/107] Update rcore.c --- src/rcore.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 74659b6fd555..ccd58bdb2201 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -195,26 +195,30 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #include // Required for: _getch(), _chdir(), _mkdir() #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() #define CHDIR _chdir + #define MKDIR(dir) _mkdir(dir) #else #include // Required for: getch(), chdir() (POSIX), access() #define GETCWD getcwd #define CHDIR chdir + #define MKDIR(dir) mkdir(dir, 0777) #endif +/* #if defined(_MSC_VER) && ((defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__)) - #include // Required for: _mkdir() - #define MKDIR(dir) _mkdir(dir) + //#include // Required for: _mkdir() + //#define MKDIR(dir) _mkdir(dir) #elif defined __GNUC__ #if defined(EMSCRIPTEN) - #define MKDIR(dir) mkdir(dir) + #define MKDIR(dir) mkdir(dir, 0777) #elif defined(__linux__) - #define MKDIR(dir) mkdir(dir, 0777); + #define MKDIR(dir) mkdir(dir, 0777) #else #include #include // Required for: mkdir() #define MKDIR(dir) _mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! #endif #endif +*/ //---------------------------------------------------------------------------------- // Defines and Macros From 13178b9373cebffb97a4ff66b734bacbb9d8758d Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Sep 2024 23:01:22 +0200 Subject: [PATCH 099/107] Update rcore.c --- src/rcore.c | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index ccd58bdb2201..3cdacbd96f40 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -197,29 +197,12 @@ unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #define CHDIR _chdir #define MKDIR(dir) _mkdir(dir) #else - #include // Required for: getch(), chdir() (POSIX), access() + #include // Required for: getch(), chdir(), mkdir(), access() #define GETCWD getcwd #define CHDIR chdir #define MKDIR(dir) mkdir(dir, 0777) #endif -/* -#if defined(_MSC_VER) && ((defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__)) - //#include // Required for: _mkdir() - //#define MKDIR(dir) _mkdir(dir) -#elif defined __GNUC__ - #if defined(EMSCRIPTEN) - #define MKDIR(dir) mkdir(dir, 0777) - #elif defined(__linux__) - #define MKDIR(dir) mkdir(dir, 0777) - #else - #include - #include // Required for: mkdir() - #define MKDIR(dir) _mkdir(dir) // OLD: mkdir(dir, 0777) -> w64devkit complaints! - #endif -#endif -*/ - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- From 55e83468c9bb515a5f2725751e5f187a7b9b5afd Mon Sep 17 00:00:00 2001 From: Menno van der Graaf Date: Tue, 24 Sep 2024 20:02:54 +0200 Subject: [PATCH 100/107] Fix isGpuReady flag on android (#4340) --- src/platforms/rcore_android.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 55706c591127..6318931ccc62 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -74,7 +74,7 @@ typedef struct { // Global Variables Definition //---------------------------------------------------------------------------------- extern CoreData CORE; // Global CORE state context - +extern bool isGpuReady; // Flag to note GPU has been initialized successfully static PlatformData platform = { 0 }; // Platform specific data //---------------------------------------------------------------------------------- @@ -989,6 +989,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Initialize OpenGL context (states and resources) // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); + isGpuReady = true; // Setup default viewport // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height From 0573ef03820f0948b38d52236d15c2f18c871cc7 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Fri, 27 Sep 2024 14:06:54 -0700 Subject: [PATCH 101/107] [SHAPES] Add more detail to comment for DrawPixel (#4344) * Update raylib_api.* by CI * Add comment that draw pixel uses geometry and may be slow * Update raylib_api.* by CI --------- Co-authored-by: github-actions[bot] --- parser/output/raylib_api.json | 4 ++-- parser/output/raylib_api.lua | 4 ++-- parser/output/raylib_api.txt | 4 ++-- parser/output/raylib_api.xml | 4 ++-- src/raylib.h | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 2e599496d95c..ca3b276cf0a8 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -5274,7 +5274,7 @@ }, { "name": "DrawPixel", - "description": "Draw a pixel", + "description": "Draw a pixel using geometry [Can be slow, use with care]", "returnType": "void", "params": [ { @@ -5293,7 +5293,7 @@ }, { "name": "DrawPixelV", - "description": "Draw a pixel (Vector version)", + "description": "Draw a pixel using geometry (Vector version) [Can be slow, use with care]", "returnType": "void", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 69ae07eda314..b010e19cf13f 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4614,7 +4614,7 @@ return { }, { name = "DrawPixel", - description = "Draw a pixel", + description = "Draw a pixel using geometry [Can be slow, use with care]", returnType = "void", params = { {type = "int", name = "posX"}, @@ -4624,7 +4624,7 @@ return { }, { name = "DrawPixelV", - description = "Draw a pixel (Vector version)", + description = "Draw a pixel using geometry (Vector version) [Can be slow, use with care]", returnType = "void", params = { {type = "Vector2", name = "position"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 458d12da0642..19abf2d00350 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -2102,14 +2102,14 @@ Function 207: GetShapesTextureRectangle() (0 input parameters) Function 208: DrawPixel() (3 input parameters) Name: DrawPixel Return type: void - Description: Draw a pixel + Description: Draw a pixel using geometry [Can be slow, use with care] Param[1]: posX (type: int) Param[2]: posY (type: int) Param[3]: color (type: Color) Function 209: DrawPixelV() (2 input parameters) Name: DrawPixelV Return type: void - Description: Draw a pixel (Vector version) + Description: Draw a pixel using geometry (Vector version) [Can be slow, use with care] Param[1]: position (type: Vector2) Param[2]: color (type: Color) Function 210: DrawLine() (5 input parameters) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 140f4910d273..13afbcb64cf7 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1297,12 +1297,12 @@ - + - + diff --git a/src/raylib.h b/src/raylib.h index f298fd731985..106a96ae5d5e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1234,8 +1234,8 @@ RLAPI Texture2D GetShapesTexture(void); // Get t RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing // Basic shapes drawing functions -RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel -RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version) +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) From 84a4d93bc4e421e2fc654b0e4d7c513ac17990e0 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 30 Sep 2024 12:06:53 +0200 Subject: [PATCH 102/107] Fix #4349 --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index f6e3fe304408..ca9489822c95 100644 --- a/src/config.h +++ b/src/config.h @@ -228,7 +228,7 @@ // rmodels: Configuration values //------------------------------------------------------------------------------------ #define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported -#define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh //------------------------------------------------------------------------------------ // Module: raudio - Configuration Flags From 0e7bcd5639729fac1df19ed2d0622ff3daab59e3 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Mon, 30 Sep 2024 03:10:02 -0700 Subject: [PATCH 103/107] [MODELS] Disable GPU skinning for MacOS platform (#4348) * Update raylib_api.* by CI * Disable GPU skinning on MacOS Add GPU skinning example to MSVC Projects. * Update raylib_api.* by CI --------- Co-authored-by: github-actions[bot] --- examples/models/models_gpu_skinning.c | 2 + parser/output/raylib_api.json | 2 +- parser/output/raylib_api.lua | 2 +- parser/output/raylib_api.txt | 2 +- parser/output/raylib_api.xml | 2 +- .../examples/models_gpu_skinning.vcxproj | 387 ++++++++++++++++++ projects/VS2022/raylib.sln | 19 + src/config.h | 15 +- src/raylib.h | 2 +- src/rlgl.h | 14 +- src/rmodels.c | 18 +- 11 files changed, 447 insertions(+), 18 deletions(-) create mode 100644 projects/VS2022/examples/models_gpu_skinning.vcxproj diff --git a/examples/models/models_gpu_skinning.c b/examples/models/models_gpu_skinning.c index e9cd73f4b8d1..846f0a890905 100644 --- a/examples/models/models_gpu_skinning.c +++ b/examples/models/models_gpu_skinning.c @@ -10,6 +10,8 @@ * BSD-like license that allows static linking with closed source software * * Copyright (c) 2024 Daniel Holden (@orangeduck) +* +* Note: Due to limitations in the Apple OpenGL driver, this feature does not work on MacOS * ********************************************************************************************/ diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index ca3b276cf0a8..58fb9d6a817e 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -11104,7 +11104,7 @@ }, { "name": "UpdateModelAnimationBoneMatrices", - "description": "Update model animation mesh bone matrices", + "description": "Update model animation mesh bone matrices (Note GPU skinning does not work on Mac)", "returnType": "void", "params": [ { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index b010e19cf13f..bea3813cf53a 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -7621,7 +7621,7 @@ return { }, { name = "UpdateModelAnimationBoneMatrices", - description = "Update model animation mesh bone matrices", + description = "Update model animation mesh bone matrices (Note GPU skinning does not work on Mac)", returnType = "void", params = { {type = "Model", name = "model"}, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 19abf2d00350..9a953b768d28 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -4230,7 +4230,7 @@ Function 503: IsModelAnimationValid() (2 input parameters) Function 504: UpdateModelAnimationBoneMatrices() (3 input parameters) Name: UpdateModelAnimationBoneMatrices Return type: void - Description: Update model animation mesh bone matrices + Description: Update model animation mesh bone matrices (Note GPU skinning does not work on Mac) Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 13afbcb64cf7..895680f2cd2a 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -2830,7 +2830,7 @@ - + diff --git a/projects/VS2022/examples/models_gpu_skinning.vcxproj b/projects/VS2022/examples/models_gpu_skinning.vcxproj new file mode 100644 index 000000000000..bd596bed6c95 --- /dev/null +++ b/projects/VS2022/examples/models_gpu_skinning.vcxproj @@ -0,0 +1,387 @@ + + + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + Win32 + + + Release + x64 + + + + {8245DAD9-D402-4D5C-8F45-32229CD3B263} + Win32Proj + models_loading + 10.0 + models_gpu_skinning + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 294d0e84dfb8..2b262bcfe015 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -299,6 +299,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_splines_drawing", "e EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_top_down_lights", "examples\shapes_top_down_lights.vcxproj", "{703BE7BA-5B99-4F70-806D-3A259F6A991E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "models_gpu_skinning", "examples\models_gpu_skinning.vcxproj", "{8245DAD9-D402-4D5C-8F45-32229CD3B263}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|x64 = Debug.DLL|x64 @@ -2531,6 +2533,22 @@ Global {703BE7BA-5B99-4F70-806D-3A259F6A991E}.Release|x64.Build.0 = Release|x64 {703BE7BA-5B99-4F70-806D-3A259F6A991E}.Release|x86.ActiveCfg = Release|Win32 {703BE7BA-5B99-4F70-806D-3A259F6A991E}.Release|x86.Build.0 = Release|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug|x64.ActiveCfg = Debug|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug|x64.Build.0 = Debug|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug|x86.ActiveCfg = Debug|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Debug|x86.Build.0 = Debug|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release|x64.ActiveCfg = Release|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release|x64.Build.0 = Release|x64 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release|x86.ActiveCfg = Release|Win32 + {8245DAD9-D402-4D5C-8F45-32229CD3B263}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2682,6 +2700,7 @@ Global {D8026C60-CCBC-45DF-9085-BF21569EB414} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {DF25E545-00FF-4E64-844C-7DF98991F901} = {278D8859-20B1-428F-8448-064F46E1F021} {703BE7BA-5B99-4F70-806D-3A259F6A991E} = {278D8859-20B1-428F-8448-064F46E1F021} + {8245DAD9-D402-4D5C-8F45-32229CD3B263} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/src/config.h b/src/config.h index ca9489822c95..8bba37606a09 100644 --- a/src/config.h +++ b/src/config.h @@ -119,9 +119,18 @@ #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 6 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 7 -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 8 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 + +// The mac OpenGL drivers do not support more than 8 VBOs, so we can't support GPU animations +#ifndef __APPLE__ +#define RL_SUPPORT_MESH_ANIMATION_VBO +#endif + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 +#endif + // Default shader vertex attribute names to set location points // NOTE: When a new shader is loaded, the following locations are tried to be set for convenience diff --git a/src/raylib.h b/src/raylib.h index 106a96ae5d5e..08121c50c5e2 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1597,7 +1597,7 @@ RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match -RLAPI void UpdateModelAnimationBoneMatrices(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices +RLAPI void UpdateModelAnimationBoneMatrices(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (Note GPU skinning does not work on Mac) // Collision detection functions RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres diff --git a/src/rlgl.h b/src/rlgl.h index 875b568f5258..843c46a2a3b6 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -344,16 +344,17 @@ #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 #endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 6 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 7 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 8 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 #endif + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -4170,8 +4171,11 @@ unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); +#endif // NOTE: If some attrib name is no found on the shader, it locations becomes -1 diff --git a/src/rmodels.c b/src/rmodels.c index ebdd178fdd38..d5c2f99546d8 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1252,9 +1252,10 @@ void UploadMesh(Mesh *mesh, bool dynamic) mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR] = 0; // Vertex buffer: colors mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; // Vertex buffer: tangents mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 + mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS] = 0; // Vertex buffer: boneIds mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = 0; // Vertex buffer: boneWeights - mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) mesh->vaoId = rlLoadVertexArray(); @@ -1340,7 +1341,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, value, SHADER_ATTRIB_VEC2, 2); rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2); } - + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO if (mesh->boneIds != NULL) { // Enable vertex attribute: boneIds (shader-location = 6) @@ -1372,6 +1374,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, value, SHADER_ATTRIB_VEC4, 2); rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS); } +#endif if (mesh->indices != NULL) { @@ -1485,13 +1488,14 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Upload model normal matrix (if locations available) if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); - + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO // Upload Bone Transforms if (material.shader.locs[SHADER_LOC_BONE_MATRICES] != -1 && mesh.boneMatrices) { rlSetUniformMatrices(material.shader.locs[SHADER_LOC_BONE_MATRICES], mesh.boneMatrices, mesh.boneCount); } - +#endif //----------------------------------------------------- // Bind active texture maps (if available) @@ -1570,7 +1574,8 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } - + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) { @@ -1586,6 +1591,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]); } +#endif if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); } @@ -1729,11 +1735,13 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // Upload model normal matrix (if locations available) if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO // Upload Bone Transforms if (material.shader.locs[SHADER_LOC_BONE_MATRICES] != -1 && mesh.boneMatrices) { rlSetUniformMatrices(material.shader.locs[SHADER_LOC_BONE_MATRICES], mesh.boneMatrices, mesh.boneCount); } +#endif //----------------------------------------------------- From 282d6478baa51a509bf0a4b1d761a0bd7fd8bbf7 Mon Sep 17 00:00:00 2001 From: Asdqwe Date: Tue, 1 Oct 2024 07:09:06 -0300 Subject: [PATCH 104/107] Complements the #4348 GPU skinning fix (#4352) --- src/rlgl.h | 5 ++++- src/rmodels.c | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 843c46a2a3b6..98310c101b3e 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -345,14 +345,17 @@ #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 #endif + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 #endif #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 #endif +#endif //---------------------------------------------------------------------------------- diff --git a/src/rmodels.c b/src/rmodels.c index d5c2f99546d8..b7f18f307c43 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1253,8 +1253,11 @@ void UploadMesh(Mesh *mesh, bool dynamic) mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT] = 0; // Vertex buffer: tangents mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS] = 0; // Vertex buffer: boneIds mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = 0; // Vertex buffer: boneWeights +#endif #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -1819,7 +1822,8 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } - + +#ifdef RL_SUPPORT_MESH_ANIMATION_VBO // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) { @@ -1835,6 +1839,7 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS], 4, RL_FLOAT, 0, 0, 0); rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS]); } +#endif if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES]); } From d9c10ed2646eea784ba71ae24ef53f67a497fc08 Mon Sep 17 00:00:00 2001 From: Asdqwe Date: Wed, 2 Oct 2024 05:49:06 -0300 Subject: [PATCH 105/107] Fixes GetClipboardText() memory leak for PLATFORM_DESKTOP_SDL (#4354) --- src/platforms/rcore_desktop_sdl.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 794f9e6a088f..90372c2f6d08 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -57,6 +57,13 @@ #include "SDL_opengl.h" // SDL OpenGL functionality (if required, instead of internal renderer) #endif +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef MAX_CLIPBOARD_BUFFER_LENGTH + #define MAX_CLIPBOARD_BUFFER_LENGTH 1024 // Size of the clipboard buffer used on GetClipboardText() +#endif + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -852,10 +859,22 @@ void SetClipboardText(const char *text) } // Get clipboard text content -// NOTE: returned string must be freed with SDL_free() const char *GetClipboardText(void) { - return SDL_GetClipboardText(); + static char buffer[MAX_CLIPBOARD_BUFFER_LENGTH] = { 0 }; + + char *clipboard = SDL_GetClipboardText(); + + int clipboardSize = snprintf(buffer, sizeof(buffer), "%s", clipboard); + if (clipboardSize >= MAX_CLIPBOARD_BUFFER_LENGTH) + { + char *truncate = buffer + MAX_CLIPBOARD_BUFFER_LENGTH - 4; + sprintf(truncate, "..."); + } + + SDL_free(clipboard); + + return buffer; } // Show mouse cursor @@ -1589,8 +1608,8 @@ int InitPlatform(void) // Initialize storage system //---------------------------------------------------------------------------- // Define base path for storage - CORE.Storage.basePath = SDL_GetBasePath(); // Alternative: GetWorkingDirectory(); - CHDIR(CORE.Storage.basePath); + CORE.Storage.basePath = SDL_GetBasePath(); // Alternative: GetWorkingDirectory(); + CHDIR(CORE.Storage.basePath); //---------------------------------------------------------------------------- TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (SDL): Initialized successfully"); From 09987b01cc2c1f4e49d3a2bb11cf235a5a529435 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 2 Oct 2024 01:49:56 -0700 Subject: [PATCH 106/107] [MODELS] Better fix for GPU skinning issues (#4353) * Make the max VBO match the animation flag. * re-enable GPU skinning for mac, and fix the max buffer to be correct based on the GPU skinning support flag. --- src/config.h | 13 ++++++++----- src/rlgl.h | 2 +- src/rmodels.c | 8 ++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/config.h b/src/config.h index 8bba37606a09..5504a18239b7 100644 --- a/src/config.h +++ b/src/config.h @@ -121,12 +121,10 @@ #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 -// The mac OpenGL drivers do not support more than 8 VBOs, so we can't support GPU animations -#ifndef __APPLE__ -#define RL_SUPPORT_MESH_ANIMATION_VBO -#endif -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#define RL_SUPPORT_MESH_GPU_SKINNING // Remove this if your GPU does not support more than 8 VBOs + +#ifdef RL_SUPPORT_MESH_GPU_SKINNING #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 #endif @@ -237,7 +235,12 @@ // rmodels: Configuration values //------------------------------------------------------------------------------------ #define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported + +#ifdef RL_SUPPORT_MESH_GPU_SKINNING #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh +#else +#define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#endif //------------------------------------------------------------------------------------ // Module: raudio - Configuration Flags diff --git a/src/rlgl.h b/src/rlgl.h index 98310c101b3e..95af8e0e775c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4175,7 +4175,7 @@ unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); #endif diff --git a/src/rmodels.c b/src/rmodels.c index b7f18f307c43..7e17386e198b 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1345,7 +1345,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2); } -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING if (mesh->boneIds != NULL) { // Enable vertex attribute: boneIds (shader-location = 6) @@ -1492,7 +1492,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Upload model normal matrix (if locations available) if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING // Upload Bone Transforms if (material.shader.locs[SHADER_LOC_BONE_MATRICES] != -1 && mesh.boneMatrices) { @@ -1578,7 +1578,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) { @@ -1738,7 +1738,7 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // Upload model normal matrix (if locations available) if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel))); -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING // Upload Bone Transforms if (material.shader.locs[SHADER_LOC_BONE_MATRICES] != -1 && mesh.boneMatrices) { From 96d91a38923094951e481d9fbc26291026e48fea Mon Sep 17 00:00:00 2001 From: Asdqwe Date: Wed, 2 Oct 2024 06:41:21 -0300 Subject: [PATCH 107/107] [rlgl] Fix rlgl standalone defaults (#4357) * Fix rlgl standalone defaults * Fix rmodels --- src/rlgl.h | 2 +- src/rmodels.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 95af8e0e775c..43698f63aa21 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -348,7 +348,7 @@ #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 #endif -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 #endif diff --git a/src/rmodels.c b/src/rmodels.c index 7e17386e198b..d2bf528fffa0 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1254,7 +1254,7 @@ void UploadMesh(Mesh *mesh, bool dynamic) mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2] = 0; // Vertex buffer: texcoords2 mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES] = 0; // Vertex buffer: indices -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS] = 0; // Vertex buffer: boneIds mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS] = 0; // Vertex buffer: boneWeights #endif @@ -1823,7 +1823,7 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); } -#ifdef RL_SUPPORT_MESH_ANIMATION_VBO +#ifdef RL_SUPPORT_MESH_GPU_SKINNING // Bind mesh VBO data: vertex bone ids (shader-location = 6, if available) if (material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) {