From 9ef3448193c4d823975816472e4e9f198cea21c9 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2025 19:47:05 +0200 Subject: [PATCH 01/25] Reviewed UUIDs --- .../examples/core_text_file_loading.vcxproj | 2 +- projects/VS2022/raylib.sln | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/projects/VS2022/examples/core_text_file_loading.vcxproj b/projects/VS2022/examples/core_text_file_loading.vcxproj index c2e25e042..15324701d 100644 --- a/projects/VS2022/examples/core_text_file_loading.vcxproj +++ b/projects/VS2022/examples/core_text_file_loading.vcxproj @@ -51,7 +51,7 @@ - {6B1A933E-71B8-4C1F-9E79-02D98830E671} + {2F578155-D51F-4C03-AB7F-5C5122CA46CC} Win32Proj core_text_file_loading 10.0 diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 7a7eca297..d0a886a4f 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -391,7 +391,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_highdpi_testbed", "exa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_screen_recording", "examples\core_screen_recording.vcxproj", "{9DE2FC01-A839-4F89-8319-9071D4C54821}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "examples\core_text_file_loading.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "examples\core_text_file_loading.vcxproj", "{2F578155-D51F-4C03-AB7F-5C5122CA46CC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -4847,6 +4847,30 @@ Global {9DE2FC01-A839-4F89-8319-9071D4C54821}.Release|x64.Build.0 = Release|x64 {9DE2FC01-A839-4F89-8319-9071D4C54821}.Release|x86.ActiveCfg = Release|Win32 {9DE2FC01-A839-4F89-8319-9071D4C54821}.Release|x86.Build.0 = Release|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|ARM64.Build.0 = Debug|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x64.ActiveCfg = Debug|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x64.Build.0 = Debug|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x86.ActiveCfg = Debug|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x86.Build.0 = Debug|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|ARM64.ActiveCfg = Release|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|ARM64.Build.0 = Release|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x64.ActiveCfg = Release|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x64.Build.0 = Release|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.ActiveCfg = Release|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From aeafce5db4a8b52371882f33dcd5f4ffacbaecac Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:50:52 +0200 Subject: [PATCH 02/25] [examples] Added: `shaders_mandelbrot_set` (#5282) * [examples] Added: `shaders_mandelbrot_set` * Simplified shader code and added comments * Comments starting with a capital letter, and some minor fixes to adhere to the convention --- .../shaders/glsl100/mandelbrot_set.fs | 61 ++ .../shaders/glsl120/mandelbrot_set.fs | 59 ++ .../shaders/glsl330/mandelbrot_set.fs | 58 ++ examples/shaders/shaders_mandelbrot_set.c | 224 +++++++ examples/shaders/shaders_mandelbrot_set.png | Bin 0 -> 71902 bytes .../examples/shaders_mandelbrot_set.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 2 + 7 files changed, 973 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs create mode 100644 examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs create mode 100644 examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs create mode 100644 examples/shaders/shaders_mandelbrot_set.c create mode 100644 examples/shaders/shaders_mandelbrot_set.png create mode 100644 projects/VS2022/examples/shaders_mandelbrot_set.vcxproj diff --git a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs new file mode 100644 index 000000000..aab8514e8 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs @@ -0,0 +1,61 @@ +#version 100 + +#define PI 3.1415926535897932384626433832795 + +precision highp float; + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform vec2 offset; // Offset of the scale +uniform float zoom; // Zoom of the scale +// NOTE: Maximum number of shader for-loop iterations depend on GPU, +// For example, on RasperryPi for this examply only supports up to 60 +uniform int maxIterations; // Max iterations per pixel + +const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max*max; // Square of max to avoid computing square root + +void main() +{ + // The pixel coordinates are scaled so they are on the mandelbrot scale + // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + while (iter < maxIterations) + { + float aa = a*a; + float bb = b*b; + if (aa + bb > max2) + break; + + float twoab = 2.0*a*b; + a = aa - bb + c.x; + b = twoab + c.y; + + ++iter; + } + + if (iter >= maxIterations) + { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } + else + { + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; + + gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + } +} diff --git a/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs new file mode 100644 index 000000000..5da3ef437 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs @@ -0,0 +1,59 @@ +#version 120 + +#define PI 3.1415926535897932384626433832795 + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform vec2 offset; // Offset of the scale +uniform float zoom; // Zoom of the scale +// NOTE: Maximum number of shader for-loop iterations depend on GPU, +// For example, on RasperryPi for this examply only supports up to 60 +uniform int maxIterations; // Max iterations per pixel + +const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max*max; // Square of max to avoid computing square root + +void main() +{ + // The pixel coordinates are scaled so they are on the mandelbrot scale + // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + while (iter < maxIterations) + { + float aa = a*a; + float bb = b*b; + if (aa + bb > max2) + break; + + float twoab = 2.0*a*b; + a = aa - bb + c.x; + b = twoab + c.y; + + ++iter; + } + + if (iter >= maxIterations) + { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } + else + { + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; + + gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + } +} diff --git a/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs new file mode 100644 index 000000000..bde74565f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs @@ -0,0 +1,58 @@ +#version 330 + +#define PI 3.1415926535897932384626433832795 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Output fragment color +out vec4 finalColor; + +uniform vec2 offset; // Offset of the scale +uniform float zoom; // Zoom of the scale +uniform int maxIterations; // Max iterations per pixel + +const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max*max; // Square of max to avoid computing square root + +void main() +{ + // The pixel coordinates are scaled so they are on the mandelbrot scale + // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + for (iter = 0; iter < maxIterations; ++iter) + { + float aa = a*a; + float bb = b*b; + if (aa + bb > max2) + break; + + float twoab = 2.0*a*b; + a = aa - bb + c.x; + b = twoab + c.y; + } + + if (iter >= maxIterations) + { + finalColor = vec4(0.0, 0.0, 0.0, 1.0); + } + else + { + float normR = float(iter%55)/55.0; + float normG = float(iter%69)/69.0; + float normB = float(iter%40)/40.0; + + finalColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + } +} diff --git a/examples/shaders/shaders_mandelbrot_set.c b/examples/shaders/shaders_mandelbrot_set.c new file mode 100644 index 000000000..c8d0ce8d3 --- /dev/null +++ b/examples/shaders/shaders_mandelbrot_set.c @@ -0,0 +1,224 @@ +/******************************************************************************************* +* +* raylib [shaders] example - mandelbrot set +* +* Example complexity rating: [★★★☆] 3/4 +* +* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, +* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version +* +* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3) +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* Example contributed by Jordi Santonja (@JordSant) +* Based on previous work by Josh Colclough (@joshcol9232) +* +* 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) 2025 Jordi Santonja (@JordSant) +* +********************************************************************************************/ + +#include "raylib.h" +#include + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +// A few good interesting places +const float pointsOfInterest[6][3] = +{ + { -1.76826775f, -0.00422996283f, 28435.9238f }, + { 0.322004497f, -0.0357099883f, 56499.7266f }, + { -0.748880744f, -0.0562955774f, 9237.59082f }, + { -1.78385007f, -0.0156200649f, 14599.5283f }, + { -0.0985441282f, -0.924688697f, 26259.8535f }, + { 0.317785531f, -0.0322612226f, 29297.9258f }, +}; + +const int screenWidth = 800; +const int screenHeight = 450; +const float zoomSpeed = 1.01f; +const float offsetSpeedMul = 2.0f; + +const float startingZoom = 0.6f; +const float startingOffset[2] = { -0.5f, 0.0f }; + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - mandelbrot set"); + + // Load mandelbrot set shader + // NOTE: Defining 0 (NULL) for vertex shader forces usage of internal default vertex shader + Shader shader = LoadShader(0, TextFormat("resources/shaders/glsl%i/mandelbrot_set.fs", GLSL_VERSION)); + + // Create a RenderTexture2D to be used for render to texture + RenderTexture2D target = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); + + // Offset and zoom to draw the mandelbrot set at. (centered on screen and default size) + float offset[2] = { startingOffset[0], startingOffset[1] }; + float zoom = startingZoom; + // Depending on the zoom the mximum number of iterations must be adapted to get more detail as we zzoom in + // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys + int maxIterations = 333; + float maxIterationsMultiplier = 166.5f; + + // Get variable (uniform) locations on the shader to connect with the program + // NOTE: If uniform variable could not be found in the shader, function returns -1 + int zoomLoc = GetShaderLocation(shader, "zoom"); + int offsetLoc = GetShaderLocation(shader, "offset"); + int maxIterationsLoc = GetShaderLocation(shader, "maxIterations"); + + // Upload the shader uniform values! + SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2); + SetShaderValue(shader, maxIterationsLoc, &maxIterations, SHADER_UNIFORM_INT); + + bool showControls = true; // Show controls + + 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 + //---------------------------------------------------------------------------------- + bool updateShader = false; + + // Press [1 - 6] to reset c to a point of interest + if (IsKeyPressed(KEY_ONE) || + IsKeyPressed(KEY_TWO) || + IsKeyPressed(KEY_THREE) || + IsKeyPressed(KEY_FOUR) || + IsKeyPressed(KEY_FIVE) || + IsKeyPressed(KEY_SIX)) + { + int interestIndex = 0; + if (IsKeyPressed(KEY_ONE)) interestIndex = 0; + else if (IsKeyPressed(KEY_TWO)) interestIndex = 1; + else if (IsKeyPressed(KEY_THREE)) interestIndex = 2; + else if (IsKeyPressed(KEY_FOUR)) interestIndex = 3; + else if (IsKeyPressed(KEY_FIVE)) interestIndex = 4; + else if (IsKeyPressed(KEY_SIX)) interestIndex = 5; + + offset[0] = pointsOfInterest[interestIndex][0]; + offset[1] = pointsOfInterest[interestIndex][1]; + zoom = pointsOfInterest[interestIndex][2]; + updateShader = true; + } + + // If "R" is pressed, reset zoom and offset + if (IsKeyPressed(KEY_R)) + { + offset[0] = startingOffset[0]; + offset[1] = startingOffset[1]; + zoom = startingZoom; + updateShader = true; + } + + if (IsKeyPressed(KEY_F1)) showControls = !showControls; // Toggle whether or not to show controls + + // Change number of max iterations with UP and DOWN keys + // WARNING: Increasing the number of max iterations greatly impacts performance + if (IsKeyPressed(KEY_UP)) + { + maxIterationsMultiplier *= 1.4f; + updateShader = true; + } + else if (IsKeyPressed(KEY_DOWN)) + { + maxIterationsMultiplier /= 1.4f; + updateShader = true; + } + + // If either left or right button is pressed, zoom in/out + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) + { + // Change zoom. If Mouse left -> zoom in. Mouse right -> zoom out + zoom *= IsMouseButtonDown(MOUSE_BUTTON_LEFT)? zoomSpeed : (1.0f/zoomSpeed); + + const Vector2 mousePos = GetMousePosition(); + Vector2 offsetVelocity; + // Find the velocity at which to change the camera. Take the distance of the mouse + // From the center of the screen as the direction, and adjust magnitude based on the current zoom + offsetVelocity.x = (mousePos.x/(float)screenWidth - 0.5f)*offsetSpeedMul/zoom; + offsetVelocity.y = (mousePos.y/(float)screenHeight - 0.5f)*offsetSpeedMul/zoom; + + // Apply move velocity to camera + offset[0] += GetFrameTime()*offsetVelocity.x; + offset[1] += GetFrameTime()*offsetVelocity.y; + + updateShader = true; + } + + // In case a parameter has been changed, update the shader values + if (updateShader) + { + // As we zoom in, increase the number of max iterations to get more detail + // Aproximate formula, but it works-ish + maxIterations = (int)(sqrtf(2.0f*sqrtf(fabsf(1.0f - sqrtf(37.5f*zoom))))*maxIterationsMultiplier); + + // Update the shader uniform values! + SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2); + SetShaderValue(shader, maxIterationsLoc, &maxIterations, SHADER_UNIFORM_INT); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + // Using a render texture to draw Mandelbrot set + BeginTextureMode(target); // Enable drawing to texture + ClearBackground(BLACK); // Clear the render texture + + // Draw a rectangle in shader mode to be used as shader canvas + // NOTE: Rectangle uses font white character texture coordinates, + // So shader can not be applied here directly because input vertexTexCoord + // Do not represent full screen coordinates (space where want to apply shader) + DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), BLACK); + EndTextureMode(); + + BeginDrawing(); + ClearBackground(BLACK); // Clear screen background + + // Draw the saved texture and rendered mandelbrot set with shader + // NOTE: We do not invert texture on Y, already considered inside shader + BeginShaderMode(shader); + // WARNING: If FLAG_WINDOW_HIGHDPI is enabled, HighDPI monitor scaling should be considered + // When rendering the RenderTexture2D to fit in the HighDPI scaled Window + DrawTextureEx(target.texture, (Vector2){ 0.0f, 0.0f }, 0.0f, 1.0f, WHITE); + EndShaderMode(); + + if (showControls) + { + DrawText("Press Mouse buttons right/left to zoom in/out and move", 10, 15, 10, RAYWHITE); + DrawText("Press F1 to toggle these controls", 10, 30, 10, RAYWHITE); + DrawText("Press [1 - 6] to change point of interest", 10, 45, 10, RAYWHITE); + DrawText("Press UP | DOWN to change number of iterations", 10, 60, 10, RAYWHITE); + DrawText("Press R to recenter the camera", 10, 75, 10, RAYWHITE); + } + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shader); // Unload shader + UnloadRenderTexture(target); // Unload render texture + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shaders/shaders_mandelbrot_set.png b/examples/shaders/shaders_mandelbrot_set.png new file mode 100644 index 0000000000000000000000000000000000000000..6d112931d3a3b23a6cae48f6319b495164280359 GIT binary patch literal 71902 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 z%0A7@2RN=~7ep@XSh%;!`N9Ryhlj+NTfbfAd?}zfxAToUf6Nw@Zzmd!7-npH!{%*! zFzwmK8hx9B|0YYmxU6kG{5eE5MWpWcp)C9Do)doS&CY)MmUqkAU*8V?>=a2|y8O%M zDH7@3+0YS>)l%Dn_AB1(mKpu2R2vzU|8RaQn`h z18`jvmyJ9AWJLQ@mi+op&OwK| zp38pTJR`!OV(PCm%NZ2Dr^VWC*DqUhW3qW5>vZ?l&aR!#j0s^uAMgHkzr1O>f$6b= zJ9B1E^$q{Mq;q-eqRqsg)vVFmJnYU^xE3|0rl9$PR~HqbV7@j_-s z?wjE!_Brv13GK?5IQ#0&urTQwonOl`3|^JzoL&IF7Modr!eHgI5+jPO&5Gyd0E8~QgYV45ix1K)$db@|y#HNHo9ZY@3BlwT&%=No%$ zk9}d`-Px>lV#40BsoIvi7>|3(s9z1;cJo6}9@CzH8cPk2lFq$$JHuq<)xI8X~p(8%#a_esMeZR*C1&ew}wI;@OfJ zryfm9IoMS#z4=fO!-KpKwFj&+^L*dD^a~W*wSl#7@^v;YyP4KcBe*2Gl9_zppPA>M zx05|Tzq$LEeqDIgHS_-`7R211(B(AkJ!{qaTgnp~58VEib>&)B-4}tXZJ8f3uIXPf z`ekveUN-p0s$-U-8z$7YK3UYz8gzH!)K-Jl$Dqt`x%Zw84YlIy7Nu>tA6wT1%|b^{w#h~JIC$Am)Ad? z-Dkf`|G{{G`_J0M+P^Ov*4O6C6fCH+77Ciq?8E*%fzj*mD?6viNZzZPi#J=DX|GK=9fd(YfpbX860@V*R>7yENh{qN-o;`{mZ z{Ii;Khc)kiul)AZ{!MUA>L)9&XM3MKO}CNoP{};)^z{@|=aC~?DtEZPY}>-+aA79U;LaVz{uXT8PWTNBpL3w*=%)K`3GD|b%q-PbDaWe@cCTNPcLpK#pX(A@LM zkt@mWPV;7ml{}Q-n|{n+HYz2xc&&VUL2=}fHLnDXnKr+d-xN4yYSrTl?9ZRG%?R@P zdhqx^3!!(hf$4q|kN>OrvAW>5{?g0yYHBwwSZIE*%lFi&`syt|7C+@&ZdT94FyZ&R zh0T#Z>r*SNWLN#&Wbaw?N5vuWTDyCBQAb zdkv9iQ+6Bo9Ayx54xbTV@_g@{uG5=E8Pga}?B_1|_G`gK{%p;g%{rWW433@8o#bJ^ z*+P5Hq8yRbrL!-X2H)KDb4li`jeDMUZP-_*eCo9Lp9KwT=fAhwxW?e?)t&W~k54ST z-v7t`C(rAdTjsNVTk4e>bnl%FidhFIZ`Tu?QyDL*Q&N_UzlG*>`>#isH zcf)30E@L?G#(Bc5nwa^vk_;9$(hM7RhL!iV9yQ(h*|c##2JA`EjR@Un zzMg?M!lyZ%eK!9#-VM9%oOElrI{5_`qnOa4HTOCq&!w%YYIwSwfcskNr>{6D+Ja8r53N1 zIrBg-NUBA>#M|lZ^N(A@xF@f9FDD}$xB2p-@8*I`-JPjsJ7@Zqilj0#>^f@d8u_BN zy@y3u@r1{5$+#(=s{7LWweHQG`0l*FN*90YiU-bX;-*XcXw3TF$JlWp_uBR}8=E!j z*NIl|dVXvN@2Rc6f5o;P5pHfPT=iS;Z{5)zH^$Yuyo}P5#12Q;71ihOOmZl3NL)BA z?O>(1$ooCDS0>&mgUxfa8mzvZ9JaV8l|N!j?NpP>q)JOZhVypkwND9t__=2H&7~!| ztPCH(YkNh+H*m{MVRmTPuT=8t`JPFs(aUB{GMc<%{`qHj>phqon@d9ZH^11CQm~-@ z|IFyhZ5L*5OLcx2?fr2_f8tr`r6*fkqm(_3qz_wIU7mI~A)4v(H+OE9J&uPho}ahb z%5c|7P|mz`VY=q0pECJ6&Y+mZ# zf0u>fP)34N>06i8%RE-cEw~W9PGbLqX=@+tRAxW5>dMrg{O@0I%5GXeVHbD8#<^P- zc5K{rb7!lSgqii5ZyskZNt?V)*I|!-k#bkFLt5^g{KB&ytf`)kd!_%(Fki>u@bhS- z$D=IvN+s z8V8-f#k%1&&ysxtZJd6y&#K9}HrmD-)`>QUeG_zGxFNK7_l{cq*h@^a{NlAkq+WEq z+Vx8RzJ{;aq@};V#cx@E`ohB!*0R{CJS=*LRK5f%_plx6m>N4XCeTs9ttoAH)$=&^ zTj3q+1Y%41*|}#%oH*r_7xw<+##x3NkIV?KnBRD>XnyADyeBgA9i?sdFT1zp`=X5X zOu}}eTNb_lXtXlrXKIeO&BBY1Oe7`dt($ozL`@*K<;L#_;p2VWJkMtN<}E*$V)VY9 zg_VEq_tJ+O%k|&WSxcUlNL|=_3z5V z>`00r=4G;h4ADhtS)VD+DtNP8WXSB~b zuU=N8W920wB=Y9Dx@GmhqKtx!g_}OOl}TBs+jYFrW{J4V2^+erHdw8gY}jC$b%Wcp zIQ91Sw989`z-xDJTW*(1TXy;Vjq}RVUzHddel0wsZlrH-`{vEh(>=WpPl%XRpIH>V z{4J+*%5?7g8pR#+);Z<*NAXH_v*~oUoc4_=SrIgOgQ?!L$KU4`E@2DF@%dO29}s(I zz0!dN>@wz;D|2`f3`?#)>N(x!wxHIk2pTlFbJ>7?wud7=}) z{9Mj@|IJqW&{M0ps#RhY54@UK(#pWinJA{DyWDu1?Ta5LnHd*6wB0So|MMiXu-mb~ zDXKgAJLaukKfyF_=@zcdZwwDz>^EX7D(#!x z7jpCmYs`;Judm+S^^>0`^Q5RK%U%5r&9uP&b+hJ|+~i>3JnA`F{gFlGzfY`H@8VWp zIQ8+@d|uCPMjiXg2Uj{dkNd^xgikk_V{>D2(QUz4(7J?H3GS88lc!CV;GQ*a=6xQa zpC_4}o1GaKc&%L(GW}x8g=e?Cc@0-X1v zdENdtykPjx@%9&g(SpqDU6I9~dD8!XOiSo?oKUqS)!>z@?*{R?%~S1uE_3?&PfPAY z-udq<7MJb%IJauaBNqF!RY$|VpRYMw{C=9a{j8G_`Ogx9?boind+E)@Qn9G3*8Ul~ zRkfQuylS>Q%m|+9YW?kzYf0-96-9RLlaBe!vZtn9O}?|{TSVkp@w|c@UVqN1PBeh+&5H!1ypOMG?d zx&5Ei_O6bfJ2NuLUiQ+xeOXt&)b50>1@H)e>#m*d;D7k@k%QALG%Px-#AHMvV!5#sY0by|JQGX5=}Gspksu2%~z#KeXF z6-NDVf49cZ+U?q3ZXJF8_46uZ&EA|jd+~y)W%Qa8x0GTnZ*R)F+`D1~a{k|Dj=bb5Mf4#@Nl)IcQf9s31i<;l~*7cq) z?*3VSI$}Q0mQZ(oP2HWpSFSYX_dPOgsp)Ls;%d+At9$y7 z`Y1m6Yg#UFnfKlWB>%(~s#8MMB||I}+gk=jjH zF9a1_kSuu`vd*cb^~&BWo?yR-b0mgkU-k7o?fI75I;QV%Sa6uw;fQaxDH&Us7#bVO zOK$x7|9bV`eG>onJlvE}?WZo^!Munk;3!M@bpEOLlCE~;zN;!vuE;HVvectAaO0-9 z*T=#_cfY92cxnanj3>l1mRSnNvJQ9Le`~OM?pKeu`N#ZJb!Q359MrL0SiEG4b4Ec% zMEw3b=hL}iC;Y@e{(Q4_PU-q(^0M#Bul+qG*|5ORPJr`#-O;Ds&foXPPBh?S_*UJ= zeNfNMZ+-KeUn&nnb`{lbDcV{n#9O=9Wp4QTrI%+2uFO8pXHv6;adXV`o4Y2zudH%9 zy=P8)WS@ZZ;xh*eINvgdt_-bnEU-U(Ud{gP+r!+QO5IFqyx*;N->JWo|C}W|_o%qb zajVY)JvXfH@I+nxJSod`ebm|6=kNd8z9qzer)FW;x9-Qux1E=+wW_)M$mez6bcvvH zcR{gRcRgMR@+OAOeqSS-p=%+!?bexyZ%RFF&g`2{oi5y_8t^K8Mp((h`FZb)eoR_A zui(4nr~h}Z)!OGJrN8?xe{S~!T>dDMx>SSPa~4Cx%C@6V z5-cxmzFD~IRh*%tOSnhb^1S78OjDX$pL}6tO$?iz&w00X%dPoSuRhwlXR*iXxcG-= z9R@!978Ld~g-F7zL z#7U*cx3lhA^x=0Ui|4EmDc`BPgy!Wufpwbmz@8HqAc{teziyQdzz9 z*}Z#By^$ebx-%Z^tJ@zhFR6OC<1sXe&eIfU4fUnlusVbw)(nq>!W8I zoh!Aszc~0{9^>{&XO~(1s;&CWawAm(z3(X`~r+OaEo6&7hP$y>5c^KyfaPD^CQ zh0N!=OQLr~&P`5rOczbb(%7}+_vTdYy{4}pCjC3TVXg2!lhaaD^QQU7#FR6wzgWL| z&euyCUO z?A)BTH%jx*6#cdKN>2*Qw!bvbVWE?nv~BF>mFEK)3@T-1Q>xONvikoreY|EX`%n7h zk2{Ui-u|^%FaO^t_slB6_Wwn)U1Bvx->+`{XnbS0Th;z8A!<_-oD|hW{ARzDH#W#| zNGZAh;KR1R-}siSK4-}u^C8M%aZG;LbVf0wjT0CmBW1m!*7ZluJsh$2C=Qey79e`-NrL-56llM{4BB4g6mSPJ%Gn=>J!VSe4 zTeCk(Rfc=4W0Q_G=$;@r%dg|hlN5)@NLiKZ{^yGhTDI{TZ=4~srY)F*kwLi2KoPRn zu*?a#c3pp-n`!!gza)5W`tJ?-(WOGeY*6nJMs#VLg zCojuh|E@DW&wJaot6ZPYs%>B8CHz07^V7nG8v?uo#X#eWZ;!5zTsoyn@{Q^uv3Pr_ z)6X|^E?yWE>vlPGby@6oiz6NDCd9}X@49r5DJ4nK^j*P%&5M#-?&uWUIJGK0=^~$4 zZv4d((>$M=gUv;UGM0!h=uY_g({q|^L4Eb+B&9j)7K?B_7n?IHtSickJ9DgKP1u#=pF;fC+im!KEnD}O`+w;_}#xC2T#Gzop|1C5Ban08to6QBQ6`qbe#Ezj7Le~z!~%xu;%Fvyi3tz@Zb{TY^g^mQDUv}^@-_`OJ@*L)Qn%Md?477{45WM6wzf3vRIG?fis_FbQ|Z`EP3 zqjmEtA1GcN=E$$y)Af=xEw|GUhV zcsb!Du9-R}AzDJsDxZ=V2^DfSJP1Wj<+( zPv*F!d$hmx=g!1FyKAIl4uSKlWlH{n$xD>4X5U@&taeRXqm!W8OL3Xd zm3qy`=gwxW%dD=`Qv|rk8dLCMYT%bDDAWwZUrM zp07{V_j4y*yt?60TYI%;PSb|T)4%mCdH(L^dM@Fl4;7~r87??FyEe>hmXy<&q;yB$ zY_nwLR?ebp9-YtL`W`Di|7X@8i`9Q8ma{UnEt}2L)$+(Z{HM$v*6l@kH^L7%fqFEI zVaq#P%h-d;cjW}^)KR~)CC2)}qbZ%4XO&xRTQ6iJDHQx-W$5RUm-yOpv4OL7-^br2 zT4EXQeRYwZ)!t{g890<3B{&adm`qwSQN{C;OQ0D8!^^3MmkMTchna1b%wDH2y7tGm zRSE{0>AM{cB;U*T2sd@B-8=o@ivQt7sh47A*0SojK*pMu#T-3Y^=CuJA5InaUfz;b zC3~u7&%WBze>Ew$d{@okJ(V9B3O+hnGK8!Q?R9(i;?RNwP3wOlqyP5P!pewt;?D*Zq zOSY`qvgb&Wp>$8x=A%dU?2wUTh|o#XYd`(8V5dw}PPFdq^XZ%_w-dMeGKB=;bBO4?SWq7FYthrFQ zUGlOpBg65NTBS=5uE?Juc=dy9W5e<1#S8~*Dgw3igU-x;^7(PJ{hq`B{pJenzvHI= zdH!n8^#>yB&T^+5Jn~q6x(FYi0BdSt#kSikYtPB3y?^@D{+7+|sm2daS4H1ue!<1- z;&E48@y3e@3SSwHISD$e-@P4UoZ)f*rL|Vl3iU0%N4YZfHu8(V;*^!uHWBz?U;d7j zfq&tSix#qTx$_NPz5I89Yv!5r{z4*YYr{V8*t6^Qm34tvU%9Y)hH7;^DvsAYcR#y? zxnaSEUVHJDbKQc{rw*5Bg;j2pZ}3&&RsZtNO4d)Jx8-6&Mna4!Ytz{ap|`(Tf}+o( z@ATH94H*l4be5RSv*g%3M>+i5*;g88ifZB>K7KlXPqO{-4T{N~Esq!{UhE7$Q66%C zeeb2tuIn>77#UuK=yYxR_)|x}l~>8W+s@!~H|J%;ZBVeP?y4$Tn%}eZR(`zVE~kd8ynHRMT@T!eHMoB$#c`*pN3KJ^6ZcMrcsK9~_Xl5jymrmCUlAX+Og*~d z;;u-B22R&|MMt?Ot1kP0|K83&+X`a~fAqQV9o?zo>oq0CyKm}^X>T>^%u6rX%vzB7 zO5(&K5A(>9>3UsEth=(zV@s!o{%=kTHMy(eA*jIFB*rVF?l9M)SIK&t>h(#Qog(+o zOWL(<+r8sjg-_R&j#<`oB>&kJC$J!sO?rme_d&5=6 zy1OYMmsiYVgZJ^KG~QWhGdE3rxUKK1=jL1UbIRAWMTJj!Rb_Mho3@HeKyMtg9a*=b`p2m$vU6Yaz78rqw@JHc zAJ4a06LYM@%zOonCTOrclkqqqQFM5o=i$4{xpzP3@@y|rys4GEVkvjzx(P2XwYCUo zT~uyQe|9dEL-DY3q+-pNB4sH#JGXydZ~o6y|6X}zB zOP^MbkEwYR@Oi7beBAW#ZLe?e?LJh)w7V&R@xpvd!OKefO)qVmwY9smx?0@uMMVD_ zwjEoOHE+fw>RyZJHhp#Uqja#nj)>HB{ZPA~yRU32-SXGo{Dvq)pP&xQMg}$ajA^@N zyWg_ymGi%zm9P2PQJ?W~x0RDX7z=Aof7|_JNB>-=EnU zuCaQ%McDUui%I2>cic1zvNm;6-JvBsa=BXu{cQPqHIkNJi@N&0i|Efd#cNBd3 zvGBOOwS%f>^~ZZ!8!cMbCxkOFJt%zS-eb$v$XcxJ6d7ZBns4D6xknMImfPQ&wX#_j z7wO4}om_d}{okiDJ72xpjVFF>3zvKAa;hTh-IuklmfOUZPulV$bMuEd)7joOrsjX& zci+!qx~|%nx-@p@;+UO*iz_{Ynqu(EIE&j2$y=sl#)Ck@Cm;T)p z+L!e6VANVm8xx@k@%ydj*XJDDzh{oN$md&q=XkfClUt+NtTZ`k<(yM0p<5-FEPUPV zy=l>p+JhfAZp;b|z1nvEwQ`e!z`}LczL-Yb^yP0b6j4dXf)@Hid0r? z*eUJR z>crza{nDLI2~X#A_b95r3HY<`a!lfKm&C|(?6`7C^YFufjQjfab#L$9Z*yvt-@oVov9BT9X5@*loHS?sYSX{Y)wSEc_c@%{yZi7a zrSK_1%O)u7nk~8e3A?Y(-lEIr&Sq4my1yrB%@5h7v8D&>h1(w!oyC}bT zo_~B=T;02+{Qq~R{W;$fTWYnQ@y#h08JUB(_Vl^9Fn)QSBz{pRPrdZMQ>3+4#e2Df zN%0#LEF7zAMdjY3!T<>MPV?P%?I!P=e+Ns-d(|{ZI9nuyCvMc>Rh{XKCD^FQ5!e^ z#aCY!^;4{c5A6?!EQ*iasmgKa=yVkGo+on&XM}@z^{O5k*x0>HNV{QubXu~e4f3(-H+&)u#~l_hi_z9OuV_sXm8G% zt4_CPO?mxkyTjUq@B>vXwt7+g6Es-LEJYSZ)@%sc%)=GteLQgOMUCCv1`dZEHBBVv zR((40=Ic?_YXMVo#L~8^u$M0hW79s65WYdXowxVRyoH6HT@xBz6gQk+*ioXmzWC0r zl7$Cd5|_U%{G#d9$YFQUUv<~Cj*Vvvv}Q}+lGw6((wyX*L2naX*803GXWh-TVf8(U zLmwCpWC|U(IoG^s&eoih=BrCOy&BW!&Gk#Y^k)9szw4}ShI*dc=pKJ>=Tv#qH(WdN zf4cFCG2BTnPT{G{n6^33V9!>K(_T+6K3y_9_NzkZGp~ieuW*&I>KtpW-5_v;X~s47 zBO7%jcgNLrhczsITs5q@Iy-i%R^$!QH(cIkEZod&3nCBroZWQw_r?465)+*K z=i51}zWnv*($DboK5omu*xyVFx1An6b4A!Xon41K+mG(jU%`rhFFX+x!1s`AMHS0-yfbYpvh^&FRzM9br%IC57AW(H5^>75QB1 z9Nz`~n5*pBtGzvSk`Ej^u5NNVXGhq+{Ff0?@;cMxc`kZ1wDHSZxT(%Mr4p$fm=>b5 ztn|^1m95gB&BLE{HvY|B6R3Sd^u=w7omSVM6#jj1aAU(+afXa(FBcu~+`O$PdFqon z&mzvOyYZ~~v-!F$E~~VP7AlD|1$C54zCF0k=eySSRiKK1^}OwU;uvpLYdyEuU7N6>bBHfrRjw!pg1y^Y4jYnYP(`J&Z^@?7aC_ zbA(Ql=#q(x`{#QtN!D?#`1rd2X8GiWuu@$0@55P(bbQl&ymX+yTdbsaUtOgUv-rY?J2RAY_ zBs*?CQ=oG%4wTHaImBeR88WUhWQrlFNISUk%tf8-JzQn13E_WPqn|Q7*zlBPR_db4 z8LK08pB4MIKjI&ZKTbp7Ju2YBryV^i^h3FNovev+6 zkb6zIEqph#xvT|cE%#+)AVXVH%$Q*}Q-n!nGe_8S?%E?84=BhypTWp*1rfLmS*!`+-`F*sWULqz z@;MU18N8RARA@?l@JOyu#~^^+<{1maR;CS6`!l~WFfed Q1_lNOPgg&ebxsLQ0Iw_X6#xJL literal 0 HcmV?d00001 diff --git a/projects/VS2022/examples/shaders_mandelbrot_set.vcxproj b/projects/VS2022/examples/shaders_mandelbrot_set.vcxproj new file mode 100644 index 000000000..4ed5581e1 --- /dev/null +++ b/projects/VS2022/examples/shaders_mandelbrot_set.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} + Win32Proj + shaders_mandelbrot_set + 10.0 + shaders_mandelbrot_set + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + 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 + + + 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)\ + + + 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)\ + + + 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\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + 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) + /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 + 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)\ + + + + + 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 + + + + + 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 d0a886a4f..ef46f8fe1 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -393,6 +393,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_screen_recording", "ex EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "examples\core_text_file_loading.vcxproj", "{2F578155-D51F-4C03-AB7F-5C5122CA46CC}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_mandelbrot_set", "examples\shaders_mandelbrot_set.vcxproj", "{1C829D1A-892C-451C-AF0B-AC65C85F5CC6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 From de8575b16e9042d2a0d7f291bb6e696098f7c54b Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2025 19:54:35 +0200 Subject: [PATCH 03/25] Update raylib.sln --- projects/VS2022/raylib.sln | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index ef46f8fe1..ec35ed5e5 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -4873,6 +4873,30 @@ Global {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x64.Build.0 = Release|x64 {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.ActiveCfg = Release|Win32 {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.Build.0 = Release|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|ARM64.Build.0 = Debug|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x64.ActiveCfg = Debug|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x64.Build.0 = Debug|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x86.ActiveCfg = Debug|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x86.Build.0 = Debug|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|ARM64.ActiveCfg = Release|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|ARM64.Build.0 = Release|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x64.ActiveCfg = Release|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x64.Build.0 = Release|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x86.ActiveCfg = Release|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5070,6 +5094,8 @@ Global {EA4AD5A7-DB95-43C0-9A67-2D94146BCF91} = {278D8859-20B1-428F-8448-064F46E1F021} {1ACC8236-EF4E-44B0-BD0C-AB1D95D5890F} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {9DE2FC01-A839-4F89-8319-9071D4C54821} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {2F578155-D51F-4C03-AB7F-5C5122CA46CC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From 7ae3eb5d3a25f865c2947bf4b6a0ed2dc101ab4d Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 11:10:08 +0200 Subject: [PATCH 04/25] Create web_basic_window.png --- examples/others/web_basic_window.png | Bin 0 -> 10297 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/others/web_basic_window.png diff --git a/examples/others/web_basic_window.png b/examples/others/web_basic_window.png new file mode 100644 index 0000000000000000000000000000000000000000..346184417a436343a769ba38a9ad9cfda0195181 GIT binary patch literal 10297 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 rcxALa7%dM*%Y)JKz+i&p5A#a1(8!d Date: Mon, 20 Oct 2025 11:13:45 +0200 Subject: [PATCH 05/25] REXM: Update examples collection --- examples/Makefile | 1 + examples/Makefile.Web | 5 +++++ examples/README.md | 10 ++++++---- examples/examples_list.txt | 2 ++ tools/rexm/examples_report.md | 4 +++- tools/rexm/examples_report_issues.md | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index a9732679d..ef7cb5948 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -664,6 +664,7 @@ SHADERS = \ shaders/shaders_hybrid_rendering \ shaders/shaders_julia_set \ shaders/shaders_lightmap_rendering \ + shaders/shaders_mandelbrot_set \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 88843af09..5d4f3ffab 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -664,6 +664,7 @@ SHADERS = \ shaders/shaders_hybrid_rendering \ shaders/shaders_julia_set \ shaders/shaders_lightmap_rendering \ + shaders/shaders_mandelbrot_set \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ @@ -1307,6 +1308,10 @@ shaders/shaders_lightmap_rendering: shaders/shaders_lightmap_rendering.c --preload-file shaders/resources/cubicmap_atlas.png@resources/cubicmap_atlas.png \ --preload-file shaders/resources/spark_flame.png@resources/spark_flame.png +shaders/shaders_mandelbrot_set: shaders/shaders_mandelbrot_set.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file shaders/resources/shaders/glsl100/mandelbrot_set.fs@resources/shaders/glsl100/mandelbrot_set.fs + shaders/shaders_mesh_instancing: shaders/shaders_mesh_instancing.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/shaders/glsl100/lighting_instancing.vs@resources/shaders/glsl100/lighting_instancing.vs \ diff --git a/examples/README.md b/examples/README.md index b40966a5a..19d110344 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,9 +17,9 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 185] +## EXAMPLES COLLECTION [TOTAL: 187] -### category: core [44] +### category: core [45] Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality. @@ -69,6 +69,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | +| [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | ### category: shapes [31] @@ -104,7 +105,7 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_pie_chart](shapes/shapes_pie_chart.c) | shapes_pie_chart | ⭐⭐⭐☆ | 5.5 | 5.6 | [Gideon Serfontein](https://github.com/GideonSerf) | | [shapes_kaleidoscope](shapes/shapes_kaleidoscope.c) | shapes_kaleidoscope | ⭐⭐☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [shapes_clock_of_clocks](shapes/shapes_clock_of_clocks.c) | shapes_clock_of_clocks | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | -| [shapes_mouse_trail](shapes/shapes_mouse_trail.c) | shapes_mouse_trail | ⭐☆☆☆ | 5.6 | 5.6-dev | [[Balamurugan R]](https://github.com/[Bala050814]) | +| [shapes_mouse_trail](shapes/shapes_mouse_trail.c) | shapes_mouse_trail | ⭐☆☆☆ | 5.6 | 5.6-dev | [Balamurugan R](https://github.com/Bala050814) | | [shapes_simple_particles](shapes/shapes_simple_particles.c) | shapes_simple_particles | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | | [shapes_starfield_effect](shapes/shapes_starfield_effect.c) | shapes_starfield_effect | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | @@ -195,7 +196,7 @@ Examples using raylib models functionality, including models loading/generation | [models_basic_voxel](models/models_basic_voxel.c) | models_basic_voxel | ⭐⭐☆☆ | 5.5 | 5.5 | [Tim Little](https://github.com/timlittle) | | [models_rotating_cube](models/models_rotating_cube.c) | models_rotating_cube | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Jopestpe](https://github.com/jopestpe) | -### category: shaders [30] +### category: shaders [31] Examples using raylib shaders functionality, including shaders loading, parameters configuration and drawing using them (model shaders and postprocessing shaders). This functionality is directly provided by raylib [rlgl](../src/rlgl.c) module. @@ -231,6 +232,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_lightmap_rendering](shaders/shaders_lightmap_rendering.c) | shaders_lightmap_rendering | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐⭐⭐☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | [shaders_depth_rendering](shaders/shaders_depth_rendering.c) | shaders_depth_rendering | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | +| [shaders_mandelbrot_set](shaders/shaders_mandelbrot_set.c) | shaders_mandelbrot_set | ⭐⭐⭐☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | ### category: audio [8] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 9989f940f..2a1b9c0b7 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -192,3 +192,5 @@ others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flas others;raylib_opengl_interop;★★★★;3.8;4.0;2021;2025;"Stephan Soller";@arkanis others;embedded_files_loading;★★☆☆;3.0;3.5;2020;2025;"Kristian Holmgren";@defutura others;web_basic_window;★☆☆☆;5.6-dev;5.6-dev;2014;2025;"Ramon Santamaria";@raysan5 +core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 +shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md index e058e09a7..fcc1f8839 100644 --- a/tools/rexm/examples_report.md +++ b/tools/rexm/examples_report.md @@ -204,4 +204,6 @@ Example elements validated: | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_mandelbrot_set | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/examples_report_issues.md b/tools/rexm/examples_report_issues.md index 3136c2709..5de76a17a 100644 --- a/tools/rexm/examples_report_issues.md +++ b/tools/rexm/examples_report_issues.md @@ -27,4 +27,4 @@ Example elements validated: | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 8604085b6ea6e6fb2345742a8a45c43534333267 Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:18:15 +0200 Subject: [PATCH 06/25] [examples] Fixed `shaders_mandelbrot_set` for WebGL (#5286) --- .../shaders/glsl100/mandelbrot_set.fs | 35 ++++++++++--------- examples/shaders/shaders_mandelbrot_set.c | 5 +++ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs index aab8514e8..fb6dee8b3 100644 --- a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs @@ -17,6 +17,9 @@ uniform int maxIterations; // Max iterations per pixel const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity const float max2 = max*max; // Square of max to avoid computing square root +// WebGL shaders for loop iteration limit only const +const int maxIterationsLimit = 20000; + void main() { // The pixel coordinates are scaled so they are on the mandelbrot scale @@ -31,31 +34,29 @@ void main() // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i - int iter = 0; - while (iter < maxIterations) + for (int iter = 0; iter < maxIterationsLimit; ++iter) { float aa = a*a; float bb = b*b; + if (iter >= maxIterations) + { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + return; + } if (aa + bb > max2) - break; + { + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; + + gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + return; + } float twoab = 2.0*a*b; a = aa - bb + c.x; b = twoab + c.y; - - ++iter; } - if (iter >= maxIterations) - { - gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); - } - else - { - float normR = float(iter - (iter/55)*55)/55.0; - float normG = float(iter - (iter/69)*69)/69.0; - float normB = float(iter - (iter/40)*40)/40.0; - - gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); - } + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); } diff --git a/examples/shaders/shaders_mandelbrot_set.c b/examples/shaders/shaders_mandelbrot_set.c index c8d0ce8d3..a373c518a 100644 --- a/examples/shaders/shaders_mandelbrot_set.c +++ b/examples/shaders/shaders_mandelbrot_set.c @@ -70,8 +70,13 @@ int main(void) float zoom = startingZoom; // Depending on the zoom the mximum number of iterations must be adapted to get more detail as we zzoom in // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys +#if defined(PLATFORM_DESKTOP) int maxIterations = 333; float maxIterationsMultiplier = 166.5f; +#else + int maxIterations = 43; + float maxIterationsMultiplier = 22.0f; +#endif // Get variable (uniform) locations on the shader to connect with the program // NOTE: If uniform variable could not be found in the shader, function returns -1 From 74f2a899d945ec9014851ca75dbf1394687058f8 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 19:09:37 +0200 Subject: [PATCH 07/25] Update rshapes.c --- src/rshapes.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rshapes.c b/src/rshapes.c index 55f7d7fb9..2b5854f86 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -1432,6 +1432,7 @@ void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) Rectangle shapeRect = GetShapesTextureRectangle(); rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); rlColor4ub(color.r, color.g, color.b, color.a); rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); @@ -1441,7 +1442,7 @@ void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) rlVertex2f(v2.x, v2.y); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); - rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(v3.x, v3.y); From ec3cb7045f710b202eb9ea15f64ca8fa9c779136 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 19:09:56 +0200 Subject: [PATCH 08/25] Update rcore.c --- src/rcore.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index f87b68783..5dea97c22 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -679,12 +679,15 @@ void InitWindow(int width, int height, const char *title) // Initialize window data CORE.Window.screen.width = width; CORE.Window.screen.height = height; + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; + CORE.Window.eventWaiting = false; - CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default + CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; // Initialize global input state - memset(&CORE.Input, 0, sizeof(CORE.Input)); // Reset CORE.Input structure to 0 + memset(&CORE.Input, 0, sizeof(CORE.Input)); // Reset CORE.Input structure to 0 CORE.Input.Keyboard.exitKey = KEY_ESCAPE; CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; @@ -696,7 +699,7 @@ void InitWindow(int width, int height, const char *title) if (result != 0) { - TRACELOG(LOG_WARNING, "SYSTEM: Failed to initialize Platform"); + TRACELOG(LOG_WARNING, "SYSTEM: Failed to initialize platform"); return; } //-------------------------------------------------------------- From 166420429106692f2f66d9bec39121ed9617f00f Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 19:10:41 +0200 Subject: [PATCH 09/25] REVIEWED: New Win32 platform backend to accomodate `rlsw` Software Renderer --- src/platforms/rcore_desktop_win32.c | 695 +++++++++++++++++----------- src/rlgl.h | 33 +- 2 files changed, 432 insertions(+), 296 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 3ccd4d3bd..7ac6138bd 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -91,22 +91,24 @@ typedef struct { HWND hwnd; // Window handler HDC hdc; // Graphic context handler HGLRC glContext; // OpenGL context handler + // Software renderer variables HDC hdcmem; // Memory graphic context handler HBITMAP hbitmap; // GDI bitmap handler unsigned int *pixels; // Pointer to pixel data buffer (BGRA format) - LARGE_INTEGER timerFrequency; unsigned int appScreenWidth; unsigned int appScreenHeight; unsigned int desiredFlags; - bool cursorEnabled; + + LARGE_INTEGER timerFrequency; } PlatformData; // Define WGL function pointer types (no wglext.h needed) typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); typedef BOOL (WINAPI *PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC, const int *, const FLOAT *, UINT, int *, UINT *); typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int); +typedef const char *(WINAPI *PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc); //---------------------------------------------------------------------------------- // Global Variables Definition @@ -119,6 +121,7 @@ static PlatformData platform = { 0 }; // Platform specific data static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; +static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; // -------------------------------------------------------------------------------- // This part of the file contains pure functions that never access global state @@ -133,10 +136,10 @@ static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; // Defines and Macros //---------------------------------------------------------------------------------- #define A_TO_W_ALLOCA(outWstr, inAnsi) do { \ - size_t len = AToWLen(inAnsi); \ - outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(len + 1)); \ - AToWCopy(outWstr, len, inAnsi); \ - outWstr[len] = 0; \ + size_t outLen = AToWLen(inAnsi); \ + outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(outLen + 1)); \ + AToWCopy(inAnsi, outWstr, outLen); \ + outWstr[outLen] = 0; \ } while (0) #define STYLE_MASK_ALL 0xffffffff @@ -150,7 +153,7 @@ static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #define WINDOW_STYLE_EX 0 -#define CLASS_NAME L"RaylibWindow" +#define CLASS_NAME L"raylibWindow" #define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT) #define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL) @@ -196,93 +199,86 @@ static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #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_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef enum { MIZED_NONE, MIZED_MIN, MIZED_MAX } Mized; - -typedef enum { - UPDATE_WINDOW_FIRST, - UPDATE_WINDOW_NORMAL, -} UpdateWindowKind; - -typedef enum { - SANITIZE_FLAGS_FIRST, - SANITIZE_FLAGS_NORMAL, -} SanitizeFlagsKind; - -typedef struct { - HMONITOR needle; - int index; - int matchIndex; - RECT rect; -} FindMonitorContext; +// Maximize-minimize request types +typedef enum { + MIZED_NONE, + MIZED_MIN, + MIZED_MAX +} Mized; +// Flag operations +// NOTE: Some ops need to be deferred typedef struct { DWORD set; DWORD clear; } FlagsOp; +// Monitor info type +typedef struct { + HMONITOR needle; + int index; + int matchIndex; + RECT rect; +} MonitorInfo; + //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- -static size_t AToWLen(const char *a) +// Get ASCII to WCHAR length +static size_t AToWLen(const char *ascii) { - int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, a, -1, NULL, 0); + int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0); - if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "Failed to calculate wide length, result=%d, error=%u", sizeNeeded, GetLastError()); + if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError()); return sizeNeeded; } -static void AToWCopy(wchar_t *outPtr, size_t outLen, const char *a) + +// Copy ASCII to WCHAR string +static void AToWCopy(const char *ascii, wchar_t *outPtr, size_t outLen) { - int size = MultiByteToWideChar(CP_UTF8, 0, a, -1, outPtr, (int)outLen); - if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Convert %zu UTF-8 chars to WCHAR but converted %zu", outLen, size); + int size = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, outPtr, (int)outLen); + if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Failed to convert %i UTF-8 chars to WCHAR, converted %i chars", outLen, size); } static bool DecoratedFromStyle(DWORD style) { if (style & STYLE_FLAGS_UNDECORATED_ON) { - if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "FLAGS: Style 0x%x has both undecorated on/off flags", style); + if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x has both undecorated on/off flags", style); return false; // Not decorated } DWORD masked = (style & STYLE_FLAGS_UNDECORATED_OFF); - if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "FLAGS: Style 0x%x is missing these flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF); + if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x is missing flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF); return true; // Decorated } -static Mized MizedFromStyle(DWORD style) -{ - // Minimized takes precedence over maximized - if (style & WS_MINIMIZE) return MIZED_MIN; - if (style & WS_MAXIMIZE) return MIZED_MAX; - return MIZED_NONE; -} - -static Mized MizedFromFlags(unsigned flags) -{ - // minimized takes precedence over maximized - if (FLAG_CHECK(flags, FLAG_WINDOW_MINIMIZED)) return MIZED_MIN; - if (flags & FLAG_WINDOW_MAXIMIZED) return MIZED_MAX; - return MIZED_NONE; -} - +// Get window style from required flags static DWORD MakeWindowStyle(unsigned flags) { - // we don't need this since we don't have any child windows, but I guess + // We don't need this since we don't have any child windows, but I guess // it improves efficiency, plus, windows adds this flag automatically anyway // so it keeps our flags in sync with the OS - DWORD style = WS_CLIPSIBLINGS ; + DWORD style = WS_CLIPSIBLINGS; style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE; style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0; style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF; - switch (MizedFromFlags(flags)) + // Minimized takes precedence over maximized + int mized = MIZED_NONE; + if (FLAG_CHECK(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN; + if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX; + + switch (mized) { case MIZED_NONE: break; case MIZED_MIN: style |= WS_MINIMIZE; break; @@ -293,15 +289,13 @@ static DWORD MakeWindowStyle(unsigned flags) return style; } -// Enforces that the actual window/platform state is in sync with raylib's flags +// Check flags state, enforces that the actual window/platform state is in sync with raylib's flags static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expectedStyle, DWORD styleCheckMask) { - //TRACELOG(LOG_INFO, "Verifying Flags 0x%x Style 0x%x Mask 0x%x", flags, expectedStyle & styleCheckMask, styleCheckMask); - DWORD styleFromFlags = MakeWindowStyle(flags); if ((styleFromFlags & styleCheckMask) != (expectedStyle & styleCheckMask)) { - TRACELOG(LOG_ERROR, "%s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)", + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)", context, flags, styleFromFlags & styleCheckMask, expectedStyle & styleCheckMask, (styleFromFlags & styleCheckMask) ^ (expectedStyle & styleCheckMask), styleCheckMask); } @@ -310,7 +304,7 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect LONG actualStyle = (LONG)GetWindowLongPtrW(hwnd, GWL_STYLE); if ((actualStyle & styleCheckMask) != (expectedStyle & styleCheckMask)) { - TRACELOG(LOG_ERROR, "%s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)", + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)", context, expectedStyle & styleCheckMask, actualStyle & styleCheckMask, (expectedStyle & styleCheckMask) ^ (actualStyle & styleCheckMask), styleCheckMask, GetLastError()); @@ -320,7 +314,7 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect { bool isIconic = IsIconic(hwnd); bool styleMinimized = !!(WS_MINIMIZE & actualStyle); - if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "WIN32: FLAGS: IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); } if (styleCheckMask & WS_MAXIMIZE) @@ -329,49 +323,32 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect placement.length = sizeof(placement); if (!GetWindowPlacement(hwnd, &placement)) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowPlacement", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s failed, error=%lu", "GetWindowPlacement", GetLastError()); } bool placementMaximized = (placement.showCmd == SW_SHOWMAXIMIZED); bool styleMaximized = WS_MAXIMIZE & actualStyle; if (placementMaximized != styleMaximized) { - TRACELOG(LOG_ERROR, "maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d", + TRACELOG(LOG_ERROR, "WIN32: FLAGS: Maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d", placementMaximized, placement.showCmd, styleMaximized); } } } -static SIZE PxFromPt2(float dpiScale, bool highdpiEnabled, int screenWidth, int screenHeight) -{ - // Get size in pixels from points - return (SIZE){ - highdpiEnabled? (int)((float)screenWidth*dpiScale) : screenWidth, - highdpiEnabled? (int)((float)screenHeight*dpiScale) : screenHeight, - }; -} - -static SIZE GetClientSize(HWND hwnd) -{ - RECT rect = { 0 }; - - if (GetClientRect(hwnd, &rect) == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetClientRect", GetLastError()); - - return (SIZE){ rect.right, rect.bottom }; -} - +// Calculate window size (with borders, title-bar...) from desired client size (framebuffer size) static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style) { RECT rect = { 0, 0, clientSize.cx, clientSize.cy }; int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi); - - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "AdjustWindowRect", GetLastError()); + if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError()); return (SIZE){ rect.right - rect.left, rect.bottom - rect.top }; } -// returns true if the window size was updated, false otherwise -static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int height, unsigned flags) +// Update window size if required +// NOTE: Returns true if the window size was updated, false otherwise +static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigned flags) { if (flags & FLAG_WINDOW_MINIMIZED) return false; @@ -386,10 +363,10 @@ static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int he MONITORINFO info = { 0 }; HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError()); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError()); RECT windowRect = { 0 }; - if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowRect", GetLastError()); + if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "WIN32: Failed to get window rect [ERROR: %lu]", GetLastError()); if ((windowRect.left == info.rcMonitor.left) && (windowRect.top == info.rcMonitor.top) && @@ -402,33 +379,45 @@ static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int he info.rcMonitor.bottom - info.rcMonitor.top, SWP_NOOWNERZORDER)) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: Failed to set window position [ERROR: %lu]", GetLastError()); } return true; } + // Get size in pixels from points, considering high-dpi UINT dpi = GetDpiForWindow(hwnd); float dpiScale = ((float)dpi)/96.0f; bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI; - SIZE desired = PxFromPt2(dpiScale, dpiScaling, width, height); - SIZE actual = GetClientSize(hwnd); - if ((actual.cx == desired.cx) || (actual.cy == desired.cy)) return false; + SIZE desiredSize = { + .cx = dpiScaling? (int)((float)width*dpiScale) : width, + .cy = dpiScaling? (int)((float)height*dpiScale) : height + }; - TRACELOG(LOG_INFO, "Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)", - actual.cx, actual.cy, desired.cx, desired.cy, dpi, dpiScaling, width, height); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; - SIZE windowSize = CalcWindowSize(dpi, desired, MakeWindowStyle(flags)); - POINT windowPos = (POINT){ 0, 0 }; + // If client size is alread desired size, no need to update + if ((clientSize.cx == desiredSize.cx) || (clientSize.cy == desiredSize.cy)) return false; + + TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)", + clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height); + + // Calculate window size from desired framebuffer size and window flags + SIZE windowSize = CalcWindowSize(dpi, desiredSize, MakeWindowStyle(flags)); + POINT windowPos = { 0 }; UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED; - if (kind == UPDATE_WINDOW_FIRST) + + if (mode == 0) // UPDATE_WINDOW_FIRST { HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); - if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError()); + if (!monitor) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor from window [ERROR: %lu]", GetLastError()); - MONITORINFO info; + MONITORINFO info = { 0 }; info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError()); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError()); #define MAX(a,b) (((a)>(b))? (a):(b)) @@ -446,15 +435,10 @@ static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int he //AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); //SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER); - // Old code - //if (!SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, windowSize.cx, windowSize.cy, swpFlags)) - //{ - // TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError()); - //} - return true; } +// Verify if we are running in Windows 10 version 1703 (Creators Update) static BOOL IsWindows10Version1703OrGreaterWin32(void) { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); @@ -463,7 +447,7 @@ static BOOL IsWindows10Version1703OrGreaterWin32(void) (DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo"); if (!Verify) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetProcAddress 'RtlVerifyVersionInfo'", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: Failed to verify Windows version [ERROR: %lu]", GetLastError()); return 0; } @@ -472,6 +456,7 @@ static BOOL IsWindows10Version1703OrGreaterWin32(void) osvi.dwMajorVersion = 10; osvi.dwMinorVersion = 0; osvi.dwBuildNumber = 15063; // Build 15063 corresponds to Windows 10 version 1703 (Creators Update) + DWORDLONG cond = 0; VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL); @@ -504,7 +489,8 @@ static void *WglGetProcAddress(const char *procname) return proc; } -static KeyboardKey KeyFromWparam(WPARAM wparam) +// Get key from wparam (mapping) +static KeyboardKey GetKeyFromWparam(WPARAM wparam) { switch (wparam) { @@ -687,6 +673,7 @@ static KeyboardKey KeyFromWparam(WPARAM wparam) } } +// Get cursor name static LPCWSTR GetCursorName(int cursor) { LPCWSTR name = (LPCWSTR)IDC_ARROW; @@ -710,37 +697,46 @@ static LPCWSTR GetCursorName(int cursor) return name; } -static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam) +// Count monitors process +// NOTE: Required by GetMonitorCount() +static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam) { int *count = (int *)lparam; *count += 1; + // Always return TRUE to continue the loop, otherwise, the caller // can't distinguish between stopping the loop and an error return TRUE; } -static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam) +// Find monitor process +// NOTE: Required by GetCurrentMonitor() +static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam) { - FindMonitorContext *c = (FindMonitorContext*)lparam; - if (handle == c->needle) + MonitorInfo *monitor = (MonitorInfo *)lparam; + + if (handle == monitor->needle) { - c->matchIndex = c->index; - c->rect = *rect; + monitor->matchIndex = monitor->index; + monitor->rect = *rect; } - c->index += 1; + monitor->index += 1; + // Always return TRUE to continue the loop, otherwise, the caller // can't distinguish between stopping the loop and an error return TRUE; } -static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsOp *deferredFlags) +// Get style changed required operations flags +// NOTE: Required for deferred operations +static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, FlagsOp *deferredFlags) { // Check window resizable flag change bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE); - bool resizableOld = ((ss->styleOld & STYLE_FLAGS_RESIZABLE) != 0); - bool resizableNew = ((ss->styleNew & STYLE_FLAGS_RESIZABLE) != 0); - if (resizable != resizableOld) TRACELOG(LOG_ERROR, "expected resizable %u but got %u", resizable, resizableOld); + bool resizableOld = ((style->styleOld & STYLE_FLAGS_RESIZABLE) != 0); + bool resizableNew = ((style->styleNew & STYLE_FLAGS_RESIZABLE) != 0); + if (resizable != resizableOld) TRACELOG(LOG_ERROR, "WIN32: Expected resizable %u but got %u", resizable, resizableOld); if (resizableOld != resizableNew) { if (resizableNew) deferredFlags->set |= FLAG_WINDOW_RESIZABLE; @@ -749,9 +745,9 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsO // Check window decorated flag change bool decorated = (0 == (coreWindowFlags & FLAG_WINDOW_UNDECORATED)); - bool decoratedOld = DecoratedFromStyle(ss->styleOld); - bool decoratedNew = DecoratedFromStyle(ss->styleNew); - if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "expected decorated %u but got %u", decorated, decoratedOld); + bool decoratedOld = DecoratedFromStyle(style->styleOld); + bool decoratedNew = DecoratedFromStyle(style->styleNew); + if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "WIN32: Expected decorated %u but got %u", decorated, decoratedOld); if (decoratedOld != decoratedNew) { if (decoratedNew) deferredFlags->clear |= FLAG_WINDOW_UNDECORATED; @@ -760,9 +756,9 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsO // Check window hidden flag change bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN); - bool hiddenOld = ((ss->styleOld & WS_VISIBLE) == 0); - bool hiddenNew = ((ss->styleNew & WS_VISIBLE) == 0); - if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "expected hidden %u but got %u", hidden, hiddenOld); + bool hiddenOld = ((style->styleOld & WS_VISIBLE) == 0); + bool hiddenNew = ((style->styleNew & WS_VISIBLE) == 0); + if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "WIN32: Expected hidden %u but got %u", hidden, hiddenOld); if (hiddenOld != hiddenNew) { if (hiddenNew) deferredFlags->set |= FLAG_WINDOW_HIDDEN; @@ -770,7 +766,9 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsO } } -// Call when the window is rezised, returns true if the new window size should update the desired app size +// Adopt window resize +// NOTE: Call when the window is rezised, returns true +// if the new window size should update the desired app size static bool AdoptWindowResize(unsigned flags) { if (flags & FLAG_WINDOW_MINIMIZED) return false; @@ -805,9 +803,12 @@ static void HandleRawInput(LPARAM lparam); static void HandleWindowResize(HWND hwnd, int *width, int *height); static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags); -static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags); +static unsigned SanitizeFlags(int mode, unsigned flags); static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height); // Update window flags +// Check if OpenGL extension is available +static bool IsWglExtensionAvailable(HDC hdc, const char *extension); + //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -826,20 +827,14 @@ bool WindowShouldClose(void) // Toggle fullscreen mode void ToggleFullscreen(void) { - TRACELOG(LOG_WARNING, "ToggleFullscreen not implemented"); + TRACELOG(LOG_WARNING, "WIN32: Toggle full screen functionality not implemented"); } // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) - { - ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE); - } - else - { - SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE); - } + if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE); + else SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE); } // Set window state: maximized, if resizable @@ -858,40 +853,95 @@ void MinimizeWindow(void) void RestoreWindow(void) { if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) && - (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) - ) { - ClearWindowState(FLAG_WINDOW_MINIMIZED); - } - else - { - ClearWindowState(FLAG_WINDOW_MINIMIZED|FLAG_WINDOW_MAXIMIZED); - } + (CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) ClearWindowState(FLAG_WINDOW_MINIMIZED); + else ClearWindowState(FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED); } // Set window configuration state using flags void SetWindowState(unsigned int flags) { - platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags | flags); + platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags | flags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); } // Clear window configuration state flags void ClearWindowState(unsigned int flags) { - platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags & ~flags); + platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags & ~flags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); } // Set icon for window void SetWindowIcon(Image image) { - TRACELOG(LOG_WARNING, "SetWindowIcon not implemented"); + if (!platform.hwnd || (image.data == NULL) || (image.width <= 0) || (image.height <= 0)) return; + + HDC hdc = GetDC(platform.hwnd); + + // Create 32-bit BGRA DIB for color + BITMAPV5HEADER bi = { 0 }; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = image.width; + bi.bV5Height = -image.height; // Negative = top-down bitmap + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + unsigned char *targetBits = NULL; + HBITMAP hColorBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&targetBits, NULL, 0); + if (!hColorBitmap) + { + ReleaseDC(platform.hwnd, hdc); + return; + } + + // Copy RGBA > BGRA (Win32 expects BGRA) + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + int i = (y*image.width + x)*4; + targetBits[i + 0] = ((unsigned char *)image.data)[i + 2]; // B + targetBits[i + 1] = ((unsigned char *)image.data)[i + 1]; // G + targetBits[i + 2] = ((unsigned char *)image.data)[i + 0]; // R + targetBits[i + 3] = ((unsigned char *)image.data)[i + 3]; // A + } + } + + // Create mask bitmap (1-bit, all opaque) + HBITMAP hMaskBitmap = CreateBitmap(image.width, image.height, 1, 1, NULL); + + // Build icon info + ICONINFO ii = { 0 }; + ZeroMemory(&ii, sizeof(ii)); + ii.fIcon = TRUE; + ii.hbmMask = hMaskBitmap; + ii.hbmColor = hColorBitmap; + + HICON hIcon = CreateIconIndirect(&ii); + + // Clean up GDI bitmaps (icon keeps copies internally) + DeleteObject(hColorBitmap); + DeleteObject(hMaskBitmap); + ReleaseDC(platform.hwnd, hdc); + + if (hIcon) + { + // Set both large and small icons + SendMessage(platform.hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(platform.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + } } // Set icon for window void SetWindowIcons(Image *images, int count) { - TRACELOG(LOG_WARNING, "SetWindowIcons not implemented"); + // TODO. } void SetWindowTitle(const char *title) @@ -902,13 +952,24 @@ void SetWindowTitle(const char *title) A_TO_W_ALLOCA(titleWide, CORE.Window.title); int result = SetWindowTextW(platform.hwnd, titleWide); - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowText", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError()); } // Set window position on screen (windowed mode) void SetWindowPosition(int x, int y) { - TRACELOG(LOG_WARNING, "SetWindowPosition not implemented"); + if (platform.hwnd != NULL) + { + RECT rect = { 0 }; + if (GetWindowRect(platform.hwnd, &rect)) + { + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + // Move the window to the new position (keeping size and z-order) + SetWindowPos(platform.hwnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE); + } + } } // Set monitor for the current window @@ -975,15 +1036,15 @@ int GetCurrentMonitor(void) HMONITOR monitor = MonitorFromWindow(platform.hwnd, MONITOR_DEFAULTTOPRIMARY); if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError()); - FindMonitorContext context; - context.needle = monitor; - context.index = 0; - context.matchIndex = -1; + MonitorInfo info = { 0 }; + info.needle = monitor; + info.index = 0; + info.matchIndex = -1; - int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&context); + int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info); if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); - return context.matchIndex; + return info.matchIndex; } // Get selected monitor position @@ -1091,10 +1152,9 @@ void HideCursor(void) // Enables cursor (unlock cursor) void EnableCursor(void) { - if (platform.cursorEnabled) TRACELOG(LOG_INFO, "EnableCursor: already enabled"); - else + if (CORE.Input.Mouse.cursorLocked) { - if (!ClipCursor(NULL)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClipCursor", GetLastError()); + if (!ClipCursor(NULL)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError()); RAWINPUTDEVICE rid = { 0 }; rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC @@ -1102,18 +1162,17 @@ void EnableCursor(void) rid.dwFlags = RIDEV_REMOVE; // Add to this window even in background rid.hwndTarget = NULL; int result = RegisterRawInputDevices(&rid, 1, sizeof(rid)); - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "RegisterRawInputDevices", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError()); ShowCursor(); - platform.cursorEnabled = true; - TRACELOG(LOG_INFO, "EnableCursor: enabled"); + CORE.Input.Mouse.cursorLocked = false; } } // Disables cursor (lock cursor) void DisableCursor(void) { - if (platform.cursorEnabled) + if (!CORE.Input.Mouse.cursorLocked) { RAWINPUTDEVICE rid = { 0 }; rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC @@ -1121,33 +1180,31 @@ void DisableCursor(void) rid.dwFlags = RIDEV_INPUTSINK; // Add to this window even in background rid.hwndTarget = platform.hwnd; int result = RegisterRawInputDevices(&rid, 1, sizeof(rid)); - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "RegisterRawInputDevices", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError()); RECT clientRect = { 0 }; - if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetClientRect", GetLastError()); + if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client rectangle [ERROR: %lu]", GetLastError()); POINT topleft = { clientRect.left, clientRect.top }; - if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClientToScreen", GetLastError()); + if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client to screen size [ERROR: %lu]", GetLastError()); LONG width = clientRect.right - clientRect.left; LONG height = clientRect.bottom - clientRect.top; - TRACELOG(LOG_INFO, "ClipCursor client %d,%d %d,%d (topleft %d,%d)", + TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)", clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y); LONG centerX = topleft.x + width/2; LONG centerY = topleft.y + height/2; RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 }; - if (!ClipCursor(&clipRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClipCursor", GetLastError()); + if (!ClipCursor(&clipRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError()); CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 }; CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 }; HideCursor(); - platform.cursorEnabled = false; - TRACELOG(LOG_INFO, "DisableCursor: disabled"); + CORE.Input.Mouse.cursorLocked = true; } - else TRACELOG(LOG_INFO, "DisableCursor: already disabled"); } // Swap back buffer with front buffer (screen drawing) @@ -1163,8 +1220,8 @@ void SwapScreenBuffer(void) InvalidateRect(platform.hwnd, NULL, FALSE); UpdateWindow(platform.hwnd); #else - if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SwapBuffers", GetLastError()); - if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ValidateRect", GetLastError()); + if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "WIN32: Failed to swap buffers [ERROR: %lu]", GetLastError()); + if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "WIN32: Failed to validate screen rect [ERROR: %lu]", GetLastError()); #endif } @@ -1216,7 +1273,7 @@ void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float d // Set mouse position XY void SetMousePosition(int x, int y) { - if (platform.cursorEnabled) + if (!CORE.Input.Mouse.cursorLocked) { CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; @@ -1230,7 +1287,7 @@ void SetMouseCursor(int cursor) { LPCWSTR cursorName = GetCursorName(cursor); HCURSOR hcursor = LoadCursorW(NULL, cursorName); - if (!hcursor) TRACELOG(LOG_ERROR, "LoadCursor %d (win32 %d) failed, error=%lu", cursor, (size_t)cursorName, GetLastError()); + if (!hcursor) TRACELOG(LOG_ERROR, "WIN32: Failed to load requested cursor [ERROR: %lu]", GetLastError()); SetCursor(hcursor); CORE.Input.Mouse.cursorHidden = false; @@ -1319,6 +1376,7 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB"); wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); + wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB"); // Setup modern pixel format if extension is available if (wglChoosePixelFormatARB) @@ -1353,10 +1411,52 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) HGLRC realContext = NULL; if (wglCreateContextAttribsARB) { + int glContextVersionMajor = 1; + int glContextVersionMinor = 1; + int glContextProfile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; + + if (rlGetVersion() == RL_OPENGL_21) // Request OpenGL 2.1 context + { + glContextVersionMajor = 2; + glContextVersionMinor = 1; + } + else if (rlGetVersion() == RL_OPENGL_33) // Request OpenGL 3.3 context + { + glContextVersionMajor = 3; + glContextVersionMinor = 3; + } + else if (rlGetVersion() == RL_OPENGL_43) // Request OpenGL 4.3 context + { + glContextVersionMajor = 4; + glContextVersionMinor = 3; + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") || + IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile")) + { + glContextVersionMajor = 2; + glContextVersionMinor = 0; + glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT; + } + else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU"); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context + { + if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") || + IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile")) + { + glContextVersionMajor = 3; + glContextVersionMinor = 0; + glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT; + } + else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU"); + } + int contextAttribs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, 3, - WGL_CONTEXT_MINOR_VERSION_ARB, 3, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported) + WGL_CONTEXT_MAJOR_VERSION_ARB, glContextVersionMajor, + WGL_CONTEXT_MINOR_VERSION_ARB, glContextVersionMinor, + WGL_CONTEXT_PROFILE_MASK_ARB, glContextProfile, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported) //WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB [glDebugMessageCallback()] 0 // Terminator }; @@ -1387,9 +1487,11 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) // Initialize platform: graphics, inputs and more int InitPlatform(void) { - platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_FIRST, CORE.Window.flags); + int result = 0; + platform.appScreenWidth = CORE.Window.screen.width; platform.appScreenHeight = CORE.Window.screen.height; + platform.desiredFlags = SanitizeFlags(0 /*SANITIZE_FLAGS_FIRST*/, CORE.Window.flags); // NOTE: From this point CORE.Window.flags should always reflect the actual state of the window CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE); @@ -1411,37 +1513,56 @@ int InitPlatform(void) } */ + HINSTANCE hInstance = GetModuleHandleW(0); + + // Define window class WNDCLASSEXW windowClass = { .cbSize = sizeof(WNDCLASSEXW), .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, .lpfnWndProc = WndProc, // Custom procedure assigned .cbWndExtra = sizeof(LONG_PTR), // extra space for the Tuple object ptr - .hInstance = GetModuleHandleW(0), + .hInstance = hInstance, .hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), // TODO: Audit if we want to set this since we're implementing WM_SETCURSOR - .lpszClassName = CLASS_NAME //L"GLWindowClass"; + .lpszClassName = CLASS_NAME // Class name: L"raylibWindow" }; - // Register window class - if (RegisterClassExW(&windowClass) == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "RegisterClass", GetLastError()); + // Load user-provided icon if available + // NOTE: raylib resource file defaults to GLFW_ICON id, so looking for same identifier + windowClass.hIcon = LoadImageW(hInstance, L"GLFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); -/* - // TODO: Remove or move this code that sets the display size; should maybe go somewhere in WndProc? + // Register window class + result = (int)RegisterClassExW(&windowClass); + if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to register window class [ERROR: %lu]", GetLastError()); + + // Get primary monitor info POINT primaryTopLeft = { 0 }; HMONITOR monitor = MonitorFromPoint(primaryTopLeft, MONITOR_DEFAULTTOPRIMARY); if (monitor != NULL) { - MONITORINFO info; + MONITORINFO info = { 0 }; info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_WARNING, "%s failed, error: %u", "GetMonitorInfo", GetLastError()); + result = (int)GetMonitorInfoW(monitor, &info); + + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get monitor info [ERROR: %u]", GetLastError()); else { CORE.Window.display.width = info.rcMonitor.right - info.rcMonitor.left; CORE.Window.display.height = info.rcMonitor.bottom - info.rcMonitor.top; } } - else TRACELOG(LOG_WARNING, "MonitorFromPoint, error: %s", GetLastError()); -*/ - + else TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get primary monitor from point [ERROR: %u]", GetLastError()); + + // Adjust the window rectangle so the *client area* matches desired size + // NOTE: Window width/height includes borders and title-bar + DWORD style = WS_OVERLAPPEDWINDOW; + RECT rect = { 0, 0, platform.appScreenWidth, platform.appScreenHeight }; + AdjustWindowRect(&rect, style, FALSE); + //AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, FALSE, WINDOW_STYLE_EX); + //AdjustWindowRectExForDpi(&rect, style, FALSE, WINDOW_STYLE_EX, dpi); + int windowWidth = rect.right - rect.left; + int windowHeight = rect.bottom - rect.top; + // Create window // NOTE: Title string needs to be converted to WCHAR WCHAR *titleWide = NULL; @@ -1454,13 +1575,13 @@ int InitPlatform(void) titleWide, MakeWindowStyle(CORE.Window.flags), // WS_OVERLAPPEDWINDOW | WS_VISIBLE CW_USEDEFAULT, CW_USEDEFAULT, - platform.appScreenWidth, platform.appScreenHeight, // TODO: Window size [width, height], needs to be updated? + windowWidth, windowHeight, // TODO: Window size [width, height], needs to be updated? NULL, NULL, GetModuleHandleW(NULL), NULL); if (!platform.hwnd) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "CreateWindow", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: WINDOW: Failed to create window [ERROR: %lu]", GetLastError()); return -1; } @@ -1475,11 +1596,11 @@ int InitPlatform(void) // Initialize software framebuffer BITMAPINFO bmi = { 0 }; ZeroMemory(&bmi, sizeof(bmi)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = platform.appScreenWidth; - bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); // Top-down bitmap - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = platform.appScreenWidth; + bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); // Top-down bitmap + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA bmi.bmiHeader.biCompression = BI_RGB; platform.hdcmem = CreateCompatibleDC(platform.hdc); @@ -1504,6 +1625,8 @@ int InitPlatform(void) //UpdateWindowSize(UPDATE_WINDOW_FIRST, platform.hwnd, platform.appScreenWidth, platform.appScreenHeight, platform.desiredFlags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; CORE.Window.currentFbo.width = CORE.Window.render.width; CORE.Window.currentFbo.height = CORE.Window.render.height; TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); @@ -1512,17 +1635,29 @@ int InitPlatform(void) TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); - CORE.Storage.basePath = GetWorkingDirectory(); + if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer + { + TRACELOG(LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(LOG_INFO, " > Vendor: %s", "raylib"); + TRACELOG(LOG_INFO, " > Renderer: %s", "rlsw - OpenGL 1.1 Software Renderer"); + TRACELOG(LOG_INFO, " > Version: %s", "1.0"); + TRACELOG(LOG_INFO, " > GLSL: %s", "NOT SUPPORTED"); + } + // Initialize timming system + //---------------------------------------------------------------------------- LARGE_INTEGER time = { 0 }; QueryPerformanceCounter(&time); QueryPerformanceFrequency(&platform.timerFrequency); CORE.Time.base = time.QuadPart; InitTimer(); - - // TODO: Enable cursor? -> Use default value as 0 - platform.cursorEnabled = true; + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully"); @@ -1535,7 +1670,7 @@ void ClosePlatform(void) if (platform.hwnd) { int result = DestroyWindow(platform.hwnd); - if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Error on window destroy: %u", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError()); platform.hwnd = NULL; } } @@ -1617,7 +1752,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara { // TODO: Enforce min/max size } - else TRACELOG(LOG_WARNING, "WINDOW: Trying to resize a non-resizable window"); + else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window"); result = TRUE; } break; @@ -1629,19 +1764,22 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags); UINT dpi = GetDpiForWindow(hwnd); - SIZE clientSize = GetClientSize(hwnd); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld); SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew); if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy) { - TRACELOG(LOG_INFO, "resize from style change: %dx%d to %dx%d", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); + TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) { // looks like windows will automatically "unminimize" a window // if a style changes modifies it's size - TRACELOG(LOG_INFO, "style change modifed window size, removing maximized flag"); + TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modifed window size, removing maximized flag"); deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED; } } @@ -1650,27 +1788,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara case WM_WINDOWPOSCHANGING: { WINDOWPOS *pos = (WINDOWPOS *)lparam; - if (pos->flags & SWP_SHOWWINDOW) - { - //if (pos->flags & SWP_HIDEWINDOW) abort(); - deferredFlags->clear |= FLAG_WINDOW_HIDDEN; - } - else if (pos->flags & SWP_HIDEWINDOW) - { - deferredFlags->set |= FLAG_WINDOW_HIDDEN; - } + if (pos->flags & SWP_SHOWWINDOW) deferredFlags->clear |= FLAG_WINDOW_HIDDEN; + else if (pos->flags & SWP_HIDEWINDOW) deferredFlags->set |= FLAG_WINDOW_HIDDEN; Mized mized = MIZED_NONE; bool isIconic = IsIconic(hwnd); bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE)); - if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state"); if (isIconic) mized = MIZED_MIN; else { WINDOWPLACEMENT placement; placement.length = sizeof(placement); - if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowPlacement", GetLastError()); + if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "WIN32: WINDOW: FAiled to get monitor placement [ERROR: %lu]", GetLastError()); if (placement.showCmd == SW_SHOWMAXIMIZED) mized = MIZED_MAX; } @@ -1683,19 +1814,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); MONITORINFO info; info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError()); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: MONITOR: Failed to get monitor info [ERROR: %lu]", GetLastError()); if ((pos->x == info.rcMonitor.left) && (pos->y == info.rcMonitor.top) && (pos->cx == (info.rcMonitor.right - info.rcMonitor.left)) && - (pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) - { - deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE; - } - else - { - deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE; - } + (pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE; + else deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE; } break; case MIZED_MIN: @@ -1739,7 +1864,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara float dpiScale = ((float)newDpi)/96.0f; bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI; - SIZE desired = PxFromPt2(dpiScale, dpiScaling, platform.appScreenWidth, platform.appScreenHeight); + // Get size in pixels from points + SIZE desired = { + .cx = dpiScaling? (int)((float)platform.appScreenWidth*dpiScale) : platform.appScreenWidth, + .cy = dpiScaling? (int)((float)platform.appScreenHeight*dpiScale) : platform.appScreenHeight + }; inoutSize->cx = desired.cx; inoutSize->cy = desired.cy; @@ -1747,18 +1876,14 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_DPICHANGED: { - RECT *suggestedRect = (RECT*)lparam; + RECT *suggestedRect = (RECT *)lparam; + // Never set the window size to anything other than the suggested rect here // Doing so can cause a window to stutter between monitors when transitioning between them - if (!SetWindowPos(hwnd, NULL, - suggestedRect->left, - suggestedRect->top, - suggestedRect->right - suggestedRect->left, - suggestedRect->bottom - suggestedRect->top, - SWP_NOZORDER | SWP_NOACTIVATE)) - { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError()); - } + int result = (int)SetWindowPos(hwnd, NULL, suggestedRect->left, suggestedRect->top, + suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top, SWP_NOZORDER | SWP_NOACTIVATE); + if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError()); + } break; case WM_SETCURSOR: { @@ -1789,7 +1914,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_MOUSEMOVE: { - if (platform.cursorEnabled) + if (!CORE.Input.Mouse.cursorLocked) { CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam); CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam); @@ -1833,32 +1958,23 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } //------------------------------------------------------------------------------------ - // Sanity check - if (platform.hwnd == hwnd) - { - CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); - } + // Sanity check for flags + if (platform.hwnd == hwnd) CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); // Operations to execute after the above check - if (flagsOp.set & flagsOp.clear) - { - TRACELOG(LOG_ERROR, "the flags 0x%x were both set and cleared!", flagsOp.set & flagsOp.clear); - } + if (flagsOp.set & flagsOp.clear) TRACELOG(LOG_WARNING, "WIN32: FLAGS: Flags 0x%x were both set and cleared", flagsOp.set & flagsOp.clear); DWORD save = CORE.Window.flags; CORE.Window.flags |= flagsOp.set; CORE.Window.flags &= ~flagsOp.clear; - if (save != CORE.Window.flags) - { - TRACELOG(LOG_DEBUG, "DeferredFlags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags); - } + if (save != CORE.Window.flags) TRACELOG(LOG_DEBUG, "WIN32: FLAGS: Current deferred flags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags); return result; } static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { - KeyboardKey key = KeyFromWparam(wparam); + KeyboardKey key = GetKeyFromWparam(wparam); // TODO: Use scancode? //BYTE scancode = lparam >> 16; @@ -1888,9 +2004,9 @@ static void HandleRawInput(LPARAM lparam) UINT inputSize = sizeof(input); UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER)); - if (size == (UINT)-1) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetRawInputData", GetLastError()); + if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError()); - if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "Unexpected WM_INPUT type %lu", input.header.dwType); + if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType); if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!"); @@ -1908,12 +2024,14 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) { if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return; - SIZE clientSize = GetClientSize(hwnd); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; - //TRACELOG(LOG_DEBUG, "WINDOW: New widow client size: [%lux%lu]", clientSize.cx, clientSize.cy); - - //CORE.Window.currentFbo.width = clientSize.cx; - //CORE.Window.currentFbo.height = clientSize.cy; + // TODO: Update framebuffer on resize + CORE.Window.currentFbo.width = (int)clientSize.cx; + CORE.Window.currentFbo.height = (int)clientSize.cy; //glViewport(0, 0, clientSize.cx, clientSize.cy); //SetupFramebuffer(0, 0); @@ -1928,7 +2046,7 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) if (AdoptWindowResize(CORE.Window.flags)) { - TRACELOG(LOG_DEBUG, "WINDOW: Updating app size to %ix%i from window resize", screenWidth, screenHeight); + TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight); *width = screenWidth; *height = screenHeight; } @@ -1951,15 +2069,21 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) DWORD previous = STYLE_MASK_WRITABLE & SetWindowLongPtrW(hwnd, GWL_STYLE, desired); if (previous != current) { - TRACELOG(LOG_ERROR, "SetWindowLong returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)", + TRACELOG(LOG_ERROR, "WIN32: WINDOW: SetWindowLongPtr() returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)", previous, current, previous ^ current, GetLastError()); } CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE); } - Mized currentMized = MizedFromStyle(MakeWindowStyle(CORE.Window.flags)); - Mized desiredMized = MizedFromStyle(MakeWindowStyle(desiredFlags)); + // Minimized takes precedence over maximized + Mized currentMized = MIZED_NONE; + Mized desiredMized = MIZED_NONE; + if (CORE.Window.flags & WS_MINIMIZE) currentMized = MIZED_MIN; + else if (CORE.Window.flags & WS_MAXIMIZE) currentMized = MIZED_MAX; + if (desiredFlags & WS_MINIMIZE) currentMized = MIZED_MIN; + else if (desiredFlags & WS_MAXIMIZE) currentMized = MIZED_MAX; + if (currentMized != desiredMized) { switch (desiredMized) @@ -1972,24 +2096,21 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) } // Sanitize flags -static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags) +static unsigned SanitizeFlags(int mode, unsigned flags) { if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE)) { - TRACELOG(LOG_INFO, "borderless windows mode is overriding maximized"); + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag"); flags &= ~FLAG_WINDOW_MAXIMIZED; } - switch (kind) + if (mode == 1) { - case SANITIZE_FLAGS_FIRST: break; - case SANITIZE_FLAGS_NORMAL: - if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT))) - { - TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); - flags &= ~FLAG_MSAA_4X_HINT; - } - break; + if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT))) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: MSAA can only be configured before window initialization"); + flags &= ~FLAG_MSAA_4X_HINT; + } } return flags; @@ -2018,16 +2139,15 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE); int vsync = (CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0; - PFNWGLSWAPINTERVALEXTPROC wglSwapInterval = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); - if (wglSwapInterval) + if (wglSwapIntervalEXT) { - (*wglSwapInterval)(vsync); + (*wglSwapIntervalEXT)(vsync); if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT; else CORE.Window.flags &= ~FLAG_VSYNC_HINT; } // TODO: Review all this code... - DWORD previousStyle; + DWORD previousStyle = 0; for (unsigned attempt = 1; ; attempt++) { CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL); @@ -2035,15 +2155,14 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) bool windowSizeUpdated = false; if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags)) { - windowSizeUpdated = UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, width, height, desiredFlags); + windowSizeUpdated = UpdateWindowSize(1, hwnd, width, height, desiredFlags); if ((FLAG_MASK_REQUIRED & desiredFlags) == (FLAG_MASK_REQUIRED & CORE.Window.flags)) break; } - if ((attempt > 1) && - (previousStyle == MakeWindowStyle(CORE.Window.flags)) && - !windowSizeUpdated) + + if ((attempt > 1) && (previousStyle == MakeWindowStyle(CORE.Window.flags)) && !windowSizeUpdated) { - TRACELOG(LOG_ERROR, "WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)", + TRACELOG(LOG_ERROR, "WIN32: WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)", attempt, desiredFlags, CORE.Window.flags, desiredFlags ^ CORE.Window.flags); } @@ -2051,3 +2170,21 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) UpdateWindowStyle(hwnd, desiredFlags); } } + +// Check if OpenGL extension is available +static bool IsWglExtensionAvailable(HDC hdc, const char *extension) +{ + bool result = false; + + if (wglGetExtensionsStringARB != NULL) + { + const char *extList = wglGetExtensionsStringARB(hdc); + if (extList != NULL) + { + // Simple substring search (could use strtok or strstr) + if (strstr(extList, extension) != NULL) result = true; + } + } + + return result; +} \ No newline at end of file diff --git a/src/rlgl.h b/src/rlgl.h index f587d81a2..e6a1c9432 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2329,6 +2329,16 @@ void rlglInit(int width, int height) RLGL.State.currentMatrix = &RLGL.State.modelview; #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) + // Initialize software renderer backend + int result = swInit(width, height); + if (result == 0) + { + TRACELOG(RL_LOG_ERROR, "RLSW: Software renderer initialization failed!"); + exit(-1); + } +#endif + // Initialize OpenGL default states //---------------------------------------------------------- // Init state: Depth test @@ -2345,39 +2355,28 @@ void rlglInit(int width, int height) glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) glEnable(GL_CULL_FACE); // Enable backface culling - // Init state: Cubemap seamless -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) -#endif - #if defined(GRAPHICS_API_OPENGL_11) // Init state: Color hints (deprecated in OpenGL 3.0+) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) #endif - -#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) - int result = swInit(width, height); // Initialize software renderer backend - if (result == 0) - { - TRACELOG(RL_LOG_ERROR, "RLSW: Software renderer initialization failed!"); - exit(-1); - } +#if defined(GRAPHICS_API_OPENGL_33) + // Init state: Cubemap seamless + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) #endif - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Store screen size into global variables RLGL.State.framebufferWidth = width; RLGL.State.framebufferHeight = height; - - TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); - //---------------------------------------------------------- #endif // Init state: Color/Depth buffers clear glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) glClearDepth(1.0f); // Set clear depth value (default) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- } // Vertex Buffer Object deinitialization (memory free) From 79b7cd6b9bfa193aa920cc7dfca64e3822e2884a Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 10:08:36 +0200 Subject: [PATCH 10/25] Format tweaks --- src/external/rlsw.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 49050f848..904bf1210 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -6,7 +6,7 @@ * rlsw is a custom OpenGL 1.1-style implementation on software, intended to provide all * functionality available on rlgl.h library used by raylib, becoming a direct software * rendering replacement for OpenGL 1.1 backend and allowing to run raylib on GPU-less -* devices when required. +* devices when required * * FEATURES: * - Rendering to custom internal framebuffer with multiple color modes supported: @@ -2892,8 +2892,8 @@ static inline bool sw_quad_is_axis_aligned(void) float dx = v1[0] - v0[0]; float dy = v1[1] - v0[1]; - if (fabsf(dx) > 1e-6f && fabsf(dy) < 1e-6f) horizontal++; - else if (fabsf(dy) > 1e-6f && fabsf(dx) < 1e-6f) vertical++; + if ((fabsf(dx) > 1e-6f) && (fabsf(dy) < 1e-6f)) horizontal++; + else if ((fabsf(dy) > 1e-6f) && (fabsf(dx) < 1e-6f)) vertical++; else return false; // Diagonal edge -> not axis-aligned } @@ -2933,8 +2933,8 @@ static inline void sw_quad_sort_cw(const sw_vertex_t* *output) // Separate vertices based on Y-coordinate for (int i = 0; i < 4; i++) { - if (input[i].screen[1] == minY && topCount < 2) top[topCount++] = &input[i]; - else if (input[i].screen[1] == maxY && bottomCount < 2) bottom[bottomCount++] = &input[i]; + if ((input[i].screen[1] == minY) && (topCount < 2)) top[topCount++] = &input[i]; + else if ((input[i].screen[1] == maxY) && (bottomCount < 2)) bottom[bottomCount++] = &input[i]; } // If we don't have enough top/bottom vertices (e.g., Y values are all different), @@ -2960,7 +2960,7 @@ static inline void sw_quad_sort_cw(const sw_vertex_t* *output) } // Sort bottom vertices by X (left to right) - if (bottomCount == 2 && bottom[0]->screen[0] > bottom[1]->screen[0]) + if ((bottomCount == 2) && (bottom[0]->screen[0] > bottom[1]->screen[0])) { const sw_vertex_t *temp = bottom[0]; bottom[0] = bottom[1]; @@ -2997,7 +2997,7 @@ static inline void FUNC_NAME(void) int width = xMax - xMin; \ int height = yMax - yMin; \ \ - if (width == 0 || height == 0) return; \ + if ((width == 0) || (height == 0)) return; \ \ float wRcp = (width > 0.0f)? 1.0f/width : 0.0f; \ float hRcp = (height > 0.0f)? 1.0f/height : 0.0f; \ @@ -3157,7 +3157,7 @@ static inline void sw_quad_render(void) if (RLSW.vertexCounter < 3) return; - if (RLSW.vertexCounter == 4 && sw_quad_is_axis_aligned()) + if ((RLSW.vertexCounter == 4) && sw_quad_is_axis_aligned()) { if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); @@ -3304,7 +3304,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ int dy = y2 - y1; \ \ /* Handling of lines that are more horizontal or vertical */ \ - if (dx == 0 && dy == 0) \ + if ((dx == 0) && (dy == 0)) \ { \ /* TODO: A point should be rendered here */ \ return; \ @@ -3444,7 +3444,7 @@ void FUNC_NAME(const sw_vertex_t *v1, const sw_vertex_t *v2) \ \ RASTER_FUNC(v1, v2); \ \ - if (dx != 0 && abs(dy/dx) < 1) \ + if ((dx != 0) && (abs(dy/dx) < 1)) \ { \ int wy = (int)((RLSW.lineWidth - 1.0f)*abs(dx)/sqrtf(dx*dx + dy*dy)); \ wy >>= 1; \ @@ -3551,13 +3551,13 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ { \ if (CHECK_BOUNDS == 1) \ { \ - if (x < RLSW.vpMin[0] || x >= RLSW.vpMax[0]) return; \ - if (y < RLSW.vpMin[1] || y >= RLSW.vpMax[1]) return; \ + if ((x < RLSW.vpMin[0]) || (x >= RLSW.vpMax[0])) return; \ + if ((y < RLSW.vpMin[1]) || (y >= RLSW.vpMax[1])) return; \ } \ else if (CHECK_BOUNDS == SW_SCISSOR_TEST) \ { \ - if (x < RLSW.scMin[0] || x >= RLSW.scMax[0]) return; \ - if (y < RLSW.scMin[1] || y >= RLSW.scMax[1]) return; \ + if ((x < RLSW.scMin[0]) || (x >= RLSW.scMax[0])) return; \ + if ((y < RLSW.scMin[1]) || (y >= RLSW.scMax[1])) return; \ } \ \ int offset = y*RLSW.framebuffer.width + x; \ @@ -3718,12 +3718,12 @@ static inline bool sw_is_texture_valid(uint32_t id) static inline bool sw_is_texture_filter_valid(int filter) { - return (filter == SW_NEAREST || filter == SW_LINEAR); + return ((filter == SW_NEAREST) || (filter == SW_LINEAR)); } static inline bool sw_is_texture_wrap_valid(int wrap) { - return (wrap == SW_REPEAT || wrap == SW_CLAMP); + return ((wrap == SW_REPEAT) || (wrap == SW_CLAMP)); } static inline bool sw_is_draw_mode_valid(int mode) @@ -4392,7 +4392,7 @@ void swRotatef(float angle, float x, float y, float z) float lengthSq = x*x + y*y + z*z; - if (lengthSq != 1.0f && lengthSq != 0.0f) + if ((lengthSq != 1.0f) && (lengthSq != 0.0f)) { float invLength = 1.0f/sqrtf(lengthSq); x *= invLength; From 3c5b3f1c1737cc20940f41ec338669d8b032abdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Tue, 21 Oct 2025 04:11:19 -0400 Subject: [PATCH 11/25] [examples] Added `shapes_lines_drawing` (#5283) * Added shapes_lines_drawing Example * store result of clamp * conventions * fixed more brackets * buffer comments --- examples/shapes/shapes_lines_drawing.c | 146 +++++++++++++++++++++++ examples/shapes/shapes_lines_drawing.png | Bin 0 -> 34456 bytes 2 files changed, 146 insertions(+) create mode 100644 examples/shapes/shapes_lines_drawing.c create mode 100644 examples/shapes/shapes_lines_drawing.png diff --git a/examples/shapes/shapes_lines_drawing.c b/examples/shapes/shapes_lines_drawing.c new file mode 100644 index 000000000..9347acaf3 --- /dev/null +++ b/examples/shapes/shapes_lines_drawing.c @@ -0,0 +1,146 @@ +/******************************************************************************************* +* +* raylib [shapes] example - lines drawing +* +* Example complexity rating: [★☆☆☆] 1/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6 +* +* Example contributed by Robin (@RobinsAviary) 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) 2025-2025 Robin (@RobinsAviary) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - lines drawing"); + + // Hint text that shows before you click the screen + bool startText = true; + + // The mouse's position on the previous frame + Vector2 mousePositionPrevious = GetMousePosition(); + + // The canvas to draw lines on + RenderTexture canvas = LoadRenderTexture(screenWidth, screenHeight); + + // The background color of the canvas + const Color backgroundColor = RAYWHITE; + + // The line's thickness + float lineThickness = 8.0f; + // The lines hue (in HSV, from 0-360) + float lineHue = 0.0f; + + // Clear the canvas to the background color + BeginTextureMode(canvas); + ClearBackground(backgroundColor); + EndTextureMode(); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // Disable the hint text once the user clicks + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && startText) + { + startText = false; + } + + // Clear the canvas when the user middle-clicks + if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) + { + BeginTextureMode(canvas); + ClearBackground(backgroundColor); + EndTextureMode(); + } + + // Store whether the left and right buttons are down + bool leftButtonDown = IsMouseButtonDown(MOUSE_BUTTON_LEFT); + bool rightButtonDown = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); + + if (leftButtonDown || rightButtonDown) + { + // The color for the line + Color drawColor; + + if (leftButtonDown) + { + // Increase the hue value by the distance our cursor has moved since the last frame (divided by 3) + lineHue += Vector2Distance(mousePositionPrevious, GetMousePosition())/3.0f; + + // While the hue is >=360, subtract it to bring it down into the range 0-360 + // This is more visually accurate than resetting to zero + while (lineHue >= 360.0f) + { + lineHue -= 360.0f; + } + + // Create the final color + drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f); + } + else if (rightButtonDown) + { + // Use the background color as an "eraser" + drawColor = backgroundColor; + } + + // Draw the line onto the canvas + BeginTextureMode(canvas); + // Circles act as "caps", smoothing corners + DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor); + DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor); + DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor); + EndTextureMode(); + } + + // Update line thickness based on mousewheel + lineThickness += GetMouseWheelMove(); + lineThickness = Clamp(lineThickness, 1.0, 500.0f); + + // Update mouse's previous position + mousePositionPrevious = GetMousePosition(); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + // Draw the render texture to the screen, flipped vertically to make it appear top-side up + DrawTextureRec(canvas.texture, (Rectangle){ 0.0f, 0.0f, (float)canvas.texture.width,(float)-canvas.texture.height }, Vector2Zero(), WHITE); + + // Draw the preview circle + if (!leftButtonDown) DrawCircleLinesV(GetMousePosition(), lineThickness/2.0f, (Color){ 127, 127, 127, 127 }); + + // Draw the hint text + if (startText) DrawText("try clicking and dragging!", 275, 215, 20, LIGHTGRAY); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + // Unload the canvas render texture + UnloadRenderTexture(canvas); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_lines_drawing.png b/examples/shapes/shapes_lines_drawing.png new file mode 100644 index 0000000000000000000000000000000000000000..59ef6593439152c128657ecacb80cc91eb146e07 GIT binary patch literal 34456 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 kF$+pWf-K*!Pz?Wz1&~ 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 literal 0 HcmV?d00001 From ec06f9be371abff097c2426e8a39c358051ff397 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 13:51:03 +0200 Subject: [PATCH 12/25] Removed trailing spaces --- .github/workflows/build_windows.yml | 12 +-- examples/core/core_3d_camera_first_person.c | 1 - examples/core/core_render_texture.c | 16 ++-- examples/core/core_text_file_loading.c | 10 +-- examples/shapes/shapes_simple_particles.c | 8 +- src/platforms/rcore_android.c | 2 +- src/platforms/rcore_desktop_glfw.c | 7 +- src/platforms/rcore_desktop_rgfw.c | 12 +-- src/platforms/rcore_desktop_win32.c | 84 ++++++++++----------- src/platforms/rcore_drm.c | 14 ++-- src/rcore.c | 4 +- src/rtext.c | 6 +- tools/rexm/rexm.c | 8 +- 13 files changed, 91 insertions(+), 93 deletions(-) diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index 9e87b5870..7a92c208a 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -40,10 +40,10 @@ jobs: ziptarget: "win64" - compiler: msvc16 ARCH: "x86" - VSARCHPATH: "Win32" + VSARCHPATH: "Win32" ziptarget: "win32" - compiler: msvc16 - ARCH: "x64" + ARCH: "x64" VSARCHPATH: "x64" ziptarget: "win64" - compiler: msvc16 @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@master - + - name: Setup Release Version run: | echo "RELEASE_NAME=raylib-${{ github.event.release.tag_name }}_${{ matrix.ziptarget }}_${{ matrix.compiler }}" >> $GITHUB_ENV @@ -69,7 +69,7 @@ jobs: if: github.event_name == 'release' && github.event.action == 'published' - name: Setup Environment - run: | + run: | dir mkdir build cd build @@ -98,7 +98,7 @@ jobs: if: | matrix.compiler == 'mingw-w64' && matrix.arch == 'i686' - + - name: Build Library (MinGW-w64 64bit) run: | cd src @@ -144,7 +144,7 @@ jobs: with: name: ${{ env.RELEASE_NAME }}.zip path: ./build/${{ env.RELEASE_NAME }}.zip - + - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 with: diff --git a/examples/core/core_3d_camera_first_person.c b/examples/core/core_3d_camera_first_person.c index 8f4dc70fb..64368c4aa 100644 --- a/examples/core/core_3d_camera_first_person.c +++ b/examples/core/core_3d_camera_first_person.c @@ -119,7 +119,6 @@ int main(void) // Some default standard keyboard/mouse inputs are hardcoded to simplify use // For advanced camera controls, it's recommended to compute camera movement manually UpdateCamera(&camera, cameraMode); // Update camera - /* // Camera PRO usage example (EXPERIMENTAL) // This new camera function allows custom movement/rotation values to be directly provided diff --git a/examples/core/core_render_texture.c b/examples/core/core_render_texture.c index 47dc66e0f..e30220a73 100644 --- a/examples/core/core_render_texture.c +++ b/examples/core/core_render_texture.c @@ -26,7 +26,7 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [core] example - render texture"); - + // Define a render texture to render int renderTextureWidth = 300; int renderTextureHeight = 300; @@ -62,14 +62,14 @@ int main(void) //----------------------------------------------------- // Draw our scene to the render texture BeginTextureMode(target); - + ClearBackground(SKYBLUE); - + DrawRectangle(0, 0, 20, 20, RED); DrawCircleV(ballPosition, (float)ballRadius, MAROON); - + EndTextureMode(); - + // Draw render texture to main framebuffer BeginDrawing(); @@ -77,14 +77,14 @@ int main(void) // Draw our render texture with rotation applied // NOTE 1: We set the origin of the texture to the center of the render texture - // NOTE 2: We flip vertically the texture setting negative source rectangle height - DrawTexturePro(target.texture, + // NOTE 2: We flip vertically the texture setting negative source rectangle height + DrawTexturePro(target.texture, (Rectangle){ 0, 0, (float)target.texture.width, (float)-target.texture.height }, (Rectangle){ screenWidth/2.0f, screenHeight/2.0f, (float)target.texture.width, (float)target.texture.height }, (Vector2){ target.texture.width/2.0f, target.texture.height/2.0f }, rotation, WHITE); DrawText("DRAWING BOUNCING BALL INSIDE RENDER TEXTURE!", 10, screenHeight - 40, 20, BLACK); - + DrawFPS(10, 10); diff --git a/examples/core/core_text_file_loading.c b/examples/core/core_text_file_loading.c index 4b334e811..033fd4dc6 100644 --- a/examples/core/core_text_file_loading.c +++ b/examples/core/core_text_file_loading.c @@ -64,7 +64,7 @@ int main(void) if (lines[i][j] == ' ') { // Making a C Style string by adding a '\0' at the required location so that we can use the MeasureText function - lines[i][j] = '\0'; + lines[i][j] = '\0'; // Checking if the text has crossed the wrapWidth, then going back and inserting a newline if (MeasureText(lines[i] + lastWrapStart, fontSize) > wrapWidth) @@ -112,8 +112,8 @@ int main(void) cam.target.y -= scroll*fontSize*1.5f; // Choosing an arbitrary speed for scroll if (cam.target.y < 0) cam.target.y = 0; // Snapping to 0 if we go too far back - - // Ensuring that the camera does not scroll past all text + + // Ensuring that the camera does not scroll past all text if (cam.target.y > textHeight - screenHeight + textTop) cam.target.y = textHeight - screenHeight + textTop; @@ -133,10 +133,10 @@ int main(void) { // Each time we go through and calculate the height of the text to move the cursor appropriately Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], fontSize, 2); - + DrawText(lines[i], 10, t, fontSize, RED); - // Inserting extra space for real newlines, + // Inserting extra space for real newlines, // wrapped lines are rendered closer together t += size.y + 10; } diff --git a/examples/shapes/shapes_simple_particles.c b/examples/shapes/shapes_simple_particles.c index 7c2a598c2..c5d9612c3 100644 --- a/examples/shapes/shapes_simple_particles.c +++ b/examples/shapes/shapes_simple_particles.c @@ -74,7 +74,7 @@ int main(void) // Definition of particles Particle *particles = (Particle*)RL_CALLOC(MAX_PARTICLES, sizeof(Particle)); // Particle array CircularBuffer circularBuffer = { 0, 0, particles }; - + // Particle emitter parameters int emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame ParticleType currentType = WATER; @@ -100,7 +100,7 @@ int main(void) // Update the parameters of each particle UpdateParticles(&circularBuffer, screenWidth, screenHeight); - + // Remove dead particles from the circular buffer UpdateCircularBuffer(&circularBuffer); @@ -252,10 +252,10 @@ static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int // Disable particle when out of screen Vector2 center = circularBuffer->buffer[i].position; float radius = circularBuffer->buffer[i].radius; - + if ((center.x < -radius) || (center.x > (screenWidth + radius)) || (center.y < -radius) || (center.y > (screenHeight + radius))) - { + { circularBuffer->buffer[i].alive = false; } } diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 6ecc41888..47dcce32b 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -461,7 +461,7 @@ int GetCurrentMonitor(void) if (display == NULL) { TRACELOG(LOG_ERROR, "GetCurrentMonitor() couldn't get the display object"); - } + } else { jclass displayClass = (*env)->FindClass(env, "android/view/Display"); diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 729ca308c..5dde1df67 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1048,6 +1048,7 @@ Image GetClipboardImage(void) void ShowCursor(void) { glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + CORE.Input.Mouse.cursorHidden = false; } @@ -1055,6 +1056,7 @@ void ShowCursor(void) void HideCursor(void) { glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + CORE.Input.Mouse.cursorHidden = true; } @@ -1075,13 +1077,10 @@ void EnableCursor(void) void DisableCursor(void) { // Reset mouse position within the window area before disabling cursor - SetMousePosition(CORE.Window.screen.width, CORE.Window.screen.height); + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - // Set cursor position in the middle - SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); - if (glfwRawMouseMotionSupported()) glfwSetInputMode(platform.handle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); CORE.Input.Mouse.cursorLocked = true; diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 52ec6ac94..86842140d 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -80,7 +80,7 @@ void CloseWindow(void); #define CloseWindow CloseWindow_win32 #define ShowCursor __imp_ShowCursor #define _APISETSTRING_ - + #undef MAX_PATH #if defined(__cplusplus) @@ -262,7 +262,7 @@ static int RGFW_gpConvTable[18] = { [RGFW_gamepadRight] = GAMEPAD_BUTTON_LEFT_FACE_RIGHT, [RGFW_gamepadDown] = GAMEPAD_BUTTON_LEFT_FACE_DOWN, [RGFW_gamepadLeft] = GAMEPAD_BUTTON_LEFT_FACE_LEFT, - [RGFW_gamepadL3] = GAMEPAD_BUTTON_LEFT_THUMB, + [RGFW_gamepadL3] = GAMEPAD_BUTTON_LEFT_THUMB, [RGFW_gamepadR3] = GAMEPAD_BUTTON_RIGHT_THUMB, }; @@ -933,7 +933,7 @@ void PollInputEvents(void) // because ProcessGestureEvent() is just called on an event, not every frame UpdateGestures(); #endif - + // Reset keys/chars pressed registered CORE.Input.Keyboard.keyPressedQueueCount = 0; CORE.Input.Keyboard.charPressedQueueCount = 0; @@ -1025,7 +1025,7 @@ void PollInputEvents(void) CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event->droppedFiles[i]); - + CORE.Window.dropFileCount++; } else if (CORE.Window.dropFileCount < 1024) @@ -1229,7 +1229,7 @@ void PollInputEvents(void) int button = (axis == GAMEPAD_AXIS_LEFT_TRIGGER)? GAMEPAD_BUTTON_LEFT_TRIGGER_2 : GAMEPAD_BUTTON_RIGHT_TRIGGER_2; int pressed = (value > 0.1f); CORE.Input.Gamepad.currentButtonState[event->gamepad][button] = pressed; - + if (pressed) CORE.Input.Gamepad.lastButtonPressed = button; else if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; } @@ -1345,7 +1345,7 @@ int InitPlatform(void) // TODO: Is this needed by raylib now? // If so, rcore_desktop_sdl should be updated too //SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); - + if (CORE.Window.flags & FLAG_VSYNC_HINT) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 7ac6138bd..de9a66911 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -84,8 +84,8 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- -// NOTE: appScreenSize is the last screen size requested by the app, -// the backend must keep the client area this size (after DPI scaling is applied) +// NOTE: appScreenSize is the last screen size requested by the app, +// the backend must keep the client area this size (after DPI scaling is applied) // when the window isn't fullscreen/maximized/minimized typedef struct { HWND hwnd; // Window handler @@ -141,7 +141,7 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; AToWCopy(inAnsi, outWstr, outLen); \ outWstr[outLen] = 0; \ } while (0) - + #define STYLE_MASK_ALL 0xffffffff #define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE) #define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY) @@ -206,10 +206,10 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; // Types and Structures Definition //---------------------------------------------------------------------------------- // Maximize-minimize request types -typedef enum { - MIZED_NONE, - MIZED_MIN, - MIZED_MAX +typedef enum { + MIZED_NONE, + MIZED_MIN, + MIZED_MAX } Mized; // Flag operations @@ -234,7 +234,7 @@ typedef struct { static size_t AToWLen(const char *ascii) { int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0); - + if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError()); return sizeNeeded; @@ -268,7 +268,7 @@ static DWORD MakeWindowStyle(unsigned flags) // it improves efficiency, plus, windows adds this flag automatically anyway // so it keeps our flags in sync with the OS DWORD style = WS_CLIPSIBLINGS; - + style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE; style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0; style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF; @@ -339,7 +339,7 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style) { RECT rect = { 0, 0, clientSize.cx, clientSize.cy }; - + int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi); if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError()); @@ -442,8 +442,8 @@ static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigne static BOOL IsWindows10Version1703OrGreaterWin32(void) { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); - - DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) = + + DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) = (DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo"); if (!Verify) { @@ -461,7 +461,7 @@ static BOOL IsWindows10Version1703OrGreaterWin32(void) VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL); - + return 0 == (*Verify)(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, cond); } @@ -473,8 +473,8 @@ static void *WglGetProcAddress(const char *procname) if ((proc == NULL) || // NOTE: Some GPU drivers could return following // invalid sentinel values instead of NULL - (proc == (void *)0x1) || - (proc == (void *)0x2) || + (proc == (void *)0x1) || + (proc == (void *)0x2) || (proc == (void *)0x3) || (proc == (void *)-1)) { @@ -767,7 +767,7 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, Fla } // Adopt window resize -// NOTE: Call when the window is rezised, returns true +// NOTE: Call when the window is rezised, returns true // if the new window size should update the desired app size static bool AdoptWindowResize(unsigned flags) { @@ -776,7 +776,7 @@ static bool AdoptWindowResize(unsigned flags) if (flags & FLAG_FULLSCREEN_MODE) return false; if (flags & FLAG_BORDERLESS_WINDOWED_MODE) return false; if (!(flags & FLAG_WINDOW_RESIZABLE)) return false; - + return true; } @@ -947,10 +947,10 @@ void SetWindowIcons(Image *images, int count) void SetWindowTitle(const char *title) { CORE.Window.title = title; - + WCHAR *titleWide = NULL; A_TO_W_ALLOCA(titleWide, CORE.Window.title); - + int result = SetWindowTextW(platform.hwnd, titleWide); if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError()); } @@ -1023,7 +1023,7 @@ void *GetWindowHandle(void) int GetMonitorCount(void) { int count = 0; - + int result = EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count); if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); @@ -1040,7 +1040,7 @@ int GetCurrentMonitor(void) info.needle = monitor; info.index = 0; info.matchIndex = -1; - + int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info); if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); @@ -1127,7 +1127,7 @@ const char *GetClipboardText(void) Image GetClipboardImage(void) { Image image = { 0 }; - + TRACELOG(LOG_WARNING, "GetClipboardText not implemented"); return image; @@ -1193,7 +1193,7 @@ void DisableCursor(void) TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)", clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y); - + LONG centerX = topleft.x + width/2; LONG centerY = topleft.y + height/2; RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 }; @@ -1502,7 +1502,7 @@ int InitPlatform(void) if (IsWindows10Version1703OrGreaterWin32()) { TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators"); - if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) + if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) TRACELOG(LOG_ERROR, "%s failed, error %u", "SetProcessDpiAwarenessContext", GetLastError()); } else @@ -1620,7 +1620,7 @@ int InitPlatform(void) } CORE.Window.ready = true; - + // TODO: Should this function be called before or after drawing context is created? --> After swInit() called! //UpdateWindowSize(UPDATE_WINDOW_FIRST, platform.hwnd, platform.appScreenWidth, platform.appScreenHeight, platform.desiredFlags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); @@ -1660,7 +1660,7 @@ int InitPlatform(void) //---------------------------------------------------------------------------- TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully"); - + return 0; } @@ -1691,7 +1691,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara FlagsOp flagsOp = { 0 }; FlagsOp *deferredFlags = &flagsOp; - + // Message processing //------------------------------------------------------------------------------------ switch (msg) @@ -1707,13 +1707,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara // Clean up for window destruction if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer { - if (platform.hdcmem) + if (platform.hdcmem) { DeleteDC(platform.hdcmem); platform.hdcmem = NULL; } - if (platform.hbitmap) + if (platform.hbitmap) { DeleteObject(platform.hbitmap); // Clears platform.pixels data platform.hbitmap = NULL; @@ -1751,9 +1751,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) { // TODO: Enforce min/max size - } + } else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window"); - + result = TRUE; } break; case WM_STYLECHANGING: @@ -1770,11 +1770,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara SIZE clientSize = { rect.right, rect.bottom }; SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld); SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew); - + if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy) { TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); - + if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) { // looks like windows will automatically "unminimize" a window @@ -1795,7 +1795,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara bool isIconic = IsIconic(hwnd); bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE)); if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state"); - + if (isIconic) mized = MIZED_MIN; else { @@ -1871,7 +1871,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara }; inoutSize->cx = desired.cx; inoutSize->cy = desired.cy; - + result = TRUE; } break; case WM_DPICHANGED: @@ -1929,7 +1929,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); break; case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); break; case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); break; - case WM_XBUTTONDOWN: + case WM_XBUTTONDOWN: { switch (HIWORD(wparam)) { @@ -1975,11 +1975,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { KeyboardKey key = GetKeyFromWparam(wparam); - + // TODO: Use scancode? //BYTE scancode = lparam >> 16; //TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state); - + if (key != KEY_NULL) { CORE.Input.Keyboard.currentKeyState[key] = state; @@ -2003,9 +2003,9 @@ static void HandleRawInput(LPARAM lparam) UINT inputSize = sizeof(input); UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER)); - + if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError()); - + if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType); if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!"); @@ -2043,7 +2043,7 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) unsigned int screenHeight = highdpi? (unsigned int)(((float)clientSize.cy)/dpiScale) : clientSize.cy; CORE.Window.screen.width = screenWidth; CORE.Window.screen.height = screenHeight; - + if (AdoptWindowResize(CORE.Window.flags)) { TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight); @@ -2062,7 +2062,7 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) { DWORD current = STYLE_MASK_WRITABLE & MakeWindowStyle(CORE.Window.flags); DWORD desired = STYLE_MASK_WRITABLE & MakeWindowStyle(desiredFlags); - + if (current != desired) { SetLastError(0); diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 58e820af1..a08a76bce 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -864,7 +864,7 @@ void SwapScreenBuffer(void) // Create framebuffer with the correct format uint32_t fb = 0; result = drmModeAddFB(platform.fd, width, height, depth, bpp, creq.pitch, creq.handle, &fb); - if (result != 0) + if (result != 0) { TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d (%s)", result, strerror(errno)); struct drm_mode_destroy_dumb dreq = { 0 }; @@ -974,12 +974,12 @@ void SwapScreenBuffer(void) // Set CRTC with better error handling result = drmModeSetCrtc(platform.fd, crtcId, fb, 0, 0, &platform.connector->connector_id, 1, mode); - if (result != 0) + if (result != 0) { TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d (%s)", result, strerror(errno)); TRACELOG(LOG_ERROR, "DISPLAY: CRTC ID: %u, FB ID: %u, Connector ID: %u", crtcId, fb, platform.connector->connector_id); TRACELOG(LOG_ERROR, "DISPLAY: Mode: %dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh); - + drmModeRmFB(platform.fd, fb); struct drm_mode_destroy_dumb dreq = {0}; dreq.handle = creq.handle; @@ -1229,7 +1229,7 @@ int InitPlatform(void) } TRACELOG(LOG_TRACE, "DISPLAY: Connector %i modes detected: %i", i, con->count_modes); - TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i, + TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i, (con->connection == DRM_MODE_CONNECTED) ? "CONNECTED" : (con->connection == DRM_MODE_DISCONNECTED) ? "DISCONNECTED" : (con->connection == DRM_MODE_UNKNOWNCONNECTION) ? "UNKNOWN" : "OTHER"); @@ -1357,10 +1357,10 @@ int InitPlatform(void) platform.modeIndex = 0; CORE.Window.display.width = platform.connector->modes[0].hdisplay; CORE.Window.display.height = platform.connector->modes[0].vdisplay; - - TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u) for software rendering", + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u) for software rendering", platform.connector->modes[0].name, - platform.connector->modes[0].hdisplay, + platform.connector->modes[0].hdisplay, platform.connector->modes[0].vdisplay, (platform.connector->modes[0].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', platform.connector->modes[0].vrefresh); diff --git a/src/rcore.c b/src/rcore.c index 5dea97c22..350490ad1 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2524,7 +2524,7 @@ int MakeDirectory(const char *dirPath) // Create final directory if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); RL_FREE(pathcpy); - + // In case something failed and requested directory // was not successfully created, return -1 if (!DirectoryExists(dirPath)) return -1; @@ -3148,7 +3148,7 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) unsigned char *block = buffer + (blockN*64); unsigned int w[64]; for (int i = 0; i < 16; i++) - { + { w[i] = ((unsigned int)block[i*4 + 0] << 24) | ((unsigned int)block[i*4 + 1] << 16) | diff --git a/src/rtext.c b/src/rtext.c index 9951dc66d..b4cd560ba 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1701,9 +1701,9 @@ char *GetTextBetween(const char *text, const char *begin, const char *end) char *TextReplace(const char *text, const char *search, const char *replacement) { char *result = NULL; - + if (!text || !search) return NULL; // Sanity check - + char *insertPoint = NULL; // Next insert point char *temp = NULL; // Temp pointer int searchLen = 0; // Search string length of (the string to remove) @@ -1753,7 +1753,7 @@ char *TextReplaceBetween(const char *text, const char *begin, const char *end, c char *result = NULL; if (!text || !begin || !end) return NULL; // Sanity check - + int beginIndex = TextFindIndex(text, begin); if (beginIndex > -1) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 20fc1d5e5..d668655c8 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -239,7 +239,7 @@ int main(int argc, char *argv[]) // build : Build example for Desktop and Web platforms // validate : Validate examples collection, generates report // update : Validate and update examples collection, generates report - + if (strcmp(argv[1], "create") == 0) { // Check for valid upcoming argument @@ -394,8 +394,8 @@ int main(int argc, char *argv[]) { // Support building not only individual examples but categories and "ALL" if ((strcmp(argv[2], "ALL") == 0) || TextInList(argv[2], exCategories, REXM_MAX_EXAMPLE_CATEGORIES)) - { - // Category/ALL rebuilt requested + { + // Category/ALL rebuilt requested strcpy(exRebuildRequested, argv[2]); } else @@ -2432,7 +2432,7 @@ static void UpdateWebMetadata(const char *exHtmlPath, const char *exFilePath) static bool TextInList(const char *text, const char **list, int listCount) { bool result = false; - + for (int i = 0; i < listCount; i++) { if (TextIsEqual(text, list[i])) { result = true; break; } From 77b9214575ec60abe6824fd7c8abf40169c8e966 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 20:07:32 +0200 Subject: [PATCH 13/25] Update rcore_desktop_sdl.c --- src/platforms/rcore_desktop_sdl.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 6655db460..a5f626ecc 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -104,7 +104,6 @@ typedef struct { SDL_GameController *gamepad[MAX_GAMEPADS]; SDL_JoystickID gamepadId[MAX_GAMEPADS]; // Joystick instance ids, they do not start from 0 SDL_Cursor *cursor; - bool cursorRelative; } PlatformData; //---------------------------------------------------------------------------------- @@ -1209,13 +1208,13 @@ void EnableCursor(void) SDL_SetRelativeMouseMode(SDL_FALSE); #if defined(USING_VERSION_SDL3) - // SDL_ShowCursor() has been split into three functions: SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() + // NOTE: SDL_ShowCursor() has been split into three functions: + // SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() SDL_ShowCursor(); #else SDL_ShowCursor(SDL_ENABLE); #endif - platform.cursorRelative = false; CORE.Input.Mouse.cursorLocked = false; } @@ -1224,7 +1223,6 @@ void DisableCursor(void) { SDL_SetRelativeMouseMode(SDL_TRUE); - platform.cursorRelative = true; CORE.Input.Mouse.cursorLocked = true; } @@ -1332,7 +1330,7 @@ void PollInputEvents(void) CORE.Input.Mouse.currentWheelMove.y = 0; // Register previous mouse position - if (platform.cursorRelative) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; + if (CORE.Input.Mouse.cursorLocked) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; else CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; // Reset last gamepad button/axis registered state @@ -1634,7 +1632,7 @@ void PollInputEvents(void) } break; case SDL_MOUSEMOTION: { - if (platform.cursorRelative) + if (CORE.Input.Mouse.cursorLocked) { CORE.Input.Mouse.currentPosition.x = (float)event.motion.xrel; CORE.Input.Mouse.currentPosition.y = (float)event.motion.yrel; From 99ed81461526b37583cac323a51a5d019b49b16c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 20:07:42 +0200 Subject: [PATCH 14/25] Update rcore_desktop_win32.c --- src/platforms/rcore_desktop_win32.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index de9a66911..9b66f8c4b 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1136,17 +1136,17 @@ Image GetClipboardImage(void) // Show mouse cursor void ShowCursor(void) { - CORE.Input.Mouse.cursorHidden = false; SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); + CORE.Input.Mouse.cursorHidden = false; } // Hides mouse cursor void HideCursor(void) { - // NOTE: we use SetCursor instead of ShowCursor because it makes it easy - // to only hide the cursor while it's inside the client area - CORE.Input.Mouse.cursorHidden = true; + // NOTE: We use SetCursor() instead of ShowCursor() because + // it makes it easy to only hide the cursor while it's inside the client area SetCursor(NULL); + CORE.Input.Mouse.cursorHidden = true; } // Enables cursor (unlock cursor) @@ -1346,7 +1346,7 @@ void PollInputEvents(void) //---------------------------------------------------------------------------------- // Initialize modern OpenGL context -// NOTE: We need to create a dummy context first to query requried extensions +// NOTE: We need to create a dummy context first to query required extensions HGLRC InitOpenGL(HWND hwnd, HDC hdc) { // First, create a dummy context to get WGL extensions @@ -1621,8 +1621,7 @@ int InitPlatform(void) CORE.Window.ready = true; - // TODO: Should this function be called before or after drawing context is created? --> After swInit() called! - //UpdateWindowSize(UPDATE_WINDOW_FIRST, platform.hwnd, platform.appScreenWidth, platform.appScreenHeight, platform.desiredFlags); + // Update flags (in case of deferred state change required) UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); CORE.Window.render.width = CORE.Window.screen.width; @@ -1887,6 +1886,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_SETCURSOR: { + // Called when mouse moves, enters/leaves window... if (LOWORD(lparam) == HTCLIENT) { SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); @@ -1972,6 +1972,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara return result; } +// Handle keyboard input event static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { KeyboardKey key = GetKeyFromWparam(wparam); @@ -1991,12 +1992,15 @@ static void HandleKey(WPARAM wparam, LPARAM lparam, char state) // TODO: Add key to the queue as well? } +// Handle mouse button input event static void HandleMouseButton(int button, char state) { + // Register current mouse button state CORE.Input.Mouse.currentButtonState[button] = state; CORE.Input.Touch.currentTouchState[button] = state; } +// Handle raw input event static void HandleRawInput(LPARAM lparam) { RAWINPUT input = { 0 }; @@ -2020,6 +2024,7 @@ static void HandleRawInput(LPARAM lparam) //if (CORE.Input.Mouse.currentPosition.y != 0) abort(); } +// Handle window resizing event static void HandleWindowResize(HWND hwnd, int *width, int *height) { if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return; @@ -2051,10 +2056,8 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) *height = screenHeight; } - CORE.Window.screenScale = MatrixScale( - (float)CORE.Window.render.width/CORE.Window.screen.width, - (float)CORE.Window.render.height/CORE.Window.screen.height, - 1.0f); + CORE.Window.screenScale = MatrixScale( (float)CORE.Window.render.width/CORE.Window.screen.width, + (float)CORE.Window.render.height/CORE.Window.screen.height, 1.0f); } // Update window style From 1b5a14e5167755d54e8b4e9851b9167302641d2f Mon Sep 17 00:00:00 2001 From: sleeptightAnsiC <91839286+sleeptightAnsiC@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:07:04 +0200 Subject: [PATCH 15/25] [rcore_desktop_sdl] fix: handle monitor ID correctly on SDL3 (#5290) SDL3 uses ID when dealing with monitors, unlike SDL2 which uses Index for the same thing. This problem was already fixed in multiple places by use of preprocessor branches, so I did the very same thing. Please, notice that this is a pretty bad solution to this problem, and I only did it to keep it consistent with the rest of the code. The more about why it's not correct is mentioned here: https://github.com/raysan5/raylib/issues/5256#issuecomment-3429156919 Hopefully, someone will refactor it someday :) Fixes: https://github.com/raysan5/raylib/issues/5256 --- src/platforms/rcore_desktop_sdl.c | 37 ++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index a5f626ecc..e135cd848 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -837,7 +837,11 @@ void SetWindowPosition(int x, int y) void SetWindowMonitor(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { // NOTE: // 1. SDL started supporting moving exclusive fullscreen windows between displays on SDL3, @@ -961,7 +965,11 @@ int GetCurrentMonitor(void) Vector2 GetMonitorPosition(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_Rect displayBounds; @@ -985,7 +993,11 @@ int GetMonitorWidth(int monitor) int width = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); @@ -1002,7 +1014,11 @@ int GetMonitorHeight(int monitor) int height = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); @@ -1019,7 +1035,11 @@ int GetMonitorPhysicalWidth(int monitor) int width = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { float ddpi = 0.0f; SDL_GetDisplayDPI(monitor, &ddpi, NULL, NULL); @@ -1039,7 +1059,11 @@ int GetMonitorPhysicalHeight(int monitor) int height = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { float ddpi = 0.0f; SDL_GetDisplayDPI(monitor, &ddpi, NULL, NULL); @@ -1059,7 +1083,11 @@ int GetMonitorRefreshRate(int monitor) int refresh = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); @@ -1075,7 +1103,14 @@ const char *GetMonitorName(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); - if ((monitor >= 0) && (monitor < monitorCount)) return SDL_GetDisplayName(monitor); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else + if ((monitor >= 0) && (monitor < monitorCount)) +#endif + { + return SDL_GetDisplayName(monitor); + } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); return ""; From 428421f21a565b301e24e624fd2ffb4e1727258a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 22 Oct 2025 11:03:19 +0200 Subject: [PATCH 16/25] Update examples_list.txt --- examples/examples_list.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 2a1b9c0b7..71cda6c1a 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -51,6 +51,7 @@ core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_screen_recording;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary +core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bouncing_ball;★☆☆☆;2.5;2.5;2013;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bullet_hell;★☆☆☆;5.6;5.6;2025;2025;"Zero";@zerohorsepower @@ -160,6 +161,7 @@ shaders;shaders_texture_rendering;★★☆☆;2.0;3.7;2019;2025;"Michał Ciesie shaders;shaders_texture_outline;★★★☆;4.0;4.0;2021;2025;"Serenity Skiff";@GoldenThumbs shaders;shaders_texture_waves;★★☆☆;2.5;3.7;2019;2025;"Anata";@anatagawa shaders;shaders_julia_set;★★★☆;2.5;4.0;2019;2025;"Josh Colclough";@joshcol9232 +shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant shaders;shaders_eratosthenes_sieve;★★★☆;2.5;4.0;2019;2025;"ProfJski";@ProfJski shaders;shaders_fog_rendering;★★★☆;2.5;3.7;2019;2025;"Chris Camacho";@chriscamacho shaders;shaders_simple_mask;★★☆☆;2.5;3.7;2019;2025;"Chris Camacho";@chriscamacho @@ -192,5 +194,3 @@ others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flas others;raylib_opengl_interop;★★★★;3.8;4.0;2021;2025;"Stephan Soller";@arkanis others;embedded_files_loading;★★☆☆;3.0;3.5;2020;2025;"Kristian Holmgren";@defutura others;web_basic_window;★☆☆☆;5.6-dev;5.6-dev;2014;2025;"Ramon Santamaria";@raysan5 -core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 -shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant From 780a7df760d45b8bd386be425ab5c2da0b1a48f6 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 23 Oct 2025 04:25:23 -0700 Subject: [PATCH 17/25] Fix warnings in more examples. (#5292) --- examples/core/core_text_file_loading.c | 16 ++++++++-------- examples/shapes/shapes_clock_of_clocks.c | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/core/core_text_file_loading.c b/examples/core/core_text_file_loading.c index 033fd4dc6..ed48f7d36 100644 --- a/examples/core/core_text_file_loading.c +++ b/examples/core/core_text_file_loading.c @@ -88,16 +88,16 @@ int main(void) for (int i = 0; i < lineCount; i++) { - Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], fontSize, 2); - textHeight += size.y + 10; + Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2); + textHeight += (int)size.y + 10; } // A simple scrollbar on the side to show how far we have red into the file Rectangle scrollBar = { - .x = screenWidth - 5, + .x = (float)screenWidth - 5, .y = 0, .width = 5, - .height = screenHeight*100/(textHeight - screenHeight) // Scrollbar height is just a percentage + .height = screenHeight*100.0f/(textHeight - screenHeight) // Scrollbar height is just a percentage }; SetTargetFPS(60); @@ -115,10 +115,10 @@ int main(void) // Ensuring that the camera does not scroll past all text if (cam.target.y > textHeight - screenHeight + textTop) - cam.target.y = textHeight - screenHeight + textTop; + cam.target.y = (float)textHeight - screenHeight + textTop; // Computing the position of the scrollBar depending on the percentage of text covered - scrollBar.y = Lerp(textTop, screenHeight - scrollBar.height, (cam.target.y - textTop)/(textHeight - screenHeight)); + scrollBar.y = Lerp((float)textTop, (float)screenHeight - scrollBar.height, (float)(cam.target.y - textTop)/(textHeight - screenHeight)); //---------------------------------------------------------------------------------- // Draw @@ -132,13 +132,13 @@ int main(void) for (int i = 0, t = textTop; i < lineCount; i++) { // Each time we go through and calculate the height of the text to move the cursor appropriately - Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], fontSize, 2); + Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2); DrawText(lines[i], 10, t, fontSize, RED); // Inserting extra space for real newlines, // wrapped lines are rendered closer together - t += size.y + 10; + t += (int)size.y + 10; } EndMode2D(); diff --git a/examples/shapes/shapes_clock_of_clocks.c b/examples/shapes/shapes_clock_of_clocks.c index 493e53b5e..3fc24aa92 100644 --- a/examples/shapes/shapes_clock_of_clocks.c +++ b/examples/shapes/shapes_clock_of_clocks.c @@ -14,6 +14,9 @@ * Copyright (c) 2025 JP Mortiboys (@themushroompirates) * ********************************************************************************************/ +#if defined(WIN32) +#define _CRT_SECURE_NO_WARNINGS +#endif #include "raylib.h" From 04930c5132f900be39b21a6b89685f0f75f2d0e5 Mon Sep 17 00:00:00 2001 From: Diogo Hartuiq Debarba <35925088+diogohartuiqdebarba@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:27:28 -0300 Subject: [PATCH 18/25] Fix typo in RPI cross-compiler variable name (#5295) --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 0d4ca9ac7..4369d8084 100644 --- a/src/Makefile +++ b/src/Makefile @@ -283,7 +283,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) - ifeq ($(USE_RPI_CROSS_COMPILER),TRUE) + ifeq ($(USE_RPI_CROSSCOMPILER),TRUE) # Define RPI cross-compiler #CC = armv6j-hardfloat-linux-gnueabi-gcc CC = $(RPI_TOOLCHAIN)/bin/$(RPI_TOOLCHAIN_NAME)-gcc From 93c5dc3b5f67b5c221cd93f379f9b31ba15b8c94 Mon Sep 17 00:00:00 2001 From: alexgb0 Date: Thu, 23 Oct 2025 17:28:00 +0200 Subject: [PATCH 19/25] [rlsw] c++ support (#5291) * rlsw c++ support * rslw formatting fixes --- src/external/rlsw.h | 141 ++++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 64 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 904bf1210..aa4803265 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -156,6 +156,12 @@ #define SW_CLIP_EPSILON 1e-4f #endif +#ifdef __cplusplus + #define CURLY_INIT(name) name +#else + #define CURLY_INIT(name) (name) +#endif + //---------------------------------------------------------------------------------- // OpenGL Compatibility Types //---------------------------------------------------------------------------------- @@ -1699,7 +1705,7 @@ static inline void sw_framebuffer_blit_to_##name( int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ - const uint8_t *srcBase = RLSW.framebuffer.color; \ + const uint8_t *srcBase = (uint8_t*)RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ @@ -1722,7 +1728,7 @@ static inline void sw_framebuffer_blit_to_##name( int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ - const uint8_t *srcBase = RLSW.framebuffer.color; \ + const uint8_t *srcBase = (uint8_t *)RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ @@ -2583,6 +2589,15 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ { \ + /* Pixel color computation */ \ + float wRcp = 1.0f/w; \ + float srcColor[4] = { \ + color[0]*wRcp, \ + color[1]*wRcp, \ + color[2]*wRcp, \ + color[3]*wRcp \ + }; \ + \ /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -2593,15 +2608,6 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \ sw_framebuffer_write_depth(dptr, z); \ \ - /* Pixel color computation */ \ - float wRcp = 1.0f/w; \ - float srcColor[4] = { \ - color[0]*wRcp, \ - color[1]*wRcp, \ - color[2]*wRcp, \ - color[3]*wRcp \ - }; \ - \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ @@ -3063,6 +3069,14 @@ static inline void FUNC_NAME(void) /* Scanline rasterization */ \ for (int x = xMin; x < xMax; x++) \ { \ + /* Pixel color computation */ \ + float srcColor[4] = { \ + color[0], \ + color[1], \ + color[2], \ + color[3] \ + }; \ + \ /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3073,14 +3087,6 @@ static inline void FUNC_NAME(void) \ sw_framebuffer_write_depth(dptr, z); \ \ - /* Pixel color computation */ \ - float srcColor[4] = { \ - color[0], \ - color[1], \ - color[2], \ - color[3] \ - }; \ - \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ @@ -3361,6 +3367,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ { \ int offset = (y1 + i)*fbWidth + (x1 + (j >> 16)); \ void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ + void *cptr; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3370,7 +3377,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - void *cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ @@ -3397,6 +3404,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ { \ int offset = (y1 + (j >> 16))*fbWidth + (x1 + i); \ void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ + void *cptr; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3406,7 +3414,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - void *cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ @@ -3687,9 +3695,14 @@ static inline void sw_poly_line_render(void) const sw_vertex_t *vertices = RLSW.vertexBuffer; int cm1 = RLSW.vertexCounter - 1; - for (int i = 0; i < cm1; i++) sw_line_render((sw_vertex_t[2]){ vertices[i], vertices[i + 1] }); + for (int i = 0; i < cm1; i++) + { + sw_vertex_t verts[2] = { vertices[i], vertices[i + 1] }; + sw_line_render(verts); + } - sw_line_render((sw_vertex_t[2]){ vertices[cm1], vertices[0] }); + sw_vertex_t verts[2] = { vertices[cm1], vertices[0] }; + sw_line_render(verts); } static inline void sw_poly_fill_render(void) @@ -3817,10 +3830,10 @@ bool swInit(int w, int h) swViewport(0, 0, w, h); swScissor(0, 0, w, h); - RLSW.loadedTextures = SW_MALLOC(SW_MAX_TEXTURES*sizeof(sw_texture_t)); + RLSW.loadedTextures = (sw_texture_t *)SW_MALLOC(SW_MAX_TEXTURES*sizeof(sw_texture_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } - RLSW.freeTextureIds = SW_MALLOC(SW_MAX_TEXTURES*sizeof(uint32_t)); + RLSW.freeTextureIds = (uint32_t *)SW_MALLOC(SW_MAX_TEXTURES*sizeof(uint32_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } RLSW.clearColor[0] = 0.0f; @@ -3898,7 +3911,7 @@ void swClose(void) SW_FREE(RLSW.loadedTextures); SW_FREE(RLSW.freeTextureIds); - RLSW = (sw_context_t) { 0 }; + RLSW = CURLY_INIT(sw_context_t) { 0 }; } bool swResizeFramebuffer(int w, int h) @@ -3908,7 +3921,7 @@ bool swResizeFramebuffer(int w, int h) void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels) { - sw_pixelformat_t pFormat = sw_get_pixel_format(format, type); + sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); if (w <= 0) { @@ -3930,26 +3943,26 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, switch (pFormat) { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_copy_to_R5G6B5(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_copy_to_R8G8B8(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_copy_to_R5G6B5(x, y, w, h, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_copy_to_R8G8B8(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, (sw_half_t *)pixels); break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels) { - sw_pixelformat_t pFormat = sw_get_pixel_format(format, type); + sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); if (wSrc <= 0) { @@ -3971,19 +3984,19 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr switch (pFormat) { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_blit_to_R5G6B5(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_blit_to_R8G8B8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_blit_to_R5G6B5(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_blit_to_R8G8B8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float*)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } @@ -4553,7 +4566,7 @@ void swBegin(SWdraw mode) void swEnd(void) { - RLSW.drawMode = 0; + RLSW.drawMode = (SWdraw)0; } void swVertex2i(int x, int y) @@ -4738,9 +4751,9 @@ void swBindArray(SWarray type, void *buffer) { switch (type) { - case SW_VERTEX_ARRAY: RLSW.array.positions = buffer; break; - case SW_TEXTURE_COORD_ARRAY: RLSW.array.texcoords = buffer; break; - case SW_COLOR_ARRAY: RLSW.array.colors = buffer; break; + case SW_VERTEX_ARRAY: RLSW.array.positions = (float *)buffer; break; + case SW_TEXTURE_COORD_ARRAY: RLSW.array.texcoords = (float *)buffer; break; + case SW_COLOR_ARRAY: RLSW.array.colors = (uint8_t *)buffer; break; default: break; } } @@ -4833,7 +4846,7 @@ void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy if (copy) { - int bytes = sw_get_pixel_bytes(pixelFormat); + int bytes = sw_get_pixel_bytes((sw_pixelformat_t)pixelFormat); int size = bytes*width*height; texture->pixels.ptr = SW_MALLOC(size); @@ -4854,7 +4867,7 @@ void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy texture->height = height; texture->wMinus1 = width - 1; texture->hMinus1 = height - 1; - texture->format = pixelFormat; + texture->format = (sw_pixelformat_t)pixelFormat; texture->tx = 1.0f/width; texture->ty = 1.0f/height; texture->copy = copy; @@ -4882,7 +4895,7 @@ void swTexParameteri(int param, int value) return; } - texture->minFilter = value; + texture->minFilter = (SWfilter)value; } break; case SW_TEXTURE_MAG_FILTER: { @@ -4892,7 +4905,7 @@ void swTexParameteri(int param, int value) return; } - texture->magFilter = value; + texture->magFilter = (SWfilter)value; } break; case SW_TEXTURE_WRAP_S: { @@ -4902,7 +4915,7 @@ void swTexParameteri(int param, int value) return; } - texture->sWrap = value; + texture->sWrap = (SWwrap)value; } break; case SW_TEXTURE_WRAP_T: { @@ -4912,7 +4925,7 @@ void swTexParameteri(int param, int value) return; } - texture->tWrap = value; + texture->tWrap = (SWwrap)value; } break; default: RLSW.errCode = SW_INVALID_ENUM; break; } From 12dc0d66758970212eaa323c2e398d8645a229e4 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 23 Oct 2025 08:45:33 -0700 Subject: [PATCH 20/25] Revert undesired change to rcore and restore correct win32 ifdef and max path (#5297) Fixes #5293 also add comment for win32 platform. --- src/rcore.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 350490ad1..e4628e77c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -26,7 +26,8 @@ * - Linux DRM subsystem (KMS mode) * > PLATFORM_ANDROID: * - Android (ARM, ARM64) -* +* > PLATFORM_DESKTOP_WIN32 (Native Win32): +* - Windows (Win32, Win64) * CONFIGURATION: * #define SUPPORT_DEFAULT_FONT (default) * Default font is loaded on window initialization to be available for the user to render simple text. @@ -161,7 +162,10 @@ #endif // Platform specific defines to handle GetApplicationDirectory() -#if (defined(_WIN32) && !defined(PLATFORM_DESKTOP_RGFW)) || (defined(_MSC_VER) && defined(PLATFORM_DESKTOP_RGFW)) +#if defined(_WIN32) + #if !defined(MAX_PATH) + #define MAX_PATH 260 + #endif struct HINSTANCE__; #if defined(__cplusplus) From 39242dba2303cd321e594908c814e08f976d31b4 Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:29:01 +0200 Subject: [PATCH 21/25] [rlsw] Smarter texture management (#5296) * simplified framebuffer management * convert texture to rgba32 * remove float copy/blit * adding SIMD framebuffer read/write This adds SIMD framebuffer read/write, and also texel fetch. This supports SSE2/SSE41 and SISD fallback (also includes ARM NEON support, conceptually identical but still needs testing). * consistency * tweaks * review of `sw_texture_sample_linear` * better quad sorting unrelated to the PR, but at least it's done * ignore some pipeline state in certains context * convention tweaks --------- Co-authored-by: Ray --- src/external/rlsw.h | 1721 ++++++++++++++++++------------------------- 1 file changed, 723 insertions(+), 998 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index aa4803265..626cb2cc5 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -115,16 +115,12 @@ #define SW_GL_FRAMEBUFFER_COPY_BGRA true #endif -#ifndef SW_GL_BINDING_COPY_TEXTURE - #define SW_GL_BINDING_COPY_TEXTURE true -#endif - #ifndef SW_COLOR_BUFFER_BITS - #define SW_COLOR_BUFFER_BITS 24 + #define SW_COLOR_BUFFER_BITS 32 //< 32 (rgba), 16 (rgb packed) or 8 (rgb packed) #endif #ifndef SW_DEPTH_BUFFER_BITS - #define SW_DEPTH_BUFFER_BITS 16 + #define SW_DEPTH_BUFFER_BITS 16 //< 24, 16 or 8 #endif #ifndef SW_MAX_PROJECTION_STACK_SIZE @@ -382,7 +378,7 @@ typedef double GLclampd; #define glDrawArrays(m, o, c) swDrawArrays((m), (o), (c)) #define glGenTextures(c, v) swGenTextures((c), (v)) #define glDeleteTextures(c, v) swDeleteTextures((c), (v)) -#define glTexImage2D(tr, l, if, w, h, b, f, t, p) swTexImage2D((w), (h), (f), (t), SW_GL_BINDING_COPY_TEXTURE, (p)) +#define glTexImage2D(tr, l, if, w, h, b, f, t, p) swTexImage2D((w), (h), (f), (t), (p)) #define glTexParameteri(tr, pname, param) swTexParameteri((pname), (param)) #define glBindTexture(tr, id) swBindTexture((id)) @@ -597,7 +593,7 @@ SWAPI void swDrawArrays(SWdraw mode, int offset, int count); SWAPI void swGenTextures(int count, uint32_t *textures); SWAPI void swDeleteTextures(int count, uint32_t *textures); -SWAPI void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data); +SWAPI void swTexImage2D(int width, int height, SWformat format, SWtype type, const void *data); SWAPI void swTexParameteri(int param, int value); SWAPI void swBindTexture(uint32_t id); @@ -615,6 +611,65 @@ SWAPI void swBindTexture(uint32_t id); #include #include // Required for: floorf(), fabsf() +#if defined(__FMA__) && defined(__AVX2__) +# define SW_HAS_FMA_AVX2 +# include +#endif + +#if defined(__FMA__) && defined(__AVX__) +# define SW_HAS_FMA_AVX +# include +#endif + +#if defined(__AVX2__) +# define SW_HAS_AVX2 +# include +#endif + +#if defined(__AVX__) +# define SW_HAS_AVX +# include +#endif + +#if defined(__SSE4_2__) +# define SW_HAS_SSE42 +# include +#endif + +#if defined(__SSE4_1__) +# define SW_HAS_SSE41 +# include +#endif + +#if defined(__SSSE3__) +# define SW_HAS_SSSE3 +# include +#endif + +#if defined(__SSE3__) +# define SW_HAS_SSE3 +# include +#endif + +#if defined(__SSE2__) +# define SW_HAS_SSE2 +# include +#endif + +#if defined(__SSE__) +# define SW_HAS_SSE +# include +#endif + +#if defined(__ARM_NEON) || defined(__aarch64__) +# if defined(__ARM_FEATURE_FMA) +# define SW_HAS_NEON_FMA +# else +# define SW_HAS_NEON +# endif +# include +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -625,7 +680,69 @@ SWAPI void swBindTexture(uint32_t id); #define SW_COLOR_PIXEL_SIZE (SW_COLOR_BUFFER_BITS/8) #define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS/8) -#define SW_STATE_CHECK(flags) ((RLSW.stateFlags & (flags)) == (flags)) +#if (SW_COLOR_BUFFER_BITS == 8) + #define COLOR_TYPE uint8_t + #define COLOR_IS_PACKED 1 + #define PACK_COLOR(r,g,b) ((((uint8_t)((r)*7+0.5f))&0x07)<<5 | (((uint8_t)((g)*7+0.5f))&0x07)<<2 | ((uint8_t)((b)*3+0.5f))&0x03) + #define UNPACK_R(p) (((p)>>5)&0x07) + #define UNPACK_G(p) (((p)>>2)&0x07) + #define UNPACK_B(p) ((p)&0x03) + #define SCALE_R(v) ((v)*255+3)/7 + #define SCALE_G(v) ((v)*255+3)/7 + #define SCALE_B(v) ((v)*255+1)/3 + #define TO_FLOAT_R(v) ((v)*(1.0f/7.0f)) + #define TO_FLOAT_G(v) ((v)*(1.0f/7.0f)) + #define TO_FLOAT_B(v) ((v)*(1.0f/3.0f)) +#elif (SW_COLOR_BUFFER_BITS == 16) + #define COLOR_TYPE uint16_t + #define COLOR_IS_PACKED 1 + #define PACK_COLOR(r,g,b) ((((uint16_t)((r)*31+0.5f))&0x1F)<<11 | (((uint16_t)((g)*63+0.5f))&0x3F)<<5 | ((uint16_t)((b)*31+0.5f))&0x1F) + #define UNPACK_R(p) (((p)>>11)&0x1F) + #define UNPACK_G(p) (((p)>>5)&0x3F) + #define UNPACK_B(p) ((p)&0x1F) + #define SCALE_R(v) ((v)*255+15)/31 + #define SCALE_G(v) ((v)*255+31)/63 + #define SCALE_B(v) ((v)*255+15)/31 + #define TO_FLOAT_R(v) ((v)*(1.0f/31.0f)) + #define TO_FLOAT_G(v) ((v)*(1.0f/63.0f)) + #define TO_FLOAT_B(v) ((v)*(1.0f/31.0f)) +#else // 32 bits + #define COLOR_TYPE uint8_t + #define COLOR_IS_PACKED 0 +#endif + +#if (SW_DEPTH_BUFFER_BITS == 8) + #define DEPTH_TYPE uint8_t + #define DEPTH_IS_PACKED 1 + #define DEPTH_MAX UINT8_MAX + #define DEPTH_SCALE (1.0f/UINT8_MAX) + #define PACK_DEPTH(d) ((DEPTH_TYPE)((d)*DEPTH_MAX)) + #define UNPACK_DEPTH(p) (p) +#elif (SW_DEPTH_BUFFER_BITS == 16) + #define DEPTH_TYPE uint16_t + #define DEPTH_IS_PACKED 1 + #define DEPTH_MAX UINT16_MAX + #define DEPTH_SCALE (1.0f/UINT16_MAX) + #define PACK_DEPTH(d) ((DEPTH_TYPE)((d)*DEPTH_MAX)) + #define UNPACK_DEPTH(p) (p) +#else // 24 bits + #define DEPTH_TYPE uint8_t + #define DEPTH_IS_PACKED 0 + #define DEPTH_MAX 0xFFFFFF + #define DEPTH_SCALE (1.0f/0xFFFFFF) + #define PACK_DEPTH_0(d) (((uint32_t)((d)*DEPTH_MAX)>>16)&0xFF) + #define PACK_DEPTH_1(d) (((uint32_t)((d)*DEPTH_MAX)>>8)&0xFF) + #define PACK_DEPTH_2(d) ((uint32_t)((d)*DEPTH_MAX)&0xFF) + #define UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) +#endif + +#define GET_COLOR_PTR(ptr, offset) ((void*)((uint8_t*)(ptr) + (offset)*SW_COLOR_PIXEL_SIZE)) +#define GET_DEPTH_PTR(ptr, offset) ((void*)((uint8_t*)(ptr) + (offset)*SW_DEPTH_PIXEL_SIZE)) +#define INC_COLOR_PTR(ptr) ((ptr) = (void*)((uint8_t*)(ptr) + SW_COLOR_PIXEL_SIZE)) +#define INC_DEPTH_PTR(ptr) ((ptr) = (void*)((uint8_t*)(ptr) + SW_DEPTH_PIXEL_SIZE)) + +#define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) +#define SW_STATE_CHECK_EX(state, flags) (((state) & (flags)) == (flags)) #define SW_STATE_SCISSOR_TEST (1 << 0) #define SW_STATE_TEXTURE_2D (1 << 1) @@ -674,16 +791,11 @@ typedef struct { } sw_vertex_t; typedef struct { - // Dirty hack for copied data - // TODO: Rework copied image handling - union { - const void *cptr; // NOTE: Is used for all data reads - void *ptr; // WARNING: Should only be used to allocate and free data - } pixels; + + uint8_t* pixels; // Texture pixels (RGBA32) int width, height; // Dimensions of the texture int wMinus1, hMinus1; // Dimensions minus one - sw_pixelformat_t format; // Pixel format (internal representation) SWfilter minFilter; // Minification filter SWfilter magFilter; // Magnification filter @@ -694,8 +806,6 @@ typedef struct { float tx; // Texel width float ty; // Texel height - bool copy; // Flag indicating whether memory has been allocated - } sw_texture_t; typedef struct { @@ -943,8 +1053,89 @@ static inline void sw_add_vertex_grad_PTCH( out->homogeneous[3] += gradients->homogeneous[3]; } -// Half floating point management functions -static inline uint32_t sw_f16_to_f32_ui(uint16_t h) +static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) +{ +#if defined(SW_HAS_NEON) + float32x4_t values = vld1q_f32(src); + float32x4_t scaled = vmulq_n_f32(values, 255.0f); + scaled = vminq_f32(vmaxq_f32(scaled, vdupq_n_f32(0.0f)), vdupq_n_f32(255.0f)); + uint32x4_t clamped = vcvtq_u32_f32(scaled); + + uint16x4_t narrow16 = vmovn_u32(clamped); + uint8x8_t narrow8 = vmovn_u16(vcombine_u16(narrow16, narrow16)); + + vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8), 0); + +#elif defined(SW_HAS_SSE41) + __m128 values = _mm_loadu_ps(src); + __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); + scaled = _mm_max_ps(_mm_min_ps(scaled, _mm_set1_ps(255.0f)), _mm_setzero_ps()); + __m128i clamped = _mm_cvtps_epi32(scaled); + + clamped = _mm_packus_epi32(clamped, clamped); + clamped = _mm_packus_epi16(clamped, clamped); + *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); + +#elif defined(SW_HAS_SSE2) + __m128 values = _mm_loadu_ps(src); + __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); + scaled = _mm_max_ps(_mm_min_ps(scaled, _mm_set1_ps(255.0f)), _mm_setzero_ps()); + __m128i clamped = _mm_cvtps_epi32(scaled); + + clamped = _mm_packs_epi32(clamped, clamped); + clamped = _mm_packus_epi16(clamped, clamped); + *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); + +#else + for (int i = 0; i < 4; i++) + { + float val = src[i]*255.0f; + val = (val > 255.0f)? 255.0f : val; + val = (val < 0.0f)? 0.0f : val; + dst[i] = (uint8_t)(val + 0.5f); + } +#endif +} + +static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) +{ +#if defined(SW_HAS_NEON) + uint32x4_t bytes = vdupq_n_u32(0); + bytes = vld1q_lane_u32((const uint32_t*)src, bytes, 0); + + uint8x8_t bytes8 = vreinterpret_u8_u32(vget_low_u32(bytes)); + uint16x8_t bytes16 = vmovl_u8(bytes8); + uint32x4_t ints = vmovl_u16(vget_low_u16(bytes16)); + + float32x4_t floats = vcvtq_f32_u32(ints); + floats = vmulq_n_f32(floats, 1.0f/255.0f); + vst1q_f32(dst, floats); + +#elif defined(SW_HAS_SSE41) + __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); + __m128i ints = _mm_cvtepu8_epi32(bytes); + __m128 floats = _mm_cvtepi32_ps(ints); + floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); + _mm_storeu_ps(dst, floats); + +#elif defined(SW_HAS_SSE2) + __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); + bytes = _mm_unpacklo_epi8(bytes, _mm_setzero_si128()); + __m128i ints = _mm_unpacklo_epi16(bytes, _mm_setzero_si128()); + __m128 floats = _mm_cvtepi32_ps(ints); + floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); + _mm_storeu_ps(dst, floats); + +#else + dst[0] = (float)src[0]/255.0f; + dst[1] = (float)src[1]/255.0f; + dst[2] = (float)src[2]/255.0f; + dst[3] = (float)src[3]/255.0f; +#endif +} + +// Half conversion functions +static inline uint32_t sw_half_to_float_ui(uint16_t h) { uint32_t s = (uint32_t)(h & 0x8000) << 16; int32_t em = h & 0x7fff; @@ -962,14 +1153,14 @@ static inline uint32_t sw_f16_to_f32_ui(uint16_t h) return s | r; } -static inline float sw_f16_to_f32(sw_half_t y) +static inline float sw_half_to_float(sw_half_t y) { - union { float f; uint32_t i; } v = { .i = sw_f16_to_f32_ui(y) }; + union { float f; uint32_t i; } v = { .i = sw_half_to_float_ui(y) }; return v.f; } -static inline uint16_t sw_f16_from_f32_ui(uint32_t ui) +static inline uint16_t sw_half_from_float_ui(uint32_t ui) { int32_t s = (ui >> 16) & 0x8000; int32_t em = ui & 0x7fffffff; @@ -989,14 +1180,15 @@ static inline uint16_t sw_f16_from_f32_ui(uint32_t ui) return (uint16_t)(s | h); } -static inline sw_half_t sw_f16_from_f32(float i) +static inline sw_half_t sw_half_from_float(float i) { union { float f; uint32_t i; } v; v.f = i; - return sw_f16_from_f32_ui(v.i); + return sw_half_from_float_ui(v.i); } // Framebuffer management functions + static inline bool sw_framebuffer_load(int w, int h) { int size = w*h; @@ -1005,7 +1197,11 @@ static inline bool sw_framebuffer_load(int w, int h) if (RLSW.framebuffer.color == NULL) return false; RLSW.framebuffer.depth = SW_MALLOC(SW_DEPTH_PIXEL_SIZE*size); - if (RLSW.framebuffer.depth == NULL) return false; + if (RLSW.framebuffer.depth == NULL) + { + SW_FREE(RLSW.framebuffer.color); + return false; + } RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; @@ -1025,11 +1221,15 @@ static inline bool sw_framebuffer_resize(int w, int h) return true; } - void *newColor = SW_REALLOC(RLSW.framebuffer.color, newSize); + void *newColor = SW_REALLOC(RLSW.framebuffer.color, SW_COLOR_PIXEL_SIZE*newSize); if (newColor == NULL) return false; - void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, newSize); - if (newDepth == NULL) return false; + void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, SW_DEPTH_PIXEL_SIZE*newSize); + if (newDepth == NULL) + { + SW_FREE(newColor); + return false; + } RLSW.framebuffer.color = newColor; RLSW.framebuffer.depth = newDepth; @@ -1041,314 +1241,221 @@ static inline bool sw_framebuffer_resize(int w, int h) return true; } -static inline void *sw_framebuffer_get_color_addr(const void *ptr, uint32_t offset) -{ - return (uint8_t *)ptr + offset*SW_COLOR_PIXEL_SIZE; -} - -static inline void sw_framebuffer_inc_color_addr(void **ptr) -{ - *ptr = (void *)(((uint8_t *)*ptr) + SW_COLOR_PIXEL_SIZE); -} - -static inline void sw_framebuffer_inc_const_color_addr(const void **ptr) -{ - *ptr = (const void *)(((const uint8_t *)*ptr) + SW_COLOR_PIXEL_SIZE); -} - -static inline void *sw_framebuffer_get_depth_addr(const void *ptr, uint32_t offset) -{ - return (uint8_t *)ptr + offset*SW_DEPTH_PIXEL_SIZE; -} - -static inline void sw_framebuffer_inc_depth_addr(void* *ptr) -{ - *ptr = (void*)(((uint8_t *)*ptr) + SW_DEPTH_PIXEL_SIZE); -} - -#if (SW_COLOR_BUFFER_BITS == 8) // RGB - (3:3:2) static inline void sw_framebuffer_read_color(float dst[4], const void *src) { - uint8_t pixel = ((uint8_t *)src)[0]; - - dst[0] = ((pixel >> 5) & 0x07)*(1.0f/7.0f); - dst[1] = ((pixel >> 2) & 0x07)*(1.0f/7.0f); - dst[2] = (pixel & 0x03)*(1.0f/3.0f); +#if COLOR_IS_PACKED + COLOR_TYPE pixel = ((COLOR_TYPE*)src)[0]; + dst[0] = TO_FLOAT_R(UNPACK_R(pixel)); + dst[1] = TO_FLOAT_G(UNPACK_G(pixel)); + dst[2] = TO_FLOAT_B(UNPACK_B(pixel)); dst[3] = 1.0f; +#else + sw_float_from_unorm8_simd(dst, src); +#endif } static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) { - uint8_t pixel = ((const uint8_t *)src)[0]; - - uint8_t r = (pixel >> 5) & 0x07; - uint8_t g = (pixel >> 2) & 0x07; - uint8_t b = pixel & 0x03; - - dst[0] = (r*255 + 3)/7; - dst[1] = (g*255 + 3)/7; - dst[2] = (b*255 + 1)/3; +#if COLOR_IS_PACKED + COLOR_TYPE pixel = ((COLOR_TYPE*)src)[0]; + dst[0] = SCALE_R(UNPACK_R(pixel)); + dst[1] = SCALE_G(UNPACK_G(pixel)); + dst[2] = SCALE_B(UNPACK_B(pixel)); dst[3] = 255; +#else + const COLOR_TYPE *p = (const COLOR_TYPE*)src; + dst[0] = p[0]; + dst[1] = p[1]; + dst[2] = p[2]; + dst[3] = p[3]; +#endif } -static inline void sw_framebuffer_write_color(void *dst, const float color[3]) +static inline void sw_framebuffer_write_color(void *dst, const float src[4]) { - uint8_t r = ((uint8_t)(color[0]*UINT8_MAX) >> 5) & 0x07; - uint8_t g = ((uint8_t)(color[1]*UINT8_MAX) >> 5) & 0x07; - uint8_t b = ((uint8_t)(color[2]*UINT8_MAX) >> 6) & 0x03; - - ((uint8_t *)dst)[0] = (r << 5) | (g << 2) | b; +#if COLOR_IS_PACKED + ((COLOR_TYPE*)dst)[0] = PACK_COLOR(src[0], src[1], src[2]); +#else + sw_float_to_unorm8_simd(dst, src); +#endif } -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) +static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[4]) { - uint8_t r8 = (uint8_t)(color[0]*7.0f + 0.5f); - uint8_t g8 = (uint8_t)(color[1]*7.0f + 0.5f); - uint8_t b8 = (uint8_t)(color[2]*3.0f + 0.5f); - - uint8_t packedColor = ((r8 & 0x07) << 5) | ((g8 & 0x07) << 2) | (b8 & 0x03); - - uint8_t *p = (uint8_t *)ptr; - +#if COLOR_IS_PACKED + COLOR_TYPE packed = PACK_COLOR(color[0], color[1], color[2]); + COLOR_TYPE *p = (COLOR_TYPE*)ptr; +#else + COLOR_TYPE r = sw_clampi(color[0]*255, 0, 255); + COLOR_TYPE g = sw_clampi(color[1]*255, 0, 255); + COLOR_TYPE b = sw_clampi(color[2]*255, 0, 255); + COLOR_TYPE a = sw_clampi(color[3]*255, 0, 255); + COLOR_TYPE *p = (COLOR_TYPE*)ptr; +#endif + if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; + int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { - uint8_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = packedColor; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = packedColor; - } -} - -#elif (SW_COLOR_BUFFER_BITS == 16) // RGB - (5:6:5) - -static inline void sw_framebuffer_read_color(float dst[4], const void *src) -{ - uint16_t pixel = ((uint16_t *)src)[0]; - - dst[0] = ((pixel >> 11) & 0x1F)*(1.0f/31.0f); - dst[1] = ((pixel >> 5) & 0x3F)*(1.0f/63.0f); - dst[2] = (pixel & 0x1F)*(1.0f/31.0f); - dst[3] = 1.0f; -} - -static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) -{ - uint16_t pixel = ((const uint16_t *)src)[0]; - - uint8_t r = (pixel >> 11) & 0x1F; - uint8_t g = (pixel >> 5) & 0x3F; - uint8_t b = pixel & 0x1F; - - dst[0] = (r*255 + 15)/31; - dst[1] = (g*255 + 31)/63; - dst[2] = (b*255 + 15)/31; - dst[3] = 255; -} - -static inline void sw_framebuffer_write_color(void *dst, const float color[3]) -{ - uint8_t r = (uint8_t)(color[0]*31.0f + 0.5f) & 0x1F; - uint8_t g = (uint8_t)(color[1]*63.0f + 0.5f) & 0x3F; - uint8_t b = (uint8_t)(color[2]*31.0f + 0.5f) & 0x1F; - - ((uint16_t *)dst)[0] = (r << 11) | (g << 5) | b; -} - -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) -{ - uint16_t r16 = (uint16_t)(color[0]*31.0f + 0.5f); - uint16_t g_16 = (uint16_t)(color[1]*63.0f + 0.5f); - uint16_t b_16 = (uint16_t)(color[2]*31.0f + 0.5f); - - uint16_t packedColor = ((r16 & 0x1F) << 11) | ((g_16 & 0x3F) << 5) | (b_16 & 0x1F); - - uint16_t *p = (uint16_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint16_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = packedColor; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = packedColor; - } -} - -#elif (SW_COLOR_BUFFER_BITS == 24) // RGB - (8:8:8) - -static inline void sw_framebuffer_read_color(float dst[4], const void *src) -{ - dst[0] = ((uint8_t *)src)[0]*(1.0f/255.0f); - dst[1] = ((uint8_t *)src)[1]*(1.0f/255.0f); - dst[2] = ((uint8_t *)src)[2]*(1.0f/255.0f); - dst[3] = 1.0f; -} - -static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) -{ - dst[0] = ((uint8_t *)src)[0]; - dst[1] = ((uint8_t *)src)[1]; - dst[2] = ((uint8_t *)src)[2]; - dst[3] = 255; -} - -static inline void sw_framebuffer_write_color(void *dst, const float color[3]) -{ - ((uint8_t *)dst)[0] = (uint8_t)(color[0]*UINT8_MAX); - ((uint8_t *)dst)[1] = (uint8_t)(color[1]*UINT8_MAX); - ((uint8_t *)dst)[2] = (uint8_t)(color[2]*UINT8_MAX); -} - -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) -{ - uint8_t r = (uint8_t)(color[0]*255.0f); - uint8_t g = (uint8_t)(color[1]*255.0f); - uint8_t b = (uint8_t)(color[2]*255.0f); - - uint8_t *p = (uint8_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint8_t *curPtr = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); - for (int xCount = 0; xCount < wScissor; xCount++) +#if COLOR_IS_PACKED + COLOR_TYPE *row = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++) *row++ = packed; +#else + COLOR_TYPE *row = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); + for (int x = 0; x < w; x++) { - *curPtr++ = r; - *curPtr++ = g; - *curPtr++ = b; + *row++ = r; + *row++ = g; + *row++ = b; + *row++ = a; } +#endif } } else { +#if COLOR_IS_PACKED + for (int i = 0; i < size; i++) *p++ = packed; +#else for (int i = 0; i < size; i++) { *p++ = r; *p++ = g; *p++ = b; + *p++ = a; } +#endif } } -#endif // SW_COLOR_BUFFER_BITS - -#if (SW_DEPTH_BUFFER_BITS == 8) - static inline float sw_framebuffer_read_depth(const void *src) { - return (float)((uint8_t *)src)[0]*(1.0f/UINT8_MAX); +#if DEPTH_IS_PACKED + return ((DEPTH_TYPE*)src)[0]*DEPTH_SCALE; +#else + const DEPTH_TYPE *p = (const DEPTH_TYPE*)src; + uint32_t d = UNPACK_DEPTH(p); + return d*DEPTH_SCALE; +#endif } static inline void sw_framebuffer_write_depth(void *dst, float depth) { - ((uint8_t *)dst)[0] = (uint8_t)(depth*UINT8_MAX); +#if DEPTH_IS_PACKED + ((DEPTH_TYPE*)dst)[0] = PACK_DEPTH(depth); +#else + DEPTH_TYPE *p = (DEPTH_TYPE*)dst; + p[0] = PACK_DEPTH_0(depth); + p[1] = PACK_DEPTH_1(depth); + p[2] = PACK_DEPTH_2(depth); +#endif } static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { - uint8_t d8 = (uint8_t)(value*UINT8_MAX); - uint8_t *p = (uint8_t *)ptr; - +#if DEPTH_IS_PACKED + DEPTH_TYPE d = PACK_DEPTH(value); + DEPTH_TYPE *p = (DEPTH_TYPE*)ptr; +#else + DEPTH_TYPE d0 = PACK_DEPTH_0(value); + DEPTH_TYPE d1 = PACK_DEPTH_1(value); + DEPTH_TYPE d2 = PACK_DEPTH_2(value); + DEPTH_TYPE *p = (DEPTH_TYPE*)ptr; +#endif + if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; + int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { - uint8_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = d8; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = d8; - } -} - -#elif (SW_DEPTH_BUFFER_BITS == 16) - -static inline float sw_framebuffer_read_depth(const void *src) -{ - return (float)((uint16_t *)src)[0]*(1.0f/UINT16_MAX); -} - -static inline void sw_framebuffer_write_depth(void *dst, float depth) -{ - ((uint16_t *)dst)[0] = (uint16_t)(depth*UINT16_MAX); -} - -static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) -{ - uint16_t d16 = (uint16_t)(value*UINT16_MAX); - uint16_t *p = (uint16_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint16_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = d16; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = d16; - } -} - -#elif (SW_DEPTH_BUFFER_BITS == 24) - -static inline float sw_framebuffer_read_depth(const void *src) -{ - uint32_t depth24 = (((uint8_t *)src)[0] << 16) | - (((uint8_t *)src)[1] << 8) | - ((uint8_t *)src)[2]; - - return depth24/(float)0xFFFFFF; -} - -static inline void sw_framebuffer_write_depth(void *dst, float depth) -{ - uint32_t depth24 = (uint32_t)(depth*0xFFFFFF); - - ((uint8_t *)dst)[0] = (depth24 >> 16) & 0xFF; - ((uint8_t *)dst)[1] = (depth24 >> 8) & 0xFF; - ((uint8_t *)dst)[2] = depth24 & 0xFF; -} - -static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) -{ - uint32_t d32 = (uint32_t)(value*UINT32_MAX); - uint8_t d_byte0 = (uint8_t)((d32 >> 16) & 0xFF); - uint8_t d_byte1 = (uint8_t)((d32 >> 8) & 0xFF); - uint8_t d_byte2 = (uint8_t)(d32 & 0xFF); - - uint8_t *p = (uint8_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint8_t *curPtr = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); - for (int xCount = 0; xCount < wScissor; xCount++) +#if DEPTH_IS_PACKED + DEPTH_TYPE *row = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++) *row++ = d; +#else + DEPTH_TYPE *row = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); + for (int x = 0; x < w; x++) { - *curPtr++ = d_byte0; - *curPtr++ = d_byte1; - *curPtr++ = d_byte2; + *row++ = d0; + *row++ = d1; + *row++ = d2; + } +#endif + } + } + else + { +#if DEPTH_IS_PACKED + for (int i = 0; i < size; i++) *p++ = d; +#else + for (int i = 0; i < size; i++) + { + *p++ = d0; + *p++ = d1; + *p++ = d2; + } +#endif + } +} + +static inline void sw_framebuffer_fill(void *colorPtr, void *depthPtr, int size, float color[4], float depth) +{ +#if COLOR_IS_PACKED + COLOR_TYPE packedColor = PACK_COLOR(color[0], color[1], color[2]); + COLOR_TYPE *pColor = (COLOR_TYPE*)colorPtr; +#else + COLOR_TYPE r = sw_clampi(color[0]*255, 0, 255); + COLOR_TYPE g = sw_clampi(color[1]*255, 0, 255); + COLOR_TYPE b = sw_clampi(color[2]*255, 0, 255); + COLOR_TYPE a = sw_clampi(color[3]*255, 0, 255); + COLOR_TYPE *pColor = (COLOR_TYPE*)colorPtr; +#endif + +#if DEPTH_IS_PACKED + DEPTH_TYPE d = PACK_DEPTH(depth); + DEPTH_TYPE *pDepth = (DEPTH_TYPE*)depthPtr; +#else + DEPTH_TYPE d0 = PACK_DEPTH_0(depth); + DEPTH_TYPE d1 = PACK_DEPTH_1(depth); + DEPTH_TYPE d2 = PACK_DEPTH_2(depth); + DEPTH_TYPE *pDepth = (DEPTH_TYPE*)depthPtr; +#endif + + if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) + { + int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; + for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) + { +#if COLOR_IS_PACKED + COLOR_TYPE *rowColor = pColor + y*RLSW.framebuffer.width + RLSW.scMin[0]; +#else + COLOR_TYPE *rowColor = pColor + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); +#endif + +#if DEPTH_IS_PACKED + DEPTH_TYPE *rowDepth = pDepth + y*RLSW.framebuffer.width + RLSW.scMin[0]; +#else + DEPTH_TYPE *rowDepth = pDepth + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); +#endif + + for (int x = 0; x < w; x++) + { +#if COLOR_IS_PACKED + *rowColor++ = packedColor; +#else + *rowColor++ = r; + *rowColor++ = g; + *rowColor++ = b; + *rowColor++ = a; +#endif + +#if DEPTH_IS_PACKED + *rowDepth++ = d; +#else + *rowDepth++ = d0; + *rowDepth++ = d1; + *rowDepth++ = d2; +#endif } } } @@ -1356,140 +1463,27 @@ static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { for (int i = 0; i < size; i++) { - *p++ = d_byte0; - *p++ = d_byte1; - *p++ = d_byte2; +#if COLOR_IS_PACKED + *pColor++ = packedColor; +#else + *pColor++ = r; + *pColor++ = g; + *pColor++ = b; + *pColor++ = a; +#endif + +#if DEPTH_IS_PACKED + *pDepth++ = d; +#else + *pDepth++ = d0; + *pDepth++ = d1; + *pDepth++ = d2; +#endif } } } -#endif // SW_DEPTH_BUFFER_BITS - -static inline void sw_framebuffer_fill(void *colorPtr, void *depthPtr, int size, float color[4], float depth_value) -{ -#if (SW_COLOR_BUFFER_BITS == 8) - // Calculate and pack 3:3:2 color - // Scale color components to the max value for each bit depth and round - uint8_t r8 = (uint8_t)(color[0]*7.0f + 0.5f); - uint8_t g8 = (uint8_t)(color[1]*7.0f + 0.5f); - uint8_t b8 = (uint8_t)(color[2]*3.0f + 0.5f); - // Pack the components into a single byte - uint8_t packedColor = ((r8 & 0x07) << 5) | ((g8 & 0x07) << 2) | (b8 & 0x03); - uint8_t *cptr = (uint8_t *)colorPtr; -#elif (SW_COLOR_BUFFER_BITS == 16) - // Calculate and pack 5:6:5 color - // Scale color components to the max value for each bit depth and round - uint16_t r16 = (uint16_t)(color[0]*31.0f + 0.5f); - uint16_t g16 = (uint16_t)(color[1]*63.0f + 0.5f); - uint16_t b16 = (uint16_t)(color[2]*31.0f + 0.5f); - // Pack the components into a 16-bit value - uint16_t packedColor = ((r16 & 0x1F) << 11) | ((g16 & 0x3F) << 5) | (b16 & 0x1F); - uint16_t *cptr = (uint16_t *)colorPtr; -#elif (SW_COLOR_BUFFER_BITS == 24) - // Calculate 8:8:8 color components - uint8_t r24 = (uint8_t)(color[0]*255.0f); - uint8_t g24 = (uint8_t)(color[1]*255.0f); - uint8_t b24 = (uint8_t)(color[2]*255.0f); - uint8_t *cptr = (uint8_t *)colorPtr; -#endif - -#if (SW_DEPTH_BUFFER_BITS == 8) - // Calculate 8-bit depth - uint8_t d8 = (uint8_t)(depth_value*UINT8_MAX); - uint8_t *dptr = (uint8_t *)depthPtr; -#elif (SW_DEPTH_BUFFER_BITS == 16) - // Calculate 16-bit depth - uint16_t d16 = (uint16_t)(depth_value*UINT16_MAX); - uint16_t *dptr = (uint16_t *)depthPtr; -#elif (SW_DEPTH_BUFFER_BITS == 24) - // Calculate 24-bit depth and pre-calculate bytes - uint32_t d32 = (uint32_t)(depth_value*UINT32_MAX); - uint8_t dByte0 = (uint8_t)((d32 >> 16) & 0xFF); - uint8_t dByte1 = (uint8_t)((d32 >> 8) & 0xFF); - uint8_t dByte2 = (uint8_t)(d32 & 0xFF); - uint8_t *dptr = (uint8_t *)depthPtr; -#endif - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - int rowStartIdx = y*RLSW.framebuffer.width + RLSW.scMin[0]; - - // Calculate starting pointers for the current row within the scissor rectangle - #if (SW_COLOR_BUFFER_BITS == 8) - uint8_t *curCPtr = cptr + rowStartIdx; - #elif (SW_COLOR_BUFFER_BITS == 16) - uint16_t *curCPtr = cptr + rowStartIdx; - #elif (SW_COLOR_BUFFER_BITS == 24) - uint8_t *curCPtr = cptr + 3*rowStartIdx; - #endif - - #if (SW_DEPTH_BUFFER_BITS == 8) - uint8_t *curDPtr = dptr + rowStartIdx; - #elif (SW_DEPTH_BUFFER_BITS == 16) - uint16_t *curDPtr = dptr + rowStartIdx; - #elif (SW_DEPTH_BUFFER_BITS == 24) - uint8_t *curDPtr = dptr + 3*rowStartIdx; - #endif - - // Fill the current row within the scissor rectangle - for (int xCount = 0; xCount < wScissor; xCount++) - { - // Write color - #if (SW_COLOR_BUFFER_BITS == 8) - *curCPtr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 16) - *curCPtr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 24) - *curCPtr++ = r24; - *curCPtr++ = g24; - *curCPtr++ = b24; - #endif - - // Write depth - #if (SW_DEPTH_BUFFER_BITS == 8) - *curDPtr++ = d8; - #elif (SW_DEPTH_BUFFER_BITS == 16) - *curDPtr++ = d16; - #elif (SW_DEPTH_BUFFER_BITS == 24) - *curDPtr++ = dByte0; - *curDPtr++ = dByte1; - *curDPtr++ = dByte2; - #endif - } - } - return; - } - - for (int i = 0; i < size; i++) - { - // Write color - #if (SW_COLOR_BUFFER_BITS == 8) - *cptr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 16) - *cptr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 24) - *cptr++ = r24; - *cptr++ = g24; - *cptr++ = b24; - #endif - - // Write depth - #if (SW_DEPTH_BUFFER_BITS == 8) - *dptr++ = d8; - #elif (SW_DEPTH_BUFFER_BITS == 16) - *dptr++ = d16; - #elif (SW_DEPTH_BUFFER_BITS == 24) - *dptr++ = dByte0; - *dptr++ = dByte1; - *dptr++ = dByte2; - #endif - } -} - -#define DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(name, DST_PTR_T) \ +#define DEFINE_FRAMEBUFFER_COPY_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ { \ const void *src = RLSW.framebuffer.color; \ @@ -1499,23 +1493,13 @@ static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST uint8_t color[4]; \ sw_framebuffer_read_color8(color, src); \ -#define DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(name, DST_PTR_T) \ -static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ -{ \ - const void *src = RLSW.framebuffer.color; \ - \ - for (int iy = y; iy < h; iy++) { \ - for (int ix = x; ix < w; ix++) { \ - float color[4]; \ - sw_framebuffer_read_color(color, src); \ - #define DEFINE_FRAMEBUFFER_COPY_END() \ - sw_framebuffer_inc_const_color_addr(&src); \ + INC_COLOR_PTR(src); \ } \ } \ } -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYSCALE, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(GRAYSCALE, uint8_t) { // NTSC grayscale conversion: Y = 0.299R + 0.587G + 0.114B uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); @@ -1523,7 +1507,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYSCALE, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYALPHA, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(GRAYALPHA, uint8_t) { // Convert RGB to grayscale using NTSC formula uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); @@ -1535,7 +1519,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYALPHA, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G6B5, uint16_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R5G6B5, uint16_t) { // Convert 8-bit RGB to 5:6:5 format uint8_t r5 = (color[0]*31 + 127)/255; @@ -1552,7 +1536,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G6B5, uint16_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R8G8B8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1568,7 +1552,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G5B5A1, uint16_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R5G5B5A1, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; @@ -1585,7 +1569,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G5B5A1, uint16_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R4G4B4A4, uint16_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R4G4B4A4, uint16_t) { uint8_t r4 = (color[0]*15 + 127)/255; uint8_t g4 = (color[1]*15 + 127)/255; @@ -1602,7 +1586,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R4G4B4A4, uint16_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8A8, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R8G8B8A8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1619,87 +1603,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8A8, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32, float) -{ - dst[0] = color[0]; - dst++; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32G32B32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32G32B32A32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - dst[3] = color[3]; - - dst += 4; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16, sw_half_t) -{ - dst[0] = sw_f16_from_f32(color[0]); - dst++; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16G16B16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16G16B16A16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - dst[3] = sw_f16_from_f32(color[3]); - - dst += 4; -} -DEFINE_FRAMEBUFFER_COPY_END() - -#define DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(name, DST_PTR_T) \ +#define DEFINE_FRAMEBUFFER_BLIT_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_blit_to_##name( \ int xDst, int yDst, int wDst, int hDst, \ int xSrc, int ySrc, int wSrc, int hSrc, \ @@ -1708,56 +1612,33 @@ static inline void sw_framebuffer_blit_to_##name( const uint8_t *srcBase = (uint8_t*)RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ - uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ - uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ + uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ + uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ \ for (int dy = 0; dy < hDst; dy++) { \ - uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ + uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ int sy = yFix >> 16; \ \ for (int dx = 0; dx < wDst; dx++) { \ - uint32_t xFix = dx*xScale; \ + uint32_t xFix = dx*xScale; \ int sx = xFix >> 16; \ - const void *srcPtr = sw_framebuffer_get_color_addr(srcBase, sy*fbWidth + sx); \ + const void *srcPtr = GET_COLOR_PTR(srcBase, sy*fbWidth + sx); \ uint8_t color[4]; \ sw_framebuffer_read_color8(color, srcPtr); \ -#define DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(name, DST_PTR_T) \ -static inline void sw_framebuffer_blit_to_##name( \ - int xDst, int yDst, int wDst, int hDst, \ - int xSrc, int ySrc, int wSrc, int hSrc, \ - DST_PTR_T *dst) \ -{ \ - const uint8_t *srcBase = (uint8_t *)RLSW.framebuffer.color; \ - int fbWidth = RLSW.framebuffer.width; \ - \ - uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ - uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ - \ - for (int dy = 0; dy < hDst; dy++) { \ - uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ - int sy = yFix >> 16; \ - \ - for (int dx = 0; dx < wDst; dx++) { \ - uint32_t xFix = dx*xScale; \ - int sx = xFix >> 16; \ - const void *srcPtr = sw_framebuffer_get_color_addr(srcBase, sy*fbWidth + sx); \ - float color[4]; \ - sw_framebuffer_read_color(color, srcPtr); \ - #define DEFINE_FRAMEBUFFER_BLIT_END() \ } \ } \ } -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYSCALE, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(GRAYSCALE, uint8_t) { uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); *dst++ = gray; } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYALPHA, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(GRAYALPHA, uint8_t) { uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); @@ -1768,7 +1649,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYALPHA, uint8_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G6B5, uint16_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R5G6B5, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g6 = (color[1]*63 + 127)/255; @@ -1784,7 +1665,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G6B5, uint16_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R8G8B8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1800,7 +1681,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8, uint8_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G5B5A1, uint16_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R5G5B5A1, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; @@ -1817,7 +1698,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G5B5A1, uint16_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R4G4B4A4, uint16_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R4G4B4A4, uint16_t) { uint8_t r4 = (color[0]*15 + 127)/255; uint8_t g4 = (color[1]*15 + 127)/255; @@ -1834,7 +1715,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R4G4B4A4, uint16_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8A8, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R8G8B8A8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1851,86 +1732,6 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8A8, uint8_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32, uint8_t) -{ - dst[0] = color[0]; - dst++; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32G32B32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32G32B32A32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - dst[3] = color[3]; - - dst += 4; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16, sw_half_t) -{ - dst[0] = sw_f16_from_f32(color[0]); - dst++; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16G16B16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16G16B16A16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - dst[3] = sw_f16_from_f32(color[3]); - - dst += 4; -} -DEFINE_FRAMEBUFFER_BLIT_END() - // Pixel format management functions static inline int sw_get_pixel_format(SWformat format, SWtype type) @@ -1985,210 +1786,178 @@ static inline int sw_get_pixel_format(SWformat format, SWtype type) return SW_PIXELFORMAT_UNKNOWN; } -int sw_get_pixel_bytes(sw_pixelformat_t format) +static inline void sw_get_pixel(uint8_t *color, const void *pixels, uint32_t offset, sw_pixelformat_t format) { - int bpp = 0; - switch (format) { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 1; break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + uint8_t gray = ((const uint8_t*)pixels)[offset]; + color[0] = gray; + color[1] = gray; + color[2] = gray; + color[3] = 255; + break; + } case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + const uint8_t *src = &((const uint8_t*)pixels)[offset*2]; + color[0] = src[0]; + color[1] = src[0]; + color[2] = src[0]; + color[3] = src[1]; + break; + } case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + uint16_t pixel = ((const uint16_t*)pixels)[offset]; + color[0] = ((pixel >> 11) & 0x1F)*255/31; // R (5 bits) + color[1] = ((pixel >> 5) & 0x3F)*255/63; // G (6 bits) + color[2] = (pixel & 0x1F)*255/31; // B (5 bits) + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + const uint8_t *src = &((const uint8_t*)pixels)[offset*3]; + color[0] = src[0]; + color[1] = src[1]; + color[2] = src[2]; + color[3] = 255; + break; + } case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 2; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 4; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 3; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 4; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 4*3; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 4*4; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 2; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 2*3; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 2*4; break; - default: break; - } - - return bpp; -} - -static inline void sw_get_pixel_grayscale(float *color, const void *pixels, uint32_t offset) -{ - float gray = (float)((uint8_t *)pixels)[offset]*(1.0f/255); - - color[0] = gray; - color[1] = gray; - color[2] = gray; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_red_16(float *color, const void *pixels, uint32_t offset) -{ - float value = sw_f16_to_f32(((sw_half_t *)pixels)[offset]); - - color[0] = value; - color[1] = value; - color[2] = value; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_red_32(float *color, const void *pixels, uint32_t offset) -{ - float value = ((float *)pixels)[offset]; - - color[0] = value; - color[1] = value; - color[2] = value; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_grayscale_alpha(float *color, const void *pixels, uint32_t offset) -{ - const uint8_t *pixelData = (const uint8_t *)pixels + 2*offset; - - color[0] = color[1] = color[2] = (float)pixelData[0]*(1.0f/255); - color[3] = (float)pixelData[1]*(1.0f/255); -} - -static inline void sw_get_pixel_rgb_565(float *color, const void *pixels, uint32_t offset) -{ - uint16_t pixel = ((uint16_t *)pixels)[offset]; - - color[0] = (float)((pixel & 0xF800) >> 11)/31; - color[1] = (float)((pixel & 0x7E0) >> 5)/63; - color[2] = (float)(pixel & 0x1F)/31; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgb_888(float *color, const void *pixels, uint32_t offset) -{ - const uint8_t *pixel = (const uint8_t *)pixels + 3*offset; - - color[0] = (float)pixel[0]*(1.0f/255); - color[1] = (float)pixel[1]*(1.0f/255); - color[2] = (float)pixel[2]*(1.0f/255); - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgb_161616(float *color, const void *pixels, uint32_t offset) -{ - const sw_half_t *pixel = (sw_half_t *)pixels + 3*offset; - - color[0] = sw_f16_to_f32(pixel[0]); - color[1] = sw_f16_to_f32(pixel[1]); - color[2] = sw_f16_to_f32(pixel[2]); - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgb_323232(float *color, const void *pixels, uint32_t offset) -{ - const float *pixel = (float *)pixels + 3*offset; - - color[0] = pixel[0]; - color[1] = pixel[1]; - color[2] = pixel[2]; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgba_5551(float *color, const void *pixels, uint32_t offset) -{ - uint16_t pixel = ((uint16_t *)pixels)[offset]; - - color[0] = (float)((pixel & 0xF800) >> 11)/31; - color[1] = (float)((pixel & 0x7C0) >> 6)/31; - color[2] = (float)((pixel & 0x3E) >> 1)/31; - color[3] = (float)(pixel & 0x1); -} - -static inline void sw_get_pixel_rgba_4444(float *color, const void *pixels, uint32_t offset) -{ - uint16_t pixel = ((uint16_t *)pixels)[offset]; - - color[0] = (float)((pixel & 0xF000) >> 12)/15; - color[1] = (float)((pixel & 0xF00) >> 8)/15; - color[2] = (float)((pixel & 0xF0) >> 4)/15; - color[3] = (float)(pixel & 0xF)/15; -} - -static inline void sw_get_pixel_rgba_8888(float *color, const void *pixels, uint32_t offset) -{ - const uint8_t *pixel = (uint8_t *)pixels + 4*offset; - - color[0] = (float)pixel[0]*(1.0f/255); - color[1] = (float)pixel[1]*(1.0f/255); - color[2] = (float)pixel[2]*(1.0f/255); - color[3] = (float)pixel[3]*(1.0f/255); -} - -static inline void sw_get_pixel_rgba_16161616(float *color, const void *pixels, uint32_t offset) -{ - const sw_half_t *pixel = (sw_half_t *)pixels + 4*offset; - - color[0] = sw_f16_to_f32(pixel[0]); - color[1] = sw_f16_to_f32(pixel[1]); - color[2] = sw_f16_to_f32(pixel[2]); - color[3] = sw_f16_to_f32(pixel[3]); -} - -static inline void sw_get_pixel_rgba_32323232(float *color, const void *pixels, uint32_t offset) -{ - const float *pixel = (float *)pixels + 4*offset; - - color[0] = pixel[0]; - color[1] = pixel[1]; - color[2] = pixel[2]; - color[3] = pixel[3]; -} - -static inline void sw_get_pixel(float *color, const void *pixels, uint32_t offset, sw_pixelformat_t format) -{ - switch (format) - { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_get_pixel_grayscale(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_get_pixel_grayscale_alpha(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_get_pixel_rgb_565(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_get_pixel_rgb_888(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_get_pixel_rgba_5551(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_get_pixel_rgba_4444(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_get_pixel_rgba_8888(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_get_pixel_red_32(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_get_pixel_rgb_323232(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_get_pixel_rgba_32323232(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_get_pixel_red_16(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_get_pixel_rgb_161616(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_get_pixel_rgba_16161616(color, pixels, offset); break; - case SW_PIXELFORMAT_UNKNOWN: break; - default: break; + { + uint16_t pixel = ((const uint16_t*)pixels)[offset]; + color[0] = ((pixel >> 11) & 0x1F)*255/31; // R (5 bits) + color[1] = ((pixel >> 6) & 0x1F)*255/31; // G (5 bits) + color[2] = ((pixel >> 1) & 0x1F)*255/31; // B (5 bits) + color[3] = (pixel & 0x01)*255; // A (1 bit) + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + uint16_t pixel = ((const uint16_t*)pixels)[offset]; + color[0] = ((pixel >> 12) & 0x0F)*255/15; // R (4 bits) + color[1] = ((pixel >> 8) & 0x0F)*255/15; // G (4 bits) + color[2] = ((pixel >> 4) & 0x0F)*255/15; // B (4 bits) + color[3] = (pixel & 0x0F)*255/15; // A (4 bits) + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + const uint8_t *src = &((const uint8_t*)pixels)[offset*4]; + color[0] = src[0]; + color[1] = src[1]; + color[2] = src[2]; + color[3] = src[3]; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R32: + { + float val = ((const float*)pixels)[offset]; + uint8_t gray = (uint8_t)(val*255.0f); + color[0] = gray; + color[1] = gray; + color[2] = gray; + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + const float *src = &((const float*)pixels)[offset*3]; + color[0] = (uint8_t)(src[0]*255.0f); + color[1] = (uint8_t)(src[1]*255.0f); + color[2] = (uint8_t)(src[2]*255.0f); + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + const float *src = &((const float*)pixels)[offset*4]; + color[0] = (uint8_t)(src[0]*255.0f); + color[1] = (uint8_t)(src[1]*255.0f); + color[2] = (uint8_t)(src[2]*255.0f); + color[3] = (uint8_t)(src[3]*255.0f); + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R16: + { + uint16_t val = ((const uint16_t*)pixels)[offset]; + uint8_t gray = sw_half_to_float(val)/255.0f; + color[0] = gray; + color[1] = gray; + color[2] = gray; + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + const uint16_t *src = &((const uint16_t*)pixels)[offset*3]; + color[0] = sw_half_to_float(src[0])/255.0f; + color[1] = sw_half_to_float(src[1])/255.0f; + color[2] = sw_half_to_float(src[2])/255.0f; + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + const uint16_t *src = &((const uint16_t*)pixels)[offset*4]; + color[0] = sw_half_to_float(src[0])/255.0f; + color[1] = sw_half_to_float(src[1])/255.0f; + color[2] = sw_half_to_float(src[2])/255.0f; + color[3] = sw_half_to_float(src[3])/255.0f; + break; + } + case SW_PIXELFORMAT_UNKNOWN: + default: + { + color[0] = 0; + color[1] = 0; + color[2] = 0; + color[3] = 0; + break; + } } } // Texture sampling functionality +static inline void sw_texture_fetch(float* color, const sw_texture_t* tex, int x, int y) +{ + sw_float_from_unorm8_simd(color, &tex->pixels[4*(y*tex->width + x)]); +} + static inline void sw_texture_sample_nearest(float *color, const sw_texture_t *tex, float u, float v) { u = (tex->sWrap == SW_REPEAT)? sw_fract(u) : sw_saturate(u); v = (tex->tWrap == SW_REPEAT)? sw_fract(v) : sw_saturate(v); - int x = u*tex->width, y = v*tex->height; + int x = u*tex->width; + int y = v*tex->height; - sw_get_pixel(color, tex->pixels.cptr, y*tex->width + x, tex->format); + sw_texture_fetch(color, tex, x, y); } static inline void sw_texture_sample_linear(float *color, const sw_texture_t *tex, float u, float v) { - // TODO: REVIEW: With a bit more cleverness we could clearly reduce the + // TODO: With a bit more cleverness we could clearly reduce the // number of operations here, but for now it works fine. - float xf = u*tex->width - 0.5f; - float yf = v*tex->height - 0.5f; + float xf = (u*tex->width) - 0.5f; + float yf = (v*tex->height) - 0.5f; - int x0 = (int)floorf(xf); - int y0 = (int)floorf(yf); + float fx = sw_fract(xf); + float fy = sw_fract(yf); - float fx = xf - x0; - float fy = yf - y0; + int x0 = (int)xf; + int y0 = (int)yf; int x1 = x0 + 1; int y1 = y0 + 1; + // NOTE: If the textures are POT we could avoid the division for SW_REPEAT + if (tex->sWrap == SW_CLAMP) { x0 = (x0 > tex->wMinus1)? tex->wMinus1 : x0; @@ -2212,10 +1981,10 @@ static inline void sw_texture_sample_linear(float *color, const sw_texture_t *te } float c00[4], c10[4], c01[4], c11[4]; - sw_get_pixel(c00, tex->pixels.cptr, y0*tex->width + x0, tex->format); - sw_get_pixel(c10, tex->pixels.cptr, y0*tex->width + x1, tex->format); - sw_get_pixel(c01, tex->pixels.cptr, y1*tex->width + x0, tex->format); - sw_get_pixel(c11, tex->pixels.cptr, y1*tex->width + x1, tex->format); + sw_texture_fetch(c00, tex, x0, y0); + sw_texture_fetch(c10, tex, x1, y0); + sw_texture_fetch(c01, tex, x0, y1); + sw_texture_fetch(c11, tex, x1, y1); for (int i = 0; i < 4; i++) { @@ -2512,23 +2281,23 @@ static inline void sw_triangle_clip_and_project(void) // Calculation of the reciprocal of W for normalization // as well as perspective-correct attributes - const float invW = 1.0f/v->homogeneous[3]; - v->homogeneous[3] = invW; + const float wRcp = 1.0f/v->homogeneous[3]; + v->homogeneous[3] = wRcp; // Division of XYZ coordinates by weight - v->homogeneous[0] *= invW; - v->homogeneous[1] *= invW; - v->homogeneous[2] *= invW; + v->homogeneous[0] *= wRcp; + v->homogeneous[1] *= wRcp; + v->homogeneous[2] *= wRcp; // Division of texture coordinates (perspective-correct) - v->texcoord[0] *= invW; - v->texcoord[1] *= invW; + v->texcoord[0] *= wRcp; + v->texcoord[1] *= wRcp; // Division of colors (perspective-correct) - v->color[0] *= invW; - v->color[1] *= invW; - v->color[2] *= invW; - v->color[3] *= invW; + v->color[0] *= wRcp; + v->color[1] *= wRcp; + v->color[2] *= wRcp; + v->color[3] *= wRcp; // Transformation to screen space sw_project_ndc_to_screen(v->screen, v->homogeneous); @@ -2583,8 +2352,8 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, } \ \ /* Pre-calculate the starting pointers for the framebuffer row */ \ - void *cptr = sw_framebuffer_get_color_addr(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ - void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ + void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ + void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ \ /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ @@ -2624,12 +2393,7 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, srcColor); \ - dstColor[0] = sw_saturate(dstColor[0]); \ - dstColor[1] = sw_saturate(dstColor[1]); \ - dstColor[2] = sw_saturate(dstColor[2]); \ - \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else \ @@ -2651,8 +2415,8 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, v += dvDx; \ } \ \ - sw_framebuffer_inc_color_addr(&cptr); \ - sw_framebuffer_inc_depth_addr(&dptr); \ + INC_COLOR_PTR(cptr); \ + INC_DEPTH_PTR(dptr); \ } \ } @@ -2777,13 +2541,17 @@ static inline void sw_triangle_render(void) } \ } - if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) + uint32_t state = RLSW.stateFlags; + if (RLSW.currentTexture == 0) state &= ~SW_STATE_TEXTURE_2D; + if ((RLSW.srcFactor == SW_ONE) && (RLSW.dstFactor == SW_ZERO)) state &= ~SW_STATE_BLEND; + + if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) else TRIANGLE_RASTER(sw_triangle_raster) #undef TRIANGLE_RASTER @@ -2859,23 +2627,23 @@ static inline void sw_quad_clip_and_project(void) // Calculation of the reciprocal of W for normalization // as well as perspective-correct attributes - const float invW = 1.0f/v->homogeneous[3]; - v->homogeneous[3] = invW; + const float wRcp = 1.0f/v->homogeneous[3]; + v->homogeneous[3] = wRcp; // Division of XYZ coordinates by weight - v->homogeneous[0] *= invW; - v->homogeneous[1] *= invW; - v->homogeneous[2] *= invW; + v->homogeneous[0] *= wRcp; + v->homogeneous[1] *= wRcp; + v->homogeneous[2] *= wRcp; // Division of texture coordinates (perspective-correct) - v->texcoord[0] *= invW; - v->texcoord[1] *= invW; + v->texcoord[0] *= wRcp; + v->texcoord[1] *= wRcp; // Division of colors (perspective-correct) - v->color[0] *= invW; - v->color[1] *= invW; - v->color[2] *= invW; - v->color[3] *= invW; + v->color[0] *= wRcp; + v->color[1] *= wRcp; + v->color[2] *= wRcp; + v->color[3] *= wRcp; // Transformation to screen space sw_project_ndc_to_screen(v->screen, v->homogeneous); @@ -2908,76 +2676,40 @@ static inline bool sw_quad_is_axis_aligned(void) static inline void sw_quad_sort_cw(const sw_vertex_t* *output) { - // Sort 4 quad vertices into clockwise order with fixed layout: - // - // v0 -- v1 - // | | - // v3 -- v2 - // - // The goal is: - // - v0: top-left (minimum Y, then minimum X) - // - v1: top-right (minimum Y row, maximum X) - // - v2: bottom-right (maximum Y, maximum X) - // - v3: bottom-left (maximum Y, minimum X) const sw_vertex_t *input = RLSW.vertexBuffer; - // Separate vertices into top and bottom based on Y-coordinate - const sw_vertex_t *top[2] = {NULL, NULL}; - const sw_vertex_t *bottom[2] = {NULL, NULL}; - int topCount = 0, bottomCount = 0; + // Calculate the centroid of the quad + float cx = (input[0].screen[0] + input[1].screen[0] + + input[2].screen[0] + input[3].screen[0])*0.25f; + float cy = (input[0].screen[1] + input[1].screen[1] + + input[2].screen[1] + input[3].screen[1])*0.25f; - // Find minimum and maximum Y - float minY = input[0].screen[1]; - float maxY = input[0].screen[1]; + // Calculate the angle of each vertex relative to the center + // and assign them directly to their correct position + const sw_vertex_t *corners[4] = { 0 }; - for (int i = 1; i < 4; i++) - { - if (input[i].screen[1] < minY) minY = input[i].screen[1]; - if (input[i].screen[1] > maxY) maxY = input[i].screen[1]; - } - - // Separate vertices based on Y-coordinate for (int i = 0; i < 4; i++) { - if ((input[i].screen[1] == minY) && (topCount < 2)) top[topCount++] = &input[i]; - else if ((input[i].screen[1] == maxY) && (bottomCount < 2)) bottom[bottomCount++] = &input[i]; + float dx = input[i].screen[0] - cx; + float dy = input[i].screen[1] - cy; + + // Determine the quadrant (clockwise from top-left) + // top-left: dx < 0, dy < 0 + // top-right: dx >= 0, dy < 0 + // bottom-right: dx >= 0, dy >= 0 + // bottom-left: dx < 0, dy >= 0 + + int idx; + if (dy < 0) idx = (dx < 0)? 0 : 1; // Top row + else idx = (dx < 0)? 3 : 2; // Bottom row + + corners[idx] = &input[i]; } - // If we don't have enough top/bottom vertices (e.g., Y values are all different), - // classify vertices as top or bottom based on whether they're closer to minY or maxY - for (int i = 0; i < 4; i++) - { - if ((topCount < 2) && (&input[i] != top[0]) && (&input[i] != bottom[0]) && (&input[i] != bottom[1])) - { - if (fabsf(input[i].screen[1] - minY) <= fabsf(input[i].screen[1] - maxY)) top[topCount++] = &input[i]; - } - if ((bottomCount < 2) && (&input[i] != top[0]) && (&input[i] != top[1]) && (&input[i] != bottom[0])) - { - if (fabsf(input[i].screen[1] - maxY) < fabsf(input[i].screen[1] - minY)) bottom[bottomCount++] = &input[i]; - } - } - - // Sort top vertices by X (left to right) - if ((topCount == 2) && (top[0]->screen[0] > top[1]->screen[0])) - { - const sw_vertex_t *temp = top[0]; - top[0] = top[1]; - top[1] = temp; - } - - // Sort bottom vertices by X (left to right) - if ((bottomCount == 2) && (bottom[0]->screen[0] > bottom[1]->screen[0])) - { - const sw_vertex_t *temp = bottom[0]; - bottom[0] = bottom[1]; - bottom[1] = temp; - } - - // Assign vertices in clockwise order as per the required layout - output[0] = top[0]; // v0: top-left - output[1] = top[topCount-1]; // v1: top-right - output[2] = bottom[bottomCount-1]; // v2: bottom-right - output[3] = bottom[0]; // v3: bottom-left + output[0] = corners[0]; // top-left + output[1] = corners[1]; // top-right + output[2] = corners[2]; // bottom-right + output[3] = corners[3]; // bottom-left } // TODO: REVIEW: Could a perfectly aligned quad, where one of the four points has a different depth, @@ -3052,8 +2784,8 @@ static inline void FUNC_NAME(void) \ for (int y = yMin; y < yMax; y++) \ { \ - void *cptr = sw_framebuffer_get_color_addr(cDstBase, y*wDst + xMin); \ - void *dptr = sw_framebuffer_get_depth_addr(dDstBase, y*wDst + xMin); \ + void *cptr = GET_COLOR_PTR(cDstBase, y*wDst + xMin); \ + void *dptr = GET_DEPTH_PTR(dDstBase, y*wDst + xMin); \ \ float z = zScanline; \ float u = uScanline; \ @@ -3101,13 +2833,7 @@ static inline void FUNC_NAME(void) { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, srcColor); \ - dstColor[0] = sw_saturate(dstColor[0]); \ - dstColor[1] = sw_saturate(dstColor[1]); \ - dstColor[2] = sw_saturate(dstColor[2]); \ - dstColor[3] = sw_saturate(dstColor[3]); \ - \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else sw_framebuffer_write_color(cptr, srcColor); \ @@ -3125,8 +2851,8 @@ static inline void FUNC_NAME(void) v += tcDx[1]; \ } \ \ - sw_framebuffer_inc_color_addr(&cptr); \ - sw_framebuffer_inc_depth_addr(&dptr); \ + INC_COLOR_PTR(cptr); \ + INC_DEPTH_PTR(dptr); \ } \ \ zScanline += zDy; \ @@ -3163,15 +2889,19 @@ static inline void sw_quad_render(void) if (RLSW.vertexCounter < 3) return; + uint32_t state = RLSW.stateFlags; + if (RLSW.currentTexture == 0) state &= ~SW_STATE_TEXTURE_2D; + if ((RLSW.srcFactor == SW_ONE) && (RLSW.dstFactor == SW_ZERO)) state &= ~SW_STATE_BLEND; + if ((RLSW.vertexCounter == 4) && sw_quad_is_axis_aligned()) { - if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_TEX_DEPTH(); - else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_quad_raster_axis_aligned_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_DEPTH(); - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) sw_quad_raster_axis_aligned_TEX(); + if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_TEX_DEPTH(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_BLEND)) sw_quad_raster_axis_aligned_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_DEPTH(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D)) sw_quad_raster_axis_aligned_TEX(); else sw_quad_raster_axis_aligned(); return; } @@ -3189,13 +2919,13 @@ static inline void sw_quad_render(void) } \ } - if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) + if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) else TRIANGLE_RASTER(sw_triangle_raster) #undef TRIANGLE_RASTER @@ -3310,7 +3040,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ int dy = y2 - y1; \ \ /* Handling of lines that are more horizontal or vertical */ \ - if ((dx == 0) && (dy == 0)) \ + if ((dx == 0) && (dy == 0)) \ { \ /* TODO: A point should be rendered here */ \ return; \ @@ -3366,8 +3096,8 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ for (int i = 0; i != longLen; i += sgnInc) \ { \ int offset = (y1 + i)*fbWidth + (x1 + (j >> 16)); \ - void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ - void *cptr; \ + void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ + void *cptr = NULL; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3377,13 +3107,12 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = GET_COLOR_PTR(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ @@ -3403,8 +3132,8 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ for (int i = 0; i != longLen; i += sgnInc) \ { \ int offset = (y1 + (j >> 16))*fbWidth + (x1 + i); \ - void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ - void *cptr; \ + void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ + void *cptr = NULL; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3414,17 +3143,16 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = GET_COLOR_PTR(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ - else sw_framebuffer_write_color(cptr, color); \ + else sw_framebuffer_write_color(cptr, color); \ \ discardB: \ j += decInc; \ @@ -3570,7 +3298,7 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ \ int offset = y*RLSW.framebuffer.width + x; \ \ - void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, offset); \ + void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, offset); \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3580,13 +3308,12 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - void *cptr = sw_framebuffer_get_color_addr(RLSW.framebuffer.color, offset); \ + void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ @@ -3724,7 +3451,7 @@ static inline bool sw_is_texture_valid(uint32_t id) if (id == 0) valid = false; else if (id >= SW_MAX_TEXTURES) valid = false; - else if (RLSW.loadedTextures[id].pixels.cptr == 0) valid = false; + else if (RLSW.loadedTextures[id].pixels == NULL) valid = false; return true; } @@ -3872,26 +3599,24 @@ bool swInit(int w, int h) RLSW.polyMode = SW_FILL; RLSW.cullFace = SW_BACK; - static const float defTex[3*2*2] = { - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, + static uint32_t defaultTex[3*2*2] = { + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF }; - RLSW.loadedTextures[0].pixels.cptr = defTex; + RLSW.loadedTextures[0].pixels = (uint8_t*)defaultTex; RLSW.loadedTextures[0].width = 2; RLSW.loadedTextures[0].height = 2; RLSW.loadedTextures[0].wMinus1 = 1; RLSW.loadedTextures[0].hMinus1 = 1; - RLSW.loadedTextures[0].format = SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32; RLSW.loadedTextures[0].minFilter = SW_NEAREST; RLSW.loadedTextures[0].magFilter = SW_NEAREST; RLSW.loadedTextures[0].sWrap = SW_REPEAT; RLSW.loadedTextures[0].tWrap = SW_REPEAT; RLSW.loadedTextures[0].tx = 0.5f; RLSW.loadedTextures[0].ty = 0.5f; - RLSW.loadedTextures[0].copy = false; RLSW.loadedTextureCount = 1; @@ -3900,10 +3625,13 @@ bool swInit(int w, int h) void swClose(void) { + // NOTE: Starts at texture 1, texture 0 does not have to be freed for (int i = 1; i < RLSW.loadedTextureCount; i++) { - sw_texture_t *texture = &RLSW.loadedTextures[i]; - if (sw_is_texture_valid(i) && texture->copy) SW_FREE(texture->pixels.ptr); + if (sw_is_texture_valid(i)) + { + SW_FREE(RLSW.loadedTextures[i].pixels); + } } SW_FREE(RLSW.framebuffer.color); @@ -3950,13 +3678,16 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, (uint8_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, (sw_half_t *)pixels); break; - default: RLSW.errCode = SW_INVALID_ENUM; break; + // Below: not implemented + case SW_PIXELFORMAT_UNCOMPRESSED_R32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + case SW_PIXELFORMAT_UNCOMPRESSED_R16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + default: + RLSW.errCode = SW_INVALID_ENUM; + break; } } @@ -3991,13 +3722,16 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float*)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; - default: RLSW.errCode = SW_INVALID_ENUM; break; + // Below: not implemented + case SW_PIXELFORMAT_UNCOMPRESSED_R32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + case SW_PIXELFORMAT_UNCOMPRESSED_R16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + default: + RLSW.errCode = SW_INVALID_ENUM; + break; } } @@ -4814,17 +4548,14 @@ void swDeleteTextures(int count, uint32_t *textures) continue; } - if (RLSW.loadedTextures[textures[i]].copy) - { - SW_FREE(RLSW.loadedTextures[textures[i]].pixels.ptr); - } + SW_FREE(RLSW.loadedTextures[textures[i]].pixels); - RLSW.loadedTextures[textures[i]].pixels.cptr = NULL; + RLSW.loadedTextures[textures[i]].pixels = NULL; RLSW.freeTextureIds[RLSW.freeTextureIdCount++] = textures[i]; } } -void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data) +void swTexImage2D(int width, int height, SWformat format, SWtype type, const void *data) { uint32_t id = RLSW.currentTexture; @@ -4844,33 +4575,27 @@ void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy sw_texture_t *texture = &RLSW.loadedTextures[id]; - if (copy) + int size = width*height; + texture->pixels = SW_MALLOC(4*size); + + if (texture->pixels == NULL) { - int bytes = sw_get_pixel_bytes((sw_pixelformat_t)pixelFormat); - int size = bytes*width*height; - texture->pixels.ptr = SW_MALLOC(size); + RLSW.errCode = SW_STACK_OVERFLOW; // WARNING: Out of memory... + return; + } - if (texture->pixels.ptr == NULL) - { - RLSW.errCode = SW_STACK_OVERFLOW; // WARING: Out of memory... - return; - } - - for (int i = 0; i < size; i++) - { - ((uint8_t *)texture->pixels.ptr)[i] = ((uint8_t *)data)[i]; - } + for (int i = 0; i < size; i++) + { + uint32_t *dst = &((uint32_t*)texture->pixels)[i]; + sw_get_pixel((uint8_t*)dst, data, i, pixelFormat); } - else texture->pixels.cptr = data; texture->width = width; texture->height = height; texture->wMinus1 = width - 1; texture->hMinus1 = height - 1; - texture->format = (sw_pixelformat_t)pixelFormat; texture->tx = 1.0f/width; texture->ty = 1.0f/height; - texture->copy = copy; } void swTexParameteri(int param, int value) @@ -4939,7 +4664,7 @@ void swBindTexture(uint32_t id) return; } - if (RLSW.loadedTextures[id].pixels.cptr == NULL) + if (RLSW.loadedTextures[id].pixels == NULL) { RLSW.errCode = SW_INVALID_OPERATION; return; From 4bb8c89084930862b70042845166739cf058e883 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 25 Oct 2025 00:49:31 -0700 Subject: [PATCH 22/25] Don't undefine the versions of LoadImage that don't conflict with raylib so that the Win32 platform can use them. (#5299) --- src/platforms/rcore_desktop_win32.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 9b66f8c4b..5b67c72b0 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -49,8 +49,6 @@ #define CloseWindow CloseWindowWin32 #define Rectangle RectangleWin32 #define ShowCursor ShowCursorWin32 -#define LoadImageA LoadImageAWin32 -#define LoadImageW LoadImageWin32 #define DrawTextA DrawTextAWin32 #define DrawTextW DrawTextWin32 #define DrawTextExA DrawTextExAWin32 @@ -63,8 +61,6 @@ #undef Rectangle // raylib symbol collision #undef ShowCursor // raylib symbol collision #undef LoadImage // raylib symbol collision -#undef LoadImageA -#undef LoadImageW #undef DrawText // raylib symbol collision #undef DrawTextA #undef DrawTextW @@ -1529,7 +1525,7 @@ int InitPlatform(void) // Load user-provided icon if available // NOTE: raylib resource file defaults to GLFW_ICON id, so looking for same identifier windowClass.hIcon = LoadImageW(hInstance, L"GLFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); - if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, (LPCWSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); // Register window class result = (int)RegisterClassExW(&windowClass); From dbcc5081964cb2413b32eecf2135d73e32420285 Mon Sep 17 00:00:00 2001 From: themushroompirates <59015901+themushroompirates@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:41:39 +0200 Subject: [PATCH 23/25] [examples] Added `models_decals` (#5298) * Added decal models example * Code formatting to match Raylib conventions --- examples/models/models_decals.c | 573 +++ examples/models/models_decals.png | Bin 0 -> 62387 bytes .../models/resources/models/obj/character.mtl | 12 + .../models/resources/models/obj/character.obj | 3454 +++++++++++++++++ .../models/obj/character_diffuse.png | Bin 0 -> 41705 bytes examples/models/resources/raylib_logo.png | Bin 0 -> 3672 bytes 6 files changed, 4039 insertions(+) create mode 100644 examples/models/models_decals.c create mode 100644 examples/models/models_decals.png create mode 100644 examples/models/resources/models/obj/character.mtl create mode 100644 examples/models/resources/models/obj/character.obj create mode 100644 examples/models/resources/models/obj/character_diffuse.png create mode 100644 examples/models/resources/raylib_logo.png diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c new file mode 100644 index 000000000..81bbd2796 --- /dev/null +++ b/examples/models/models_decals.c @@ -0,0 +1,573 @@ +/******************************************************************************************* +* +* raylib [models] example - decals +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.6-dev +* +* Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5) +* Based on previous work by @mrdoob +* +* 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) 2025 JP Mortiboys (@themushroompirates) and Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" + +#include + +#undef FLT_MAX +#define FLT_MAX 340282346638528859811704183484516925440.0f // Maximum value of a float, from bit pattern 01111111011111111111111111111111 + +#define MAX_DECALS 256 + +typedef struct MeshBuilder { + int vertexCount; + int vertexCapacity; + Vector3 *vertices; + Vector2 *uvs; +} MeshBuilder; + +// Utility functions +void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); +void FreeMeshBuilder(MeshBuilder *mb); +Mesh BuildMesh(MeshBuilder *mb); + +Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); + +Mesh GenMeshDecal(Mesh inputMesh, Ray ray); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [models] example - decals"); + + // 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, 1.0f, 0.0f }; // Camera looking at point + camera.up = (Vector3){ 0.0f, 1.6f, 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 character model + Model model = LoadModel("resources/models/obj/character.obj"); + + // Apply character skin + Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); // Load model texture + SetTextureFilter(modelTexture, TEXTURE_FILTER_BILINEAR); + model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; // Set model diffuse texture + + + BoundingBox modelBBox = GetMeshBoundingBox(model.meshes[0]); // Get mesh bounding box + + camera.target = Vector3Lerp(modelBBox.min, modelBBox.max, 0.5f); + camera.position = Vector3Scale(modelBBox.max, 1.0f); + camera.position.x *= 0.1f; + + float modelSize = fminf( + fminf(fabsf(modelBBox.max.x - modelBBox.min.x), + fabsf(modelBBox.max.y - modelBBox.min.y)), + fabsf(modelBBox.max.z - modelBBox.min.z) + ); + + camera.position = (Vector3){ 0, modelBBox.max.y * 1.2f, modelSize * 3.0f }; + + float decalSize = modelSize * .25f; + float decalOffset = 0.01f; + + Model placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize)); + placementCube.materials[0].maps[0].color = LIME; + + Material decalMaterial = LoadMaterialDefault(); + decalMaterial.maps[0].color = YELLOW; + + Image decalImage = LoadImage("resources/raylib_logo.png"); + ImageResizeNN(&decalImage, decalImage.width / 4, decalImage.height / 4); + Texture decalTexture = LoadTextureFromImage(decalImage); + UnloadImage(decalImage); + + SetTextureFilter(decalTexture, TEXTURE_FILTER_BILINEAR); + decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture; + decalMaterial.maps[MATERIAL_MAP_DIFFUSE].color = RAYWHITE; + + // We're going to use these to build up our decal meshes + // They'll resize automatically as we go, we'll free them at the end + MeshBuilder mesh_builders[2] = { {0}, {0} }; + + bool showModel = true; + Model decalModels[MAX_DECALS] = {0}; + int decalCount = 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 + { + // Update + //---------------------------------------------------------------------------------- + + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { + UpdateCamera(&camera, CAMERA_THIRD_PERSON); + } + + if (IsKeyPressed(KEY_SPACE)) { + showModel = !showModel; + } + + // Display information about closest hit + RayCollision collision = { 0 }; + collision.distance = FLT_MAX; + collision.hit = false; + + // Get mouse ray + Ray ray = GetScreenToWorldRay(GetMousePosition(), camera); + + // Check ray collision against bounding box first, before trying the full ray-mesh test + RayCollision boxHitInfo = GetRayCollisionBox(ray, modelBBox); + + if ((boxHitInfo.hit) && decalCount < MAX_DECALS) + { + // Check ray collision against model meshes + RayCollision meshHitInfo = { 0 }; + for (int m = 0; m < model.meshCount; m++) + { + // NOTE: We consider the model.transform for the collision check but + // it can be checked against any transform Matrix, used when checking against same + // model drawn multiple times with multiple transforms + meshHitInfo = GetRayCollisionMesh(ray, model.meshes[m], model.transform); + if (meshHitInfo.hit) + { + // Save the closest hit mesh + if ((!collision.hit) || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; + } + } + + if (meshHitInfo.hit) + { + collision = meshHitInfo; + } + } + + // -------------- + + if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && decalCount < MAX_DECALS) + { + + // Create the transformation to project the decal + Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); + Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); + + // Spin the placement around a bit + splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD * ((float)GetRandomValue(-180, 180)))); + + Matrix splatInv = MatrixInvert(splat); + + // Reset the mesh builders + mesh_builders[0].vertexCount = 0; + mesh_builders[1].vertexCount = 0; + + // We'll be flip-flopping between the two mesh builders + // Reading from one and writing to the other, then swapping + int mb_index = 0; + + // First pass, just get any triangle inside the bounding box + // (for each mesh of the model) + for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) { + Mesh mesh = model.meshes[meshIndex]; + for (int tri = 0; tri < mesh.triangleCount; tri++) + { + Vector3 vertices[3]; + + // The way we calculate the vertices of the mesh triangle + // depend on whether the mesh vertices are indexed or not + if (mesh.indices == 0) + { + for (int v = 0; v < 3; v++) { + vertices[v] = (Vector3) { + mesh.vertices[3*3*tri + 3*v + 0], + mesh.vertices[3*3*tri + 3*v + 1], + mesh.vertices[3*3*tri + 3*v + 2] + }; + } + } + else + { + for (int v = 0; v < 3; v++) + { + vertices[v] = (Vector3) { + mesh.vertices[ 3*mesh.indices[3*tri+0] + v], + mesh.vertices[ 3*mesh.indices[3*tri+1] + v], + mesh.vertices[ 3*mesh.indices[3*tri+2] + v] + }; + } + } + + // Transform all 3 vertices of the triangle + // and check if they are inside our decal box + int insideCount = 0; + for (int i = 0; i < 3; i++) + { + // To splat space + Vector3 v = Vector3Transform(vertices[i], splat); + + if (fabsf(v.x) 0) + { + AddTriangleToMeshBuilder(&mesh_builders[mb_index], vertices); + } + } + } + + // Clipping time! We need to clip against all 6 directions + Vector3 planes[6] = { + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 } + }; + for (int face = 0; face < 6; face++) + { + // Swap current model builder (so we read from the one we just wrote to) + mb_index = 1 - mb_index; + + MeshBuilder *inMesh = &mesh_builders[1-mb_index]; + MeshBuilder *outMesh = &mesh_builders[mb_index]; + + // Reset write builder + outMesh->vertexCount = 0; + + float s = 0.5f * decalSize; + + for (int i = 0; i < inMesh->vertexCount; i+=3) + { + Vector3 nV1, nV2, nV3, nV4; + + float d1 = Vector3DotProduct(inMesh->vertices[ i + 0 ], planes[face] ) - s; + float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s; + float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s; + + int v1Out = d1 > 0; + int v2Out = d2 > 0; + int v3Out = d3 > 0; + + // calculate, how many vertices of the face lie outside of the clipping plane + int total = v1Out + v2Out + v3Out; + + switch ( total ) { + case 0: { + // the entire face lies inside of the plane, no clipping needed + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]}); + break; + } + + case 1: { + // one vertex lies outside of the plane, perform clipping + + if (v1Out) { + nV1 = inMesh->vertices[ i + 1 ]; + nV2 = inMesh->vertices[ i + 2 ]; + nV3 = ClipSegment( inMesh->vertices[ i ], nV1, planes[face], s ); + nV4 = ClipSegment( inMesh->vertices[ i ], nV2, planes[face], s ); + } + + if (v2Out) { + nV1 = inMesh->vertices[ i ]; + nV2 = inMesh->vertices[ i + 2 ]; + nV3 = ClipSegment( inMesh->vertices[ i + 1 ], nV1, planes[face], s ); + nV4 = ClipSegment( inMesh->vertices[ i + 1 ], nV2, planes[face], s ); + + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1}); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4}); + break; + } + + if (v3Out) { + nV1 = inMesh->vertices[ i ]; + nV2 = inMesh->vertices[ i + 1 ]; + nV3 = ClipSegment( inMesh->vertices[ i + 2 ], nV1, planes[face], s ); + nV4 = ClipSegment( inMesh->vertices[ i + 2 ], nV2, planes[face], s ); + } + + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); + + break; + } + + case 2: { + // two vertices lies outside of the plane, perform clipping + if (!v1Out) { + nV1 = inMesh->vertices[ i ]; + nV2 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); + nV3 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + if (!v2Out) { + nV1 = inMesh->vertices[ i + 1 ]; + nV2 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); + nV3 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + if (!v3Out) { + nV1 = inMesh->vertices[ i + 2 ]; + nV2 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); + nV3 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + break; + } + + case 3: { + // the entire face lies outside of the plane, so let's discard the corresponding vertices + break; + } + + } + } + } + + // Now we just need to re-transform the vertices + MeshBuilder *theMesh = &mesh_builders[mb_index]; + + // Allocate room for UVs + if (theMesh->vertexCount > 0) + { + theMesh->uvs = (Vector2*)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + for (int i = 0; i < theMesh->vertexCount; i++) + { + + // Calculate the UVs based on the projected coords + // They are clipped to (-decalSize .. decalSize) and we want them (0..1) + theMesh->uvs[i].x = (theMesh->vertices[i].x / decalSize + .5f); + theMesh->uvs[i].y = (theMesh->vertices[i].y / decalSize + .5f); + + // From splat space to world space + theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], splatInv); + + // Tiny nudge in the normal direction so it renders properly over the mesh + theMesh->vertices[i] = Vector3Add(theMesh->vertices[i], Vector3Scale(collision.normal, decalOffset)); + } + + // Decal model data ready, create it and add it + int decalIndex = decalCount++; + decalModels[decalIndex] = LoadModelFromMesh(BuildMesh(theMesh)); + decalModels[decalIndex].materials[0] = decalMaterial; + } + + } + + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + // Draw the model at the origin and default scale + if (showModel) + { + DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); + } + + // Draw the decal models + for (int i = 0; i < decalCount; i++) + { + DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); + } + + // If we hit the mesh, draw the box for the decal + if (collision.hit) + { + + Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); + Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); + + placementCube.transform = MatrixInvert(splat); + DrawModel(placementCube, (Vector3){0}, 1.0f, Fade(WHITE, 0.5f)); + } + + DrawGrid(10, 10.0f); + + EndMode3D(); + + { + float yPos = 10; + float x0 = GetScreenWidth() - 300; + float x1 = x0 + 100; + float x2 = x1 + 100; + + DrawText("Vertices", x1, yPos, 10, LIME); + DrawText("Triangles", x2, yPos, 10, LIME); + yPos += 15; + + int vertexCount = 0; + int triangleCount = 0; + + for (int i = 0; i < model.meshCount; i++) + { + vertexCount += model.meshes[i].vertexCount; + triangleCount += model.meshes[i].triangleCount; + } + + DrawText("Main model", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; + + for (int i = 0; i < decalCount; i++) + { + DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); + + vertexCount += decalModels[i].meshes[0].vertexCount; + triangleCount += decalModels[i].meshes[0].triangleCount; + yPos += 15; + } + + DrawText("TOTAL", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; + } + + DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); + + DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); + + DrawFPS(10, 10); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(model); + UnloadTexture(modelTexture); + + for (int i = 0; i < decalCount; i++) + { + UnloadModel(decalModels[i]); + } + + UnloadTexture(decalTexture); + + FreeMeshBuilder(&mesh_builders[0]); + FreeMeshBuilder(&mesh_builders[1]); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +// A really simple dynamic array manager +void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { + // Reallocate and copy if we need to + if (mb->vertexCapacity <= mb->vertexCount + 3 ) + { + int newVertexCapacity = (1 + (mb->vertexCapacity / 256)) * 256; + Vector3 *newVertices = (Vector3*)MemAlloc(newVertexCapacity * sizeof(Vector3)); + if (mb->vertexCapacity > 0) + { + memcpy(newVertices, mb->vertices, mb->vertexCount * sizeof(Vector3)); + MemFree(mb->vertices); + } + mb->vertices = newVertices; + mb->vertexCapacity = newVertexCapacity; + } + + // Add 3 vertices + int index = mb->vertexCount; + mb->vertexCount += 3; + + for (int i = 0; i < 3; i++) + { + mb->vertices[index+i] = vertices[i]; + } +} + +void FreeMeshBuilder(MeshBuilder *mb) +{ + MemFree(mb->vertices); + + if (mb->uvs) + { + MemFree(mb->uvs); + } + + *mb = (MeshBuilder){ 0 }; +} + +// Construct a Raylib Mesh from our MeshBuilder data +Mesh BuildMesh(MeshBuilder *mb) +{ + Mesh outMesh = { 0 }; + + outMesh.vertexCount = mb->vertexCount; + outMesh.triangleCount = mb->vertexCount / 3; + + outMesh.vertices = MemAlloc(outMesh.vertexCount * 3 * sizeof(float)); + + if (mb->uvs) + { + outMesh.texcoords = MemAlloc(outMesh.vertexCount * 2 * sizeof(float)); + } + + for (int i = 0; i < mb->vertexCount; i++) + { + outMesh.vertices[3*i+0] = mb->vertices[i].x; + outMesh.vertices[3*i+1] = mb->vertices[i].y; + outMesh.vertices[3*i+2] = mb->vertices[i].z; + if (mb->uvs) + { + outMesh.texcoords[2*i+0] = mb->uvs[i].x; + outMesh.texcoords[2*i+1] = mb->uvs[i].y; + } + } + + UploadMesh(&outMesh, false); + + return outMesh; +} + +Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) +{ + float d0 = Vector3DotProduct(v0, p) - s; + float d1 = Vector3DotProduct(v1, p) - s; + + float s0 = d0 / ( d0 - d1 ); + + Vector3 position = Vector3Lerp(v0, v1, s0); + + return position; +} \ No newline at end of file diff --git a/examples/models/models_decals.png b/examples/models/models_decals.png new file mode 100644 index 0000000000000000000000000000000000000000..48e027fe2d1711237f457995d46ddcc0647a102e GIT binary patch literal 62387 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ#=yWJp1k%11H zGcDNj>&wgFf`2SeeKRvN+YU|;pKn)pW-$Q6;bCaCG;<=MAPD(IgSs~7}!H@lnOTcpH*w|W= z4tJ2-+?*3!+7BG<7MHwp_pW>J!3++bUjhz!;)}OWoOt`r3Auw;GaI)acFc-wE537> ziOX#v1G-|@_hdRoZ|303b*Kmk6o;|gs@_NQeLn)>eiUtwWBYKYs*E&6T z+kZcyyTvlXy}8DO#m%|FfurYKU60ztiE>Oji#-|2IePToOZmz!R&20maho2|yG$T} zqrpl(eE(l2_BOK#GH+XYmUUkgI3UNgc(eRs&14f61x15fjEcspAdP38lr34@(yiSt z+ZS;hu##6W6sX=J++59^?^|b#A`M zV%p@K01CmM{cQ|NEN<2c%?74Dd42L{7ct8+E%sjHc3EMefWy7-j6tW59Xqxm@h}@( zeSLk~{r!s4mmKE`L?*DiPMn@>;@}RB+81w*XRt}|GF}moc-z2pP{eSTWu1xiWhF@9 zX(u@|?ETDmX?xO9F1Fy{;I`uD=L{{Ztc>36^G z(1-W`>)XWT?fTU;wg?=!_A?_VCuPIdt*oo3OqkG6?)2{7-tJ4B6Cb3cK1fNF+nBg& z$r__s^V||Rdg81sX1#HDW-w-POLuUKSS1mA&oqjs!6~7nQ#8#gV6sT!2lMMG0uP?0 z7=%wwt>Ao96I1h{>EXNXavuNtbu8r`ZdI>-{(G*^{HZg1!3Pdb&cqX^PC0Q(`T6;^ z39I`xxpT*@6jxXKhQIjA%-iL?it*PM zpVmE$*6G*Vcu02p86O`W#%EDHf9`bb<$A=lhbND156}6Ax&6Z1=NZh|`MV`ig*o8) z<1gx0gx5PPU*J36PBwi*gH+tYg$sGu`D7YaGtM*&vR1sbX%o-lgp^i>wdwXBOy{^? zmdfH#XcyI-+i=lfnXi}BTt>;W3tid|y!f19z|8guzK=slRE!}h>P zM>DamO~Jd4jW<)UZL9h^qniI;t6io^y_n0-$jI3C^z?LLp6;uw!;P0LSz?fLXNTar zGL>>Cx7BkdiZcE9R+k_h!D?Do*xBsFr`B$BqRXG(vaIKdNtgbb4AEoCmUSNQ8YbKl zKC*P)6-nNRHihl#ULNv#-+R8*bn17xy|n2?y9z;N-v zOu@GM_L36+{w80TB%Chr=xNf8nqO>(o3FobteyN{dD*;UW!H~yN$rOe}$61J2rZ`))*dK$deOVhzWzVoZy}c3vGSjvpJ$(6;o9}Qy1H8O$B!Qp z%Y0@sT{oNbj!SYcYi&yCwgpSWzw$FntdTI1-1kbpxmvFFCI8OmjZ0e8^sFx&b!U(H z*sitqQPz@U=QG$_5=D601SIat_{Zt==xtnN^Yzn41H(U!l7|nr-ul_lY0T)K$CUWs zOh*d~d$YPZKeITy_2HG#{cWY{EVnzu-@bjzV`pcV!dg>XYs)MyF76y2E*|s$MQiKg z(hCK=j2GUx&#aSVUURwe-cN>~v*mj0a>^ETUtFwgBJ)V{x(~Z*o%G6&#O2 zi(|VnY2!hW_{DSiZXG#x+W5G{ytSTZwju-sDncenRJdbI6 z=O1aao6^zGm5q!hC0<(M8S?q@p32V>r%s;^JR^Os+G&;8`jic&1t}7&hwWJ$jB6I$ zGgKCzt4SmczS!{)OLsCZ{vP{<38%YVD{OSH-9on&YLoYC9ZGk z?AhGgvr8n`OUv>)RM{@$l%hbRVieFnPnXeEU-${=8*x%eSxRy{*$*cW2$QWom)P-%b)% z5EK-A@HApUa#iB5_H6Z(FJfON268t|ixaf^nYG2%z{n|~B$a2XwZdY_bQ2c0AdU;4 z-%Y+4yoSvn)lm3dg8^gzlfA#+T;1UJ@5S-AP1Dqy`fjJDr7fGe|L@_BS*mFcfvEiE~wR9P%Y}P-Min7Yp-)d z;SqMrZI8>Q>&2c@P*!&SyrHnPH1)=YL}q6P2ZlTA9-4s@)ra#*KlEf}bT5OOG6CR< zX_k_kbAy{=!~1u_7jlko&AxuXOgX=aJ$FL=pC5*879I&3^7`a`9h~i1Hso5(H_aAn z-nrBAu1$Jw?%561-}Bf4(pl18zL+=R;vLfvCkC_MhEmCk6%C}Aj@bJaO#XJknah&;d(eFe? zq>YFZxD7D3uZb=!%5As#pUyP_AObdrF*R4{mgqsCHlNAFi> z_yXy3&(F`d*Vy#y_V)aPa|Hx=i!TK1RcE=vcv44aN#%}5yV4sj{AK>KZ+^mto$Z%8 z81McL3=3;Z?e!zvrSczQrW zuUXDXf4?W^>K2%%IqEsQZ&BFI6@9p3%R4n)mJ8NpJ1*I7+abxD{R-TEGYRc&V_;P@ z2+`+~iM{6n_&R zypzz$ttVV!o5yjL7<%)cTV&CZ@8zV2dj(9w3Un+`87ImRS(vWY_*mfvajp0U0B+Byib;Scd16H z6Wb%PlAPX^2X8b;t?IS9@aFj8c7Dk}fBqCaldX}pOPcmmAWNV>?B0}C22Jm^AC5lF zu$dsvv{-h+#VN*8OchQEEA%!>t?)Qn{+;uL?~Oh&woYYveV*5I8jidy{mwbRPxbI} z?c#HPut6Ou5V9b1^*mT$rESV-X2+PntkoU z*LD0}fA<$Yc5CbC;Aq=h{k^GIYA&aut&3-7$wlS8d{^DfGG2-#**`{f7vPTcN~mzcNr z?}-mGJ7QkjGe3zFRC7~f3lmFgyI=n=;BSj=&fI=g?`Z}%Zr|qL`DcI9QLeTbGbGpo zm<68vSa^X`(r5dE%T>o3uc*53n0Bz?2z&N(7I5cd{+mC}J~k6}xnyiHQrBfU5i4gd zb?yph!n9QV6cYycj`S}O=Sno>lCj}&bd-86VtH}9=3DQ;T z?Q7WHOEc=;{q^auIy1-p%3obs<-HGfJ*`pDlUkC-)!pBpeq&p%^c|1LuxF0D4Qqct z4qyEAjR6z^$>l$i|Z~c_WwdrkRNL4i0#gc8p+IvDkV(wdsx{H1%Kj}>i*O!oBH}G&!PoKM^ zOnguJlf&2ZOV{RTZ=PnkBin^jK0tw9%)?l=3U0( zOcRzB!jP0@lH`=|MP&8Fxi>zR2E4P|dGN!_tF{RY7xzmu&H8Ck@R04W=!2teSGp8) zjOBfOeR(=NI}hs#o|$FZ-PqdNn)vnA)en9g_xtbj9FkeHcM)25{v7#k-Cgomd~#Acb!`j)78VDSpR z)F!i*|I4!v+^PCB>EX4<(?d23M?YV@Xi<}*v2pPYw;x+FFCUmIFoS((1e^a=xAV~# z+>1?eof%Ai8}9m&Wiqw5jiJ;jp@c2PM3winfJAJP>8uT!+>8i_>s{=sVV zvrEdbZSCiY^XH!rh>5XTBkbbs&3#xtLn5nqqhSG)zp(UK#>I*bXCXDY|6)alyP#fW z&$1PX!r-2%u<=)ChAV;#bJbgB&a_(m^@f#vXlN);YisLatHeDOADa>%9qCN4Z}K}N z6%Oe`6MfU2gm-4-Fg`4qKnNvmM_T|B^ z!gA*JZ`)?pcIC>I#Q6df@(ZUwkg54?$M#6<%f&nXcOlLST&(Eu7vii{ijoj#>D4+n zl+6{8G<0Ok<11@hZ(ld*scOz#{tG=0At6^b_^V8vI+f@Df9GkZd-e7vB^)T*D797p zvvP@}!X_Il=_v~Bx6VFaaE^WFX89eDnAPk;@*Cw>v2ri`;qdKx#+?gx)7-5U7Qfx0 z2n_`7wN4H0UVR*kGcR*KOi51Oa4|IXM(H!HJ%+w3?R$HB+iR0{R)5z!Jjb&5z(1KY z-s@k~a$8j$?wn`dby%w61h4IkzY67v*$jNGEnK@wCd8C09cM2(p1h*%XnI*viukF~v+XyO&xumZ^h7%h3)(I^5_pHRt&5f;^xwNf6e!p={^@pB^!XKQRg8uN_ z`n-VmO2OCL`a8^|rKQ_=rOgZ?qNDk5v!o;@GJc<}q<=iaO;LbpN#DOmLH*~YBGj3$ zEm)lXyj_fw!Q}VH4-cK$?Ctko{9-D_v{=#MDL91HRysmM_}K$K#wUxbR&MsZdp2aY zV{qK0huX3Dj+2lZ7`U7iQrQ%p=rND&Z!k}UA>Ib)e!o$;UF-{(Jk zzrWsrXW{?9t{WU5)i?ZkZ(na%^Y2^p!;`!Hc_#P8H|~}!;e2>{y1t}?`zN8-6Muew zZaiiB^ycXWYvcFV1*m$Yr>F0zcI&^Rs+;AJTQl)iMO$gq1Z#cgQb{q>GTz}V2RVB==tRP!aupQby? z-;gv6nV9MkvqyH_`u+RoGhX3rygR+|JZQME*;;==@QRKY!v*X&E`~elcdcP%O zRBiYDIcUK3LW-#;et+Fxd#PuO6&?BoB;J500$gyKaGL}Uxjy^s)2NrEuv!;j9#F)A0Ktg;KEBBIBN3|t$WM$lq{|Rt6 zu3M+a7EmSKy8|3{w^|u4XFTX@W6*VGxbl+W;cWAKqlRgbjnl$2GcyfWtX^&06DMXd z>l<4&$A-H92R^jAPkH#`>TaI&JiCM+fBvfT&gGHfb~-*KQapL-`3Z@u`OQ<#&$F%g zUTR@w)%5x4zrVi^&gGxrR$N^CAuUE*Tf6Q4{(63oDY`K`1n&K0uuLdAK{E2s$uS*;bGA5 z?z%&K-qIB-P89t9mfQB|Q4-q)BaHy}*0_(=zrJL)O`0UMqjd4|xZY{HTX-0Y^krpw zr-Fm&62w(q3!^xC%BpynxJRmD*%*)DV_+Z(` zgKz#{{yv|{TJd+l9FA8l8rs^EpX8XEo3|}lq5^7n&8imP;UZxt%)DvMotrZr{`e{F ztTywkW!bdGd*Xt9)102PGh}>YUNZaAXU!>qp&Z)pPqc0Gf9Chs6^`lq$L&v!sC~e5 zc+uSBl6f{&eqUIdzVNB%%oVuc+PWb6a^cR@v$ITj%HQ2d%*f78W_WwQ>i<96!;4(I zC2rlmExE7ux0|tkL*D#*ZS2Qb@A3k#?r^NYZhLqGd#RCV=I!}>*Z(DnI!`~yKZQ2K(`ZXH{IJke!>fkmu zsfb))p>JwBHSyV*nGthkgoF+;yjpT*hGBE!=VxbmX3d(#v*7X;2lvlSpX^_>=w$sC zEh#_w@$vD8$JU+XkP$g}^8C?*3TI9qJ#gxb&y?zf?=prxO`v&d zrWQptwYC)d`L@;57Oq&qv5x=BPX=A-X3jko-wPcMU5xXdc+k^-X5&;=qtA-kXU-pB z+asLz`)9Pp-N)e}obzPcG#RF@oNr&>w%YyuoCcKyS&@L}EJxH!Ew#6KzxUYtcTNC< z@U9qv>#i+d3ZyMNnV}7Z3zO`#)b(QO-1rYKtV&cqEWrHZ5%XQ`E9cC47BeV=<}?;} zHZksOY61n#kmV5Tq|3CNO z;X}sj?e~5%c>ekKdHOrgK&cxy3(SL@H%^zl@pD7S^kj>w$BKv5mEN~8ma0v#7MV0@ z63>ZL{SMyO3mQ%(3drAo!|5Rx&Ca^_q09MWx@#KS?_6lU*?%^crSf|*a&)auQ_^P^+X+yC5}IS6A2NXSBaR|2n>s zN1~t3u=_MkJh%J8Oo^Eiljizv*fpQ^Mx}A^)YKcyJn1=HB|N+xefbgYOm52MehzI9 zX4^8JRMvlA_3w`*^R}n4&{kvc0ymkW_Vf+?Zw{Ny>z`M16f_0e@D5xQE$qxI>Aaee zX$+e1JFs}cxeqUmK6peenLN!&wpa35o~=`0z+XnWv(;Zd$+F2lING|VWESgA?m3k^ zYuEg$~=I9u@bw*H3t{JXo3E?Bha5W_7VJuNLRG3HW>sOac}-fQ$? zcd^{-;ailOlOthbVsc=*bd%YdW)XRtiV2G5=F=Is)Tdlp;#qOnz{F(A!^7+@>ii{- zM9p~XU(}o_oUxUUnO*u_)+M*#c@KY_b?1q%|Fl*&FpT6I0Xwz0bw?Y4m`{U++8|`l~CQ4W` zL@!S+=@dPz0iKLwh_&N;+oIv=D0T16nl(I^7BTV2@2^R(`2CXmq$1la*Gl#`JRO%` zZaOsmz+R#G`~M~|>Yv_gc&7A(k1OLFODfw_J7(q#OWnh) zY;04V4{E$)nVZ2O#Bg@j0hNT!>RU1<@GdL;`eoMN1KYNwZ_sm3mSAKEmy(HXa{Yfo z{%r%#XCCuiN4gS4dfFIXDl)unY4D5fD&C$_@$H#%v)Bg1U%VEVcwetZ2@> zW^lW@xl~dt@f$KmPgdEE8yr&e&-B))oYe@A+Wqjx)760_+}#fG)QLXwgv zFWkS+Z+Dlgr1{s&%gZJG=GmNFuwcP~fY8vR4AO5H=C-l!(%rjZ!-T}+eXCbWI;eu-WuI}!QznHgF%5F*V zc6Vp5S$~$^6E9yO*zCB}vTvn;^luSkYhscntlB@B>hr zP$&gF%42iM!f&P1E#aoKYMhF7Z1vL2Y__SAZKdt!67MP>V&B8wGJA4wUf@y<#X|?! z_iUYgDK&nQy7w$4-{=8bBz%NoEb*-LF zjm_=B$K5;&Ry#PnW4QPA5W}+n#w!^7Eq8x@^tR)4*OWQ;Sfg&Sav6BNw^KB|@Y3?P zVU$^a+kp&@2Yihe`A!J<$}Zk4yZC063Cjx!CKfA2h3OhzT?R{j+%VF)Up(od;fG~~ z7lL#rSY*g|GHqx)#r;M=py=bv-~N&*jj~cV&fb6WD}ZOmP1!9aZ_Xaq=Q;9*Rq(;X zBmu^IW_(4;+#4qT72k24>Be1|yJ(gg`7D~yxYtvs@UZO5AI=dmX?XoA3CC zCaWHq1DDrI+_)k6ub%nc>z}<_wm<)F-}XPg&dA_XbKW-gvpo#4Cr%wxP(9NAsQ!ZP zi^Y|q^&A18-w99fz46Y@v79kO*j5+R|N0E>e?0=vf1L884YU{-C-c&dXYHf$!&& z_sn<}SbON*t31PCbx7<=gZPuDX&WvCB?U|tO@Ht#A^ibMnU#aAkkdk)L+4KiXzQPR zz+|$zLMNl~{Ip%BnfM*~dTQ*Z%s(-g+yaMvjk6 z*5W#AczAqtczR@dc>d39knNpcFCp>IG2O;tu7JY)iLzm}Mn(n2#fCjhui6+?%jYLV z-~0OLD0kb%oBbC-Un?3-j(2ruG`x#W809*5Jyl(nl5F&tVT*=v8mmU}^) zO-2~omMdH)xBRm-qF0?TYOvxrFmLXAv~|mtl!%>0s?6&impPU-d}}}O=1}QnOS|n2 z=a?28H{A2<0JpMa#KO7W4|1RPIQ*H<91&Xg>*RqC?7P@mcUaDElwJ8N#r5ue=To{b z7XO^HM?j&pQat1Sq>UR5nKRt@em<+1@b<`=c|FH2gJUJX!BVFd^a&QJv!ujTR9HCs`|~^3)Y!CrT)K2A&-L~3?cI}&cYpqD zUOO#e<_hhE9rFb%x+Iwk-XH(pe_v9<;q|4u#rbXQ=b9L%Z>U}RI97jgD(gD|hxmJ} zw|+B5iE&s}@-FGVaQ5S3*(n#Vm`X8i;RlsU49hoNR1I5vv&Mvl&7Gs5`|cd0-k2`-m_->&N*?e;Foj9mwHc^a5z2d&)>fX-5Q^)yveyR z%_I4B%akco3>y6QuUfmdHF|5=+bFgRX(cllRy=mJ$IAvbkv*pA6hRl;>Y-S$ucTlN+ugkH5XUds;zLlk?cw zvuAlWxcy0Z-_l^^aYA;b=VY};YsJm!=hGNW?-&^wJ@}_mdY`?B;Vq_v-$kTMSo9LpO=thG@o-i@%HDB^1k95oS9zL959>R zoZ|Rf{mYjx54aqA%P!=ccy)F4!KCVS_fi)eI~lQsqxOQ(M&+j)c}~>b+K|Z1{9;Dj zg~B}s)|xMC8qOp%%hq}yR!Lc*)qcIiEzOOsqQq#?H=W9fe5wIKVUinK`tGezzmm9M znR6nqI!@>zE!jZI(!y5FrDq?MM=5dB=ADE3l^>m^fxla?o!d!J_>&2 z7r!wb){tle)mRGymw-zweK+TXiq-}V{eO$Y&flp&_u=n#dA7U<8@KJK;j_yt|7y3x zVIre=$VAu;!K-PyGpws{%5~YFEQoEm#@-ig{RD5 z&|fBdU}-$h0_h7EQ~og@m3;U}pnr!%ra=yq-rC92rlv~tFjP(v-`U)l`(?VAvDghO z(=9y94_3UR@~@6C z*w33@_<_~DN}c6N9H*TeTigFBJEu;Z*!gcB>pRPz|3B+9E6cMV$_tpm{P)m_4#^+C z4k&k(8m(r_l~Y*C?9hFVX&s+{E)$R0Z^H;>=IDrk-|vJc+;>m+4+v&h*T-smS?#a} zL;HaY_Lf5};I)IIXR&uCokZ^$V9f5Gj8GYjXs zv_9htX3b=qAnnMv>{zUtLb1d4U6+lP7H-&-B=P_MY5hB4hYmA6tn@PrYtwJGu%2MG z$~$W@&wV+8D)!R_drBr;+3oL;*q2*u<5BbFop4*A!2wX3P&8%&tz~_7_o9GAq@u%_ zzmoj-YmRV;d~j{{IK6Jl)TyB6m2=R8lc!G`=iJ*f^WgRC;%oS?a7Ip+JJImHPe3SL z#$o%C{XhTAefaQpKTmo5-t-@T9*bAlb1z{)FIVoCs0+%Aq+9R`L%@9qYye$2?d zB7Xn>3nm9G8K?UvMl|U1aJH;o+x)m8ji<)jv*4tlHs5E7b$ewO@NsXFv6>ynXnZ5x z-r=FcTS>Jbv*XQg-0h+yj>v14&z~^sn)~a@TdfR_udk2)-_K@HsAzEOrG&5MVnu^J z&JW)0az9Xgn+O<}{&}q;LP%A29gA_wtKDSHa<@|Mvep#jvYnQlNxNTU4#rPc@?s=}3c~ zrbceY3rm|9RHvUhb;@mV;|T!=`zVPeyg$FJySUrmZL*LqXqL34BcQd^#=*ICHcAqKxqdMTV#-yfIHGiu0_Qoq)?-o8HhM%72Y z=1nsg&YffV^oIRR%o9G&Q<-rrI{z5&i2Q#q|JF(&Pc3Rk?17#w_gSs951nY-nOIx$ zExBNd?BdPxU;5cW$#`1XLjCtlYjqlTN|jkYe(^H)@P0kHw+#%BE1&~4jTZ$JlvpZm z+>}_bd_&TTX-}RfUYH`3=y1P;Z&$0Oo?hRpht}oqV%YTc_1i8l_n&;1Gm&jxUg?u( z%5Cpcaz51Y?D_fEroeOBa=CBO`!A;3UtnfX^FNarxga%Zjm{Yp@5RSnecEw0&3%s6jSJ*Bd2anSoYlx?#^SJ9UP1iD>(~8mM-{jlFP{Cl_@rPsN6!tu6QEX#k0E%R z*^gs|H&?Pw_@kuc#0>${k{&!xNslP~#C0d)!o{6;@7^`xevx~7o1ui1l#xy4Czd;$ zjCWo9PQ`Iw@C@G`zfb*i?fypbkFjj;BoAI=be#8h!63}>&%*j7zpEP4I=-QC@R zZ=C0gGI}oh-6(0X`^dp#UXu%0-ri13J{T)%RCM6UKVyR)xtm5A+`Jbqt-g4(e_{Bt z{(H4XMXw(UUS*VwO)|ap06a#rz=Y-09vulW2J>n5401bvzj&+8c441Dal-S%uF--O zO;@;oi@=H^Y;Aq zxiIbAIlm23+1c4_0&IoTbfb?MX2@TTdGa*Q z=fYn(1H-a2GiO|U8qOTO?m*vN*|~ou>Yo07aaWz^uvkwAC}g$U0u8_+yHwx-o8IBB z&DT;HN*(t7|8-d5*zuzZss~ONeEi7G_Jr+AE4TOyv1j`6`%(l%MOkmVd#OL+-w=3~ zFXq;l&*$Se-TB3Q{E}{jMT6ObtgNi08M30HM;Q*yx36bA-{mvYsP*A<|0}`kE=Hn1sxrqEngR?=_$8f{QBS^ zGiV}ogXWPkCkJt^O`DlkH_Gonu}L=iIBVt4P+m*k5D|X^!ysp|}Q2Rqq$z^}!T&cX~vvnPvorXMS4Bh-USbJZ*JFI@rfJ=jq z@yku&sm=#^USxjgyBoze!D3hHbDn3-tqIak+|7>%+dJ~D21U&3i$d9;BCp|-fWlhq zhJ7#hEL+xg@Z7mRm2`t3#alae?v(74w?D^ltC-QKZ-!rohQ2s6Kf5&ZixBOiISMEH zEwvl(BywEF4@1?KX2T9 zLY61zBNKP^7dOMh8VjO1JiNS`-a7R&XJ%$@u*jGuc7)-s4F4kgx<4FZ@|xzyFB>Vf zxw5B#TIiXOH5GRS6!r--?cxzg+uq;L?_5{+ufesgtBdRKuF}^MO-%~htAt;*6fma? zJ!veTZo@b)gY{O3->Sn~mjtHsPe`{fDk?g`*M8!J2b)0M-(RLXl#Pm?`K(EcZ7p+= zXfRo0e2CR%VVCgn&d50jo)$OR#PC#|GU7>->f(7J?(U@1_^vb2ZBEJ8|CjkUh$yRP zv8QOf{d>UF+G&-=K95$@9f`e`QRQ|Co8=cTy`RAmV9nz8-fm&-EU8Vh6F?n2)^nit zH$$#-LxDQW&V#zj%7+c=|NUX?7m1FJX1g$tD}tRNUY+F%!}W=RsSI};{yjTgKIv|r zLi?^atd-xG%c8AS8JTWfzjW!+A%;WY>tYfk_Se}SZsV1fkh867Nz~b6%ry1l^T!E4 zK0lXlR?$~4nASemaYldp!GO?d2OV7b`X?9UocPZyk}mtp{rw}UOn3RY?5qO&1gnc4 zi$tflPx@jwMNf9gw)^hh^`D#?qCOf_edm0tS>RFX0BU%NS%RzhW>uC8-)kHE7E9^m zar5)1GsK46xOR=LOlY59$Hpd=!z-)LOWdpd>v*X%qDzU1=hP#o0)6H3V=^)_E96A1 ztgLtxVxptD&8n}ji+#YynQ@H0gh^sotN6mrVhj2_dp24%_f0ZsZV|ATD^Gl1^XETP z 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

AIX)i);+Q2;^({W#g{?F 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 II;Vst0D_a9V*mgE literal 0 HcmV?d00001 diff --git a/examples/models/resources/raylib_logo.png b/examples/models/resources/raylib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..15bbaa2f42d01ac30fb672d31ab00701e6c32895 GIT binary patch literal 3672 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 literal 0 HcmV?d00001 From 79fd6be0084a80dcbf7c7af2c1ccb12b78b77c47 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 25 Oct 2025 11:14:53 +0200 Subject: [PATCH 24/25] Minor format tweaks --- src/external/rlsw.h | 73 ++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 626cb2cc5..bc7a813d3 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -791,8 +791,7 @@ typedef struct { } sw_vertex_t; typedef struct { - - uint8_t* pixels; // Texture pixels (RGBA32) + uint8_t *pixels; // Texture pixels (RGBA32) int width, height; // Dimensions of the texture int wMinus1, hMinus1; // Dimensions minus one @@ -805,7 +804,6 @@ typedef struct { float tx; // Texel width float ty; // Texel height - } sw_texture_t; typedef struct { @@ -881,7 +879,6 @@ typedef struct { int freeTextureIdCount; uint32_t stateFlags; - } sw_context_t; //---------------------------------------------------------------------------------- @@ -966,11 +963,7 @@ static inline int sw_clampi(int v, int min, int max) return v; } -static inline void sw_lerp_vertex_PTCH( - sw_vertex_t *SW_RESTRICT out, - const sw_vertex_t *SW_RESTRICT a, - const sw_vertex_t *SW_RESTRICT b, - float t) +static inline void sw_lerp_vertex_PTCH(sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT a, const sw_vertex_t *SW_RESTRICT b, float t) { const float tInv = 1.0f - t; @@ -997,11 +990,7 @@ static inline void sw_lerp_vertex_PTCH( out->homogeneous[3] = a->homogeneous[3]*tInv + b->homogeneous[3]*t; } -static inline void sw_get_vertex_grad_PTCH( - sw_vertex_t *SW_RESTRICT out, - const sw_vertex_t *SW_RESTRICT a, - const sw_vertex_t *SW_RESTRICT b, - float scale) +static inline void sw_get_vertex_grad_PTCH(sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT a, const sw_vertex_t *SW_RESTRICT b, float scale) { // Calculate gradients for Position out->position[0] = (b->position[0] - a->position[0])*scale; @@ -1026,9 +1015,7 @@ static inline void sw_get_vertex_grad_PTCH( out->homogeneous[3] = (b->homogeneous[3] - a->homogeneous[3])*scale; } -static inline void sw_add_vertex_grad_PTCH( - sw_vertex_t *SW_RESTRICT out, - const sw_vertex_t *SW_RESTRICT gradients) +static inline void sw_add_vertex_grad_PTCH(sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT gradients) { // Add gradients to Position out->position[0] += gradients->position[0]; @@ -1065,7 +1052,6 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) uint8x8_t narrow8 = vmovn_u16(vcombine_u16(narrow16, narrow16)); vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8), 0); - #elif defined(SW_HAS_SSE41) __m128 values = _mm_loadu_ps(src); __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); @@ -1075,7 +1061,6 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) clamped = _mm_packus_epi32(clamped, clamped); clamped = _mm_packus_epi16(clamped, clamped); *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); - #elif defined(SW_HAS_SSE2) __m128 values = _mm_loadu_ps(src); __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); @@ -1085,7 +1070,6 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) clamped = _mm_packs_epi32(clamped, clamped); clamped = _mm_packus_epi16(clamped, clamped); *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); - #else for (int i = 0; i < 4; i++) { @@ -1110,14 +1094,12 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) float32x4_t floats = vcvtq_f32_u32(ints); floats = vmulq_n_f32(floats, 1.0f/255.0f); vst1q_f32(dst, floats); - #elif defined(SW_HAS_SSE41) __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); __m128i ints = _mm_cvtepu8_epi32(bytes); __m128 floats = _mm_cvtepi32_ps(ints); floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); _mm_storeu_ps(dst, floats); - #elif defined(SW_HAS_SSE2) __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); bytes = _mm_unpacklo_epi8(bytes, _mm_setzero_si128()); @@ -1125,7 +1107,6 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) __m128 floats = _mm_cvtepi32_ps(ints); floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); _mm_storeu_ps(dst, floats); - #else dst[0] = (float)src[0]/255.0f; dst[1] = (float)src[1]/255.0f; @@ -1188,7 +1169,7 @@ static inline sw_half_t sw_half_from_float(float i) } // Framebuffer management functions - +//------------------------------------------------------------------------------------------- static inline bool sw_framebuffer_load(int w, int h) { int size = w*h; @@ -1731,9 +1712,10 @@ DEFINE_FRAMEBUFFER_BLIT_BEGIN(R8G8B8A8, uint8_t) dst += 4; } DEFINE_FRAMEBUFFER_BLIT_END() +//------------------------------------------------------------------------------------------- // Pixel format management functions - +//------------------------------------------------------------------------------------------- static inline int sw_get_pixel_format(SWformat format, SWtype type) { int channels = 0; @@ -1920,9 +1902,10 @@ static inline void sw_get_pixel(uint8_t *color, const void *pixels, uint32_t off } } } +//------------------------------------------------------------------------------------------- // Texture sampling functionality - +//------------------------------------------------------------------------------------------- static inline void sw_texture_fetch(float* color, const sw_texture_t* tex, int x, int y) { sw_float_from_unorm8_simd(color, &tex->pixels[4*(y*tex->width + x)]); @@ -2016,9 +1999,10 @@ static inline void sw_texture_sample(float *color, const sw_texture_t *tex, floa default: break; } } +//------------------------------------------------------------------------------------------- -// Color Blending functionality - +// Color blending functionality +//------------------------------------------------------------------------------------------- static inline void sw_factor_zero(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = factor[3] = 0.0f; @@ -2091,17 +2075,19 @@ static inline void sw_blend_colors(float *SW_RESTRICT dst/*[4]*/, const float *S dst[2] = srcFactor[2]*src[2] + dstFactor[2]*dst[2]; dst[3] = srcFactor[3]*src[3] + dstFactor[3]*dst[3]; } +//------------------------------------------------------------------------------------------- // Projection helper functions - +//------------------------------------------------------------------------------------------- static inline void sw_project_ndc_to_screen(float screen[2], const float ndc[4]) { screen[0] = RLSW.vpCenter[0] + ndc[0]*RLSW.vpHalfSize[0]; screen[1] = RLSW.vpCenter[1] - ndc[1]*RLSW.vpHalfSize[1]; } +//------------------------------------------------------------------------------------------- -// Polygon Clipping management - +// Polygon clipping management +//------------------------------------------------------------------------------------------- #define DEFINE_CLIP_FUNC(name, FUNC_IS_INSIDE, FUNC_COMPUTE_T) \ static inline int sw_clip_##name( \ sw_vertex_t output[SW_MAX_CLIPPED_POLYGON_VERTICES], \ @@ -2133,9 +2119,10 @@ static inline int sw_clip_##name( \ return outputCount; \ } +//------------------------------------------------------------------------------------------- // Frustum cliping functions - +//------------------------------------------------------------------------------------------- #define IS_INSIDE_PLANE_W(h) ((h)[3] >= SW_CLIP_EPSILON) #define IS_INSIDE_PLANE_X_POS(h) ((h)[0] <= (h)[3]) #define IS_INSIDE_PLANE_X_NEG(h) (-(h)[0] <= (h)[3]) @@ -2159,9 +2146,10 @@ DEFINE_CLIP_FUNC(y_pos, IS_INSIDE_PLANE_Y_POS, COMPUTE_T_PLANE_Y_POS) DEFINE_CLIP_FUNC(y_neg, IS_INSIDE_PLANE_Y_NEG, COMPUTE_T_PLANE_Y_NEG) DEFINE_CLIP_FUNC(z_pos, IS_INSIDE_PLANE_Z_POS, COMPUTE_T_PLANE_Z_POS) DEFINE_CLIP_FUNC(z_neg, IS_INSIDE_PLANE_Z_NEG, COMPUTE_T_PLANE_Z_NEG) +//------------------------------------------------------------------------------------------- // Scissor clip functions - +//------------------------------------------------------------------------------------------- #define COMPUTE_T_SCISSOR_X_MIN(hPrev, hCurr) (((RLSW.scClipMin[0])*(hPrev)[3] - (hPrev)[0])/(((hCurr)[0] - (RLSW.scClipMin[0])*(hCurr)[3]) - ((hPrev)[0] - (RLSW.scClipMin[0])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_X_MAX(hPrev, hCurr) (((RLSW.scClipMax[0])*(hPrev)[3] - (hPrev)[0])/(((hCurr)[0] - (RLSW.scClipMax[0])*(hCurr)[3]) - ((hPrev)[0] - (RLSW.scClipMax[0])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_Y_MIN(hPrev, hCurr) (((RLSW.scClipMin[1])*(hPrev)[3] - (hPrev)[1])/(((hCurr)[1] - (RLSW.scClipMin[1])*(hCurr)[3]) - ((hPrev)[1] - (RLSW.scClipMin[1])*(hPrev)[3]))) @@ -2176,9 +2164,9 @@ DEFINE_CLIP_FUNC(scissor_x_min, IS_INSIDE_SCISSOR_X_MIN, COMPUTE_T_SCISSOR_X_MIN DEFINE_CLIP_FUNC(scissor_x_max, IS_INSIDE_SCISSOR_X_MAX, COMPUTE_T_SCISSOR_X_MAX) DEFINE_CLIP_FUNC(scissor_y_min, IS_INSIDE_SCISSOR_Y_MIN, COMPUTE_T_SCISSOR_Y_MIN) DEFINE_CLIP_FUNC(scissor_y_max, IS_INSIDE_SCISSOR_Y_MAX, COMPUTE_T_SCISSOR_Y_MAX) +//------------------------------------------------------------------------------------------- -// Main clip function - +// Main polygon clip function static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VERTICES], int *vertexCounter) { static sw_vertex_t tmp[SW_MAX_CLIPPED_POLYGON_VERTICES]; @@ -2218,7 +2206,7 @@ static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VE } // Triangle rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_triangle_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations. @@ -2556,9 +2544,10 @@ static inline void sw_triangle_render(void) #undef TRIANGLE_RASTER } +//------------------------------------------------------------------------------------------- // Quad rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_quad_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations. @@ -2930,9 +2919,10 @@ static inline void sw_quad_render(void) #undef TRIANGLE_RASTER } +//------------------------------------------------------------------------------------------- // Line rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_line_clip_coord(float q, float p, float *t0, float *t1) { if (fabsf(p) < SW_CLIP_EPSILON) @@ -3243,9 +3233,10 @@ static inline void sw_line_render(sw_vertex_t *vertices) else sw_line_raster(&vertices[0], &vertices[1]); } } +//------------------------------------------------------------------------------------------- // Point rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_point_clip_and_project(sw_vertex_t *v) { if (v->homogeneous[3] != 1.0f) @@ -3409,9 +3400,10 @@ static inline void sw_point_render(sw_vertex_t *v) else sw_point_raster(v->screen[0], v->screen[1], v->homogeneous[2], v->color); } } +//------------------------------------------------------------------------------------------- // Polygon modes mendering logic - +//------------------------------------------------------------------------------------------- static inline void sw_poly_point_render(void) { for (int i = 0; i < RLSW.vertexCounter; i++) sw_point_render(&RLSW.vertexBuffer[i]); @@ -3546,6 +3538,7 @@ static inline bool sw_is_blend_dst_factor_valid(int blend) return result; } +//------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------- // Module Functions Definition From efe62f0e0df426ff64b14edc75dafd934552f1c0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 25 Oct 2025 11:15:30 +0200 Subject: [PATCH 25/25] Formating review to follow raylib code conventions #5298 --- examples/models/models_decals.c | 397 +++++++++++++++----------------- 1 file changed, 181 insertions(+), 216 deletions(-) diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index 81bbd2796..af1ecfc6d 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -2,7 +2,7 @@ * * raylib [models] example - decals * -* Example complexity rating: [★★★☆] 3/4 +* Example complexity rating: [★★★★] 4/4 * * Example originally created with raylib 5.6-dev * @@ -17,15 +17,19 @@ ********************************************************************************************/ #include "raylib.h" + #include "raymath.h" -#include +#include // Required for: memcpy() #undef FLT_MAX #define FLT_MAX 340282346638528859811704183484516925440.0f // Maximum value of a float, from bit pattern 01111111011111111111111111111111 #define MAX_DECALS 256 +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- typedef struct MeshBuilder { int vertexCount; int vertexCapacity; @@ -33,14 +37,14 @@ typedef struct MeshBuilder { Vector2 *uvs; } MeshBuilder; -// Utility functions -void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); -void FreeMeshBuilder(MeshBuilder *mb); -Mesh BuildMesh(MeshBuilder *mb); - -Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); - -Mesh GenMeshDecal(Mesh inputMesh, Ray ray); +//------------------------------------------------------------------------------------ +// Module Functions Declaration +//------------------------------------------------------------------------------------ +static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); +static void FreeMeshBuilder(MeshBuilder *mb); +static Mesh BuildMesh(MeshBuilder *mb); +static Mesh GenMeshDecal(Mesh inputMesh, Ray ray); +static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); //------------------------------------------------------------------------------------ // Program main entry point @@ -67,11 +71,10 @@ int main(void) Model model = LoadModel("resources/models/obj/character.obj"); // Apply character skin - Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); // Load model texture + Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); SetTextureFilter(modelTexture, TEXTURE_FILTER_BILINEAR); - model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; // Set model diffuse texture + model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; - BoundingBox modelBBox = GetMeshBoundingBox(model.meshes[0]); // Get mesh bounding box camera.target = Vector3Lerp(modelBBox.min, modelBBox.max, 0.5f); @@ -79,14 +82,12 @@ int main(void) camera.position.x *= 0.1f; float modelSize = fminf( - fminf(fabsf(modelBBox.max.x - modelBBox.min.x), - fabsf(modelBBox.max.y - modelBBox.min.y)), - fabsf(modelBBox.max.z - modelBBox.min.z) - ); + fminf(fabsf(modelBBox.max.x - modelBBox.min.x), fabsf(modelBBox.max.y - modelBBox.min.y)), + fabsf(modelBBox.max.z - modelBBox.min.z)); - camera.position = (Vector3){ 0, modelBBox.max.y * 1.2f, modelSize * 3.0f }; + camera.position = (Vector3){ 0.0f, modelBBox.max.y*1.2f, modelSize*3.0f }; - float decalSize = modelSize * .25f; + float decalSize = modelSize*0.25f; float decalOffset = 0.01f; Model placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize)); @@ -96,7 +97,7 @@ int main(void) decalMaterial.maps[0].color = YELLOW; Image decalImage = LoadImage("resources/raylib_logo.png"); - ImageResizeNN(&decalImage, decalImage.width / 4, decalImage.height / 4); + ImageResizeNN(&decalImage, decalImage.width/4, decalImage.height/4); Texture decalTexture = LoadTextureFromImage(decalImage); UnloadImage(decalImage); @@ -106,28 +107,23 @@ int main(void) // We're going to use these to build up our decal meshes // They'll resize automatically as we go, we'll free them at the end - MeshBuilder mesh_builders[2] = { {0}, {0} }; + MeshBuilder meshBuilders[2] = { 0 }; bool showModel = true; - Model decalModels[MAX_DECALS] = {0}; + Model decalModels[MAX_DECALS] = { 0 }; int decalCount = 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 { // Update //---------------------------------------------------------------------------------- - - if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { - UpdateCamera(&camera, CAMERA_THIRD_PERSON); - } - - if (IsKeyPressed(KEY_SPACE)) { - showModel = !showModel; - } + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) UpdateCamera(&camera, CAMERA_THIRD_PERSON); + + if (IsKeyPressed(KEY_SPACE)) showModel = !showModel; // Display information about closest hit RayCollision collision = { 0 }; @@ -140,7 +136,7 @@ int main(void) // Check ray collision against bounding box first, before trying the full ray-mesh test RayCollision boxHitInfo = GetRayCollisionBox(ray, modelBBox); - if ((boxHitInfo.hit) && decalCount < MAX_DECALS) + if ((boxHitInfo.hit) && (decalCount < MAX_DECALS)) { // Check ray collision against model meshes RayCollision meshHitInfo = { 0 }; @@ -153,52 +149,47 @@ int main(void) if (meshHitInfo.hit) { // Save the closest hit mesh - if ((!collision.hit) || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; + if (!collision.hit || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; } } - if (meshHitInfo.hit) - { - collision = meshHitInfo; - } + if (meshHitInfo.hit) collision = meshHitInfo; } - // -------------- - - if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && decalCount < MAX_DECALS) + // Add decal to mesh on hit point + if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (decalCount < MAX_DECALS)) { - // Create the transformation to project the decal Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); - Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); + Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){ 0.0f, 1.0f, 0.0f }); // Spin the placement around a bit - splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD * ((float)GetRandomValue(-180, 180)))); - + splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD*((float)GetRandomValue(-180, 180)))); Matrix splatInv = MatrixInvert(splat); // Reset the mesh builders - mesh_builders[0].vertexCount = 0; - mesh_builders[1].vertexCount = 0; + meshBuilders[0].vertexCount = 0; + meshBuilders[1].vertexCount = 0; // We'll be flip-flopping between the two mesh builders // Reading from one and writing to the other, then swapping - int mb_index = 0; + int mbIndex = 0; - // First pass, just get any triangle inside the bounding box - // (for each mesh of the model) - for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) { + // First pass, just get any triangle inside the bounding box (for each mesh of the model) + for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) + { Mesh mesh = model.meshes[meshIndex]; for (int tri = 0; tri < mesh.triangleCount; tri++) { - Vector3 vertices[3]; + Vector3 vertices[3] = { 0 }; // The way we calculate the vertices of the mesh triangle // depend on whether the mesh vertices are indexed or not if (mesh.indices == 0) { - for (int v = 0; v < 3; v++) { - vertices[v] = (Vector3) { + for (int v = 0; v < 3; v++) + { + vertices[v] = (Vector3){ mesh.vertices[3*3*tri + 3*v + 0], mesh.vertices[3*3*tri + 3*v + 1], mesh.vertices[3*3*tri + 3*v + 2] @@ -209,7 +200,7 @@ int main(void) { for (int v = 0; v < 3; v++) { - vertices[v] = (Vector3) { + vertices[v] = (Vector3){ mesh.vertices[ 3*mesh.indices[3*tri+0] + v], mesh.vertices[ 3*mesh.indices[3*tri+1] + v], mesh.vertices[ 3*mesh.indices[3*tri+2] + v] @@ -225,19 +216,14 @@ int main(void) // To splat space Vector3 v = Vector3Transform(vertices[i], splat); - if (fabsf(v.x) 0) - { - AddTriangleToMeshBuilder(&mesh_builders[mb_index], vertices); - } + if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices); } } @@ -250,20 +236,21 @@ int main(void) { 0, 0, 1 }, { 0, 0, -1 } }; + for (int face = 0; face < 6; face++) { // Swap current model builder (so we read from the one we just wrote to) - mb_index = 1 - mb_index; + mbIndex = 1 - mbIndex; - MeshBuilder *inMesh = &mesh_builders[1-mb_index]; - MeshBuilder *outMesh = &mesh_builders[mb_index]; + MeshBuilder *inMesh = &meshBuilders[1 - mbIndex]; + MeshBuilder *outMesh = &meshBuilders[mbIndex]; // Reset write builder outMesh->vertexCount = 0; - float s = 0.5f * decalSize; + float s = 0.5f*decalSize; - for (int i = 0; i < inMesh->vertexCount; i+=3) + for (int i = 0; i < inMesh->vertexCount; i += 3) { Vector3 nV1, nV2, nV3, nV4; @@ -271,100 +258,101 @@ int main(void) float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s; float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s; - int v1Out = d1 > 0; - int v2Out = d2 > 0; - int v3Out = d3 > 0; + int v1Out = (d1 > 0); + int v2Out = (d2 > 0); + int v3Out = (d3 > 0); - // calculate, how many vertices of the face lie outside of the clipping plane + // Calculate, how many vertices of the face lie outside of the clipping plane int total = v1Out + v2Out + v3Out; - switch ( total ) { - case 0: { - // the entire face lies inside of the plane, no clipping needed + switch (total) + { + case 0: + { + // The entire face lies inside of the plane, no clipping needed AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]}); - break; - } - - case 1: { - // one vertex lies outside of the plane, perform clipping - - if (v1Out) { - nV1 = inMesh->vertices[ i + 1 ]; - nV2 = inMesh->vertices[ i + 2 ]; - nV3 = ClipSegment( inMesh->vertices[ i ], nV1, planes[face], s ); - nV4 = ClipSegment( inMesh->vertices[ i ], nV2, planes[face], s ); + } break; + case 1: + { + // One vertex lies outside of the plane, perform clipping + if (v1Out) + { + nV1 = inMesh->vertices[i + 1]; + nV2 = inMesh->vertices[i + 2]; + nV3 = ClipSegment(inMesh->vertices[i], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i], nV2, planes[face], s); } - if (v2Out) { - nV1 = inMesh->vertices[ i ]; - nV2 = inMesh->vertices[ i + 2 ]; - nV3 = ClipSegment( inMesh->vertices[ i + 1 ], nV1, planes[face], s ); - nV4 = ClipSegment( inMesh->vertices[ i + 1 ], nV2, planes[face], s ); + if (v2Out) + { + nV1 = inMesh->vertices[i]; + nV2 = inMesh->vertices[i + 2]; + nV3 = ClipSegment(inMesh->vertices[i + 1], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i + 1], nV2, planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1}); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4}); break; } - if (v3Out) { - nV1 = inMesh->vertices[ i ]; - nV2 = inMesh->vertices[ i + 1 ]; - nV3 = ClipSegment( inMesh->vertices[ i + 2 ], nV1, planes[face], s ); - nV4 = ClipSegment( inMesh->vertices[ i + 2 ], nV2, planes[face], s ); + if (v3Out) + { + nV1 = inMesh->vertices[i]; + nV2 = inMesh->vertices[i + 1]; + nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s); } AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); - - break; - } - - case 2: { - // two vertices lies outside of the plane, perform clipping - if (!v1Out) { - nV1 = inMesh->vertices[ i ]; - nV2 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); - nV3 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); + } break; + case 2: + { + // Two vertices lies outside of the plane, perform clipping + if (!v1Out) + { + nV1 = inMesh->vertices[i]; + nV2 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - if (!v2Out) { - nV1 = inMesh->vertices[ i + 1 ]; - nV2 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); - nV3 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); + + if (!v2Out) + { + nV1 = inMesh->vertices[i + 1]; + nV2 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - if (!v3Out) { - nV1 = inMesh->vertices[ i + 2 ]; - nV2 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); - nV3 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); + + if (!v3Out) + { + nV1 = inMesh->vertices[i + 2]; + nV2 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - break; - } - - case 3: { - // the entire face lies outside of the plane, so let's discard the corresponding vertices - break; - } - + } break; + case 3: // The entire face lies outside of the plane, so let's discard the corresponding vertices + default: break; } } } // Now we just need to re-transform the vertices - MeshBuilder *theMesh = &mesh_builders[mb_index]; + MeshBuilder *theMesh = &meshBuilders[mbIndex]; // Allocate room for UVs if (theMesh->vertexCount > 0) { - theMesh->uvs = (Vector2*)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + for (int i = 0; i < theMesh->vertexCount; i++) { - // Calculate the UVs based on the projected coords // They are clipped to (-decalSize .. decalSize) and we want them (0..1) - theMesh->uvs[i].x = (theMesh->vertices[i].x / decalSize + .5f); - theMesh->uvs[i].y = (theMesh->vertices[i].y / decalSize + .5f); + theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f); + theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f); // From splat space to world space theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], splatInv); @@ -378,89 +366,73 @@ int main(void) decalModels[decalIndex] = LoadModelFromMesh(BuildMesh(theMesh)); decalModels[decalIndex].materials[0] = decalMaterial; } - } - //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); BeginMode3D(camera); - // Draw the model at the origin and default scale - if (showModel) - { - DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); - } + if (showModel) DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); // Draw the decal models - for (int i = 0; i < decalCount; i++) - { - DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); - } + for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); // If we hit the mesh, draw the box for the decal if (collision.hit) { - Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); - placementCube.transform = MatrixInvert(splat); DrawModel(placementCube, (Vector3){0}, 1.0f, Fade(WHITE, 0.5f)); } DrawGrid(10, 10.0f); - EndMode3D(); + + float yPos = 10; + float x0 = GetScreenWidth() - 300; + float x1 = x0 + 100; + float x2 = x1 + 100; + DrawText("Vertices", x1, yPos, 10, LIME); + DrawText("Triangles", x2, yPos, 10, LIME); + yPos += 15; + + int vertexCount = 0; + int triangleCount = 0; + + for (int i = 0; i < model.meshCount; i++) { - float yPos = 10; - float x0 = GetScreenWidth() - 300; - float x1 = x0 + 100; - float x2 = x1 + 100; + vertexCount += model.meshes[i].vertexCount; + triangleCount += model.meshes[i].triangleCount; + } + + DrawText("Main model", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; + + for (int i = 0; i < decalCount; i++) + { + DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); - DrawText("Vertices", x1, yPos, 10, LIME); - DrawText("Triangles", x2, yPos, 10, LIME); - yPos += 15; - - int vertexCount = 0; - int triangleCount = 0; - - for (int i = 0; i < model.meshCount; i++) - { - vertexCount += model.meshes[i].vertexCount; - triangleCount += model.meshes[i].triangleCount; - } - - DrawText("Main model", x0, yPos, 10, LIME); - DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); - yPos += 15; - - for (int i = 0; i < decalCount; i++) - { - DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); - - vertexCount += decalModels[i].meshes[0].vertexCount; - triangleCount += decalModels[i].meshes[0].triangleCount; - yPos += 15; - } - - DrawText("TOTAL", x0, yPos, 10, LIME); - DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + vertexCount += decalModels[i].meshes[0].vertexCount; + triangleCount += decalModels[i].meshes[0].triangleCount; yPos += 15; } + + DrawText("TOTAL", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); - DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); DrawFPS(10, 10); @@ -474,15 +446,13 @@ int main(void) UnloadModel(model); UnloadTexture(modelTexture); - for (int i = 0; i < decalCount; i++) - { - UnloadModel(decalModels[i]); - } - + // TODO: WARNING: This line crashes program on closing + //for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); + UnloadTexture(decalTexture); - FreeMeshBuilder(&mesh_builders[0]); - FreeMeshBuilder(&mesh_builders[1]); + FreeMeshBuilder(&meshBuilders[0]); + FreeMeshBuilder(&meshBuilders[1]); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- @@ -490,18 +460,24 @@ int main(void) return 0; } -// A really simple dynamic array manager -void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Add triangles to mesh builder (dynamic array manager) +static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) +{ // Reallocate and copy if we need to - if (mb->vertexCapacity <= mb->vertexCount + 3 ) + if (mb->vertexCapacity <= (mb->vertexCount + 3)) { - int newVertexCapacity = (1 + (mb->vertexCapacity / 256)) * 256; - Vector3 *newVertices = (Vector3*)MemAlloc(newVertexCapacity * sizeof(Vector3)); + int newVertexCapacity = (1 + (mb->vertexCapacity/256))*256; + Vector3 *newVertices = (Vector3 *)MemAlloc(newVertexCapacity*sizeof(Vector3)); + if (mb->vertexCapacity > 0) { - memcpy(newVertices, mb->vertices, mb->vertexCount * sizeof(Vector3)); + memcpy(newVertices, mb->vertices, mb->vertexCount*sizeof(Vector3)); MemFree(mb->vertices); } + mb->vertices = newVertices; mb->vertexCapacity = newVertexCapacity; } @@ -510,44 +486,33 @@ void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { int index = mb->vertexCount; mb->vertexCount += 3; - for (int i = 0; i < 3; i++) - { - mb->vertices[index+i] = vertices[i]; - } + for (int i = 0; i < 3; i++) mb->vertices[index+i] = vertices[i]; } -void FreeMeshBuilder(MeshBuilder *mb) +// Free mesh builder +static void FreeMeshBuilder(MeshBuilder *mb) { MemFree(mb->vertices); - - if (mb->uvs) - { - MemFree(mb->uvs); - } - + if (mb->uvs) MemFree(mb->uvs); *mb = (MeshBuilder){ 0 }; } -// Construct a Raylib Mesh from our MeshBuilder data -Mesh BuildMesh(MeshBuilder *mb) +// Build a Mesh from MeshBuilder data +static Mesh BuildMesh(MeshBuilder *mb) { Mesh outMesh = { 0 }; outMesh.vertexCount = mb->vertexCount; - outMesh.triangleCount = mb->vertexCount / 3; - - outMesh.vertices = MemAlloc(outMesh.vertexCount * 3 * sizeof(float)); - - if (mb->uvs) - { - outMesh.texcoords = MemAlloc(outMesh.vertexCount * 2 * sizeof(float)); - } - + outMesh.triangleCount = mb->vertexCount/3; + outMesh.vertices = MemAlloc(outMesh.vertexCount*3*sizeof(float)); + if (mb->uvs) outMesh.texcoords = MemAlloc(outMesh.vertexCount*2*sizeof(float)); + for (int i = 0; i < mb->vertexCount; i++) { outMesh.vertices[3*i+0] = mb->vertices[i].x; outMesh.vertices[3*i+1] = mb->vertices[i].y; outMesh.vertices[3*i+2] = mb->vertices[i].z; + if (mb->uvs) { outMesh.texcoords[2*i+0] = mb->uvs[i].x; @@ -560,14 +525,14 @@ Mesh BuildMesh(MeshBuilder *mb) return outMesh; } -Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) +// Clip segment +static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) { float d0 = Vector3DotProduct(v0, p) - s; float d1 = Vector3DotProduct(v1, p) - s; - - float s0 = d0 / ( d0 - d1 ); + float s0 = d0/(d0 - d1); Vector3 position = Vector3Lerp(v0, v1, s0); return position; -} \ No newline at end of file +}