diff --git a/CMakeOptions.txt b/CMakeOptions.txt
index 3c1c12742..32cfb814e 100644
--- a/CMakeOptions.txt
+++ b/CMakeOptions.txt
@@ -8,7 +8,7 @@ if(EMSCRIPTEN)
endif()
enum_option(PLATFORM "Desktop;Web;Android;Raspberry Pi;DRM;SDL" "Platform to build for.")
-enum_option(OPENGL_VERSION "OFF;4.3;3.3;2.1;1.1;ES 2.0;ES 3.0" "Force a specific OpenGL Version?")
+enum_option(OPENGL_VERSION "OFF;4.3;3.3;2.1;1.1;ES 2.0;ES 3.0;Software" "Force a specific OpenGL Version?")
# Configuration options
option(BUILD_EXAMPLES "Build the examples." ${PROJECT_IS_TOP_LEVEL})
diff --git a/cmake/LibraryConfigurations.cmake b/cmake/LibraryConfigurations.cmake
index 98c7f2362..8f127fc23 100644
--- a/cmake/LibraryConfigurations.cmake
+++ b/cmake/LibraryConfigurations.cmake
@@ -158,6 +158,8 @@ if (NOT ${OPENGL_VERSION} MATCHES "OFF")
set(GRAPHICS "GRAPHICS_API_OPENGL_ES2")
elseif (${OPENGL_VERSION} MATCHES "ES 3.0")
set(GRAPHICS "GRAPHICS_API_OPENGL_ES3")
+ elseif (${OPENGL_VERSION} MATCHES "Software")
+ set(GRAPHICS "GRAPHICS_API_OPENGL_11_SOFTWARE")
endif ()
if (NOT "${SUGGESTED_GRAPHICS}" STREQUAL "" AND NOT "${SUGGESTED_GRAPHICS}" STREQUAL "${GRAPHICS}")
message(WARNING "You are overriding the suggested GRAPHICS=${SUGGESTED_GRAPHICS} with ${GRAPHICS}! This may fail.")
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index e63c1a7c8..b482df0fc 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -105,6 +105,10 @@ elseif ("${PLATFORM}" STREQUAL "DRM")
list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/others/rlgl_standalone.c)
list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/others/raylib_opengl_interop.c)
+elseif ("${OPENGL_VERSION}" STREQUAL "Software")
+ list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/others/rlgl_standalone.c)
+ list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/others/raylib_opengl_interop.c)
+
elseif (NOT SUPPORT_GESTURES_SYSTEM)
# Items requiring gestures system
list(REMOVE_ITEM example_sources ${CMAKE_CURRENT_SOURCE_DIR}/textures/textures_mouse_painting.c)
diff --git a/examples/Makefile b/examples/Makefile
index ee17bb350..36faed3b3 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -516,6 +516,7 @@ CORE = \
core/core_custom_logging \
core/core_drop_files \
core/core_high_dpi \
+ core/core_input_actions \
core/core_input_gamepad \
core/core_input_gestures \
core/core_input_gestures_testbed \
@@ -606,6 +607,7 @@ TEXT = \
MODELS = \
models/models_animation_gpu_skinning \
models/models_animation_playing \
+ models/models_basic_voxel \
models/models_billboard_rendering \
models/models_bone_socket \
models/models_box_collisions \
diff --git a/examples/Makefile.Web b/examples/Makefile.Web
index a16d57853..b1b1f0779 100644
--- a/examples/Makefile.Web
+++ b/examples/Makefile.Web
@@ -516,6 +516,7 @@ CORE = \
core/core_custom_logging \
core/core_drop_files \
core/core_high_dpi \
+ core/core_input_actions \
core/core_input_gamepad \
core/core_input_gestures \
core/core_input_gestures_testbed \
@@ -606,6 +607,7 @@ TEXT = \
MODELS = \
models/models_animation_gpu_skinning \
models/models_animation_playing \
+ models/models_basic_voxel \
models/models_billboard_rendering \
models/models_bone_socket \
models/models_box_collisions \
@@ -732,6 +734,9 @@ core/core_drop_files: core/core_drop_files.c
core/core_high_dpi: core/core_high_dpi.c
$(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM)
+core/core_input_actions: core/core_input_actions.c
+ $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM)
+
core/core_input_gamepad: core/core_input_gamepad.c
$(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \
--preload-file core/resources/ps3.png@resources/ps3.png \
@@ -1045,6 +1050,9 @@ models/models_animation_playing: models/models_animation_playing.c
--preload-file models/resources/models/iqm/guytex.png@resources/models/iqm/guytex.png \
--preload-file models/resources/models/iqm/guyanim.iqm@resources/models/iqm/guyanim.iqm
+models/models_basic_voxel: models/models_basic_voxel.c
+ $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM)
+
models/models_billboard_rendering: models/models_billboard_rendering.c
$(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \
--preload-file models/resources/billboard.png@resources/billboard.png
diff --git a/examples/README.md b/examples/README.md
index 1753a31bc..48e83c852 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: 163]
+## EXAMPLES COLLECTION [TOTAL: 164]
-### category: core [37]
+### category: core [38]
Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality.
@@ -62,6 +62,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c
| [core_high_dpi](core/core_high_dpi.c) |
| ⭐⭐☆☆ | 5.0 | 5.5 | [Jonathan Marler](https://github.com/marler8997) |
| [core_render_texture](core/core_render_texture.c) |
| ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) |
| [core_undo_redo](core/core_undo_redo.c) |
| ⭐⭐⭐☆ | 5.5 | 5.6 | [Ramon Santamaria](https://github.com/raysan5) |
+| [core_input_actions](core/core_input_actions.c) |
| ⭐⭐☆☆ | 5.5 | 5.6 | [Jett](https://github.com/JettMonstersGoBoom) |
### category: shapes [20]
@@ -144,7 +145,7 @@ Examples using raylib text functionality, including sprite fonts loading/generat
| [text_codepoints_loading](text/text_codepoints_loading.c) |
| ⭐⭐⭐☆ | 4.2 | 4.2 | [Ramon Santamaria](https://github.com/raysan5) |
| [text_inline_styling](text/text_inline_styling.c) |
| ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Wagner Barongello](https://github.com/SultansOfCode) |
-### category: models [23]
+### category: models [24]
Examples using raylib models functionality, including models loading/generation and drawing, provided by raylib [models](../src/rmodels.c) module.
@@ -173,6 +174,7 @@ Examples using raylib models functionality, including models loading/generation
| [models_animation_gpu_skinning](models/models_animation_gpu_skinning.c) |
| ⭐⭐⭐☆ | 4.5 | 4.5 | [Daniel Holden](https://github.com/orangeduck) |
| [models_bone_socket](models/models_bone_socket.c) |
| ⭐⭐⭐⭐️ | 4.5 | 4.5 | [iP](https://github.com/ipzaur) |
| [models_tesseract_view](models/models_tesseract_view.c) |
| ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Timothy van der Valk](https://github.com/arceryz) |
+| [models_basic_voxel](models/models_basic_voxel.c) |
| ⭐⭐☆☆ | 5.5 | 5.5 | [Tim Little](https://github.com/timlittle) |
### category: shaders [29]
diff --git a/examples/core/core_input_actions.c b/examples/core/core_input_actions.c
new file mode 100644
index 000000000..aff356087
--- /dev/null
+++ b/examples/core/core_input_actions.c
@@ -0,0 +1,197 @@
+/*******************************************************************************************
+*
+* raylib [core] example - input actions
+*
+* Example complexity rating: [★★☆☆] 2/4
+*
+* Example originally created with raylib 5.5, last time updated with raylib 5.6
+*
+* Example contributed by Jett (@JettMonstersGoBoom) 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 Jett (@JettMonstersGoBoom)
+*
+********************************************************************************************/
+
+// Simple example for decoding input as actions, allowing remapping of input to different keys or gamepad buttons
+// For example instead of using `IsKeyDown(KEY_LEFT)`, you can use `IsActionDown(ACTION_LEFT)`
+// which can be reassigned to e.g. KEY_A and also assigned to a gamepad button. the action will trigger with either gamepad or keys
+
+#include "raylib.h"
+
+//----------------------------------------------------------------------------------
+// Types and Structures Definition
+//----------------------------------------------------------------------------------
+typedef enum ActionType {
+ NO_ACTION = 0,
+ ACTION_UP,
+ ACTION_DOWN,
+ ACTION_LEFT,
+ ACTION_RIGHT,
+ ACTION_FIRE,
+ MAX_ACTION
+} ActionType;
+
+// Key and button inputs
+typedef struct ActionInput {
+ int key;
+ int button;
+} ActionInput;
+
+//----------------------------------------------------------------------------------
+// Global Variables Definition
+//----------------------------------------------------------------------------------
+static int gamepadIndex = 0; // Gamepad default index
+static ActionInput actionInputs[MAX_ACTION] = { 0 };
+
+//----------------------------------------------------------------------------------
+// Module Functions Declaration
+//----------------------------------------------------------------------------------
+static bool IsActionPressed(int action); // Check action key/button pressed
+static bool IsActionReleased(int action); // Check action key/button released
+static bool IsActionDown(int action); // Check action key/button down
+
+static void SetActionsDefault(void); // Set the "default" keyset
+static void SetActionsCursor(void); // Set the "alternate" keyset
+
+//------------------------------------------------------------------------------------
+// Program main entry point
+//------------------------------------------------------------------------------------
+int main(void)
+{
+ // Initialization
+ //--------------------------------------------------------------------------------------
+ const int screenWidth = 800;
+ const int screenHeight = 450;
+
+ InitWindow(screenWidth, screenHeight, "raylib [core] example - input actions");
+
+ // Set default actions
+ char actionSet = 0;
+ SetActionsDefault();
+
+ Vector2 position = (Vector2){ 400.0f, 200.0f };
+ Vector2 size = (Vector2){ 40.0f, 40.0f };
+
+ SetTargetFPS(60);
+ //--------------------------------------------------------------------------------------
+
+ // Main game loop
+ while (!WindowShouldClose()) // Detect window close button or ESC key
+ {
+ // Update
+ //----------------------------------------------------------------------------------
+ gamepadIndex = 0; // set this to gamepad being checked
+ if (IsActionDown(ACTION_UP)) position.y -= 2;
+ if (IsActionDown(ACTION_DOWN)) position.y += 2;
+ if (IsActionDown(ACTION_LEFT)) position.x -= 2;
+ if (IsActionDown(ACTION_RIGHT)) position.x += 2;
+ if (IsActionPressed(ACTION_FIRE))
+ {
+ position.x = (screenWidth-size.x)/2;
+ position.y = (screenHeight-size.y)/2;
+ }
+
+ // Switch control scheme by pressing TAB
+ if (IsKeyPressed(KEY_TAB))
+ {
+ actionSet = !actionSet;
+ if (actionSet == 0) SetActionsDefault();
+ else SetActionsCursor();
+ }
+ //----------------------------------------------------------------------------------
+
+ // Draw
+ //----------------------------------------------------------------------------------
+ BeginDrawing();
+
+ ClearBackground(GRAY);
+
+ DrawRectangleV(position, size, RED);
+
+ DrawText((actionSet == 0)? "Current input set: WASD (default)" : "Current input set: Cursor", 10, 10, 20, WHITE);
+ DrawText("Use TAB key to toggles Actions keyset", 10, 50, 20, GREEN);
+
+ EndDrawing();
+ //----------------------------------------------------------------------------------
+ }
+
+ // De-Initialization
+ //--------------------------------------------------------------------------------------
+ CloseWindow(); // Close window and OpenGL context
+ //--------------------------------------------------------------------------------------
+
+ return 0;
+}
+
+//----------------------------------------------------------------------------------
+// Module Functions Definition
+//----------------------------------------------------------------------------------
+// Check action key/button pressed
+// NOTE: Combines key pressed and gamepad button pressed in one action
+static bool IsActionPressed(int action)
+{
+ bool result = false;
+
+ if (action < MAX_ACTION) result = (IsKeyPressed(actionInputs[action].key) || IsGamepadButtonPressed(gamepadIndex, actionInputs[action].button));
+
+ return result;
+}
+
+// Check action key/button released
+// NOTE: Combines key released and gamepad button released in one action
+static bool IsActionReleased(int action)
+{
+ bool result = false;
+
+ if (action < MAX_ACTION) result = (IsKeyReleased(actionInputs[action].key) || IsGamepadButtonReleased(gamepadIndex, actionInputs[action].button));
+
+ return result;
+}
+
+// Check action key/button down
+// NOTE: Combines key down and gamepad button down in one action
+static bool IsActionDown(int action)
+{
+ bool result = false;
+
+ if (action < MAX_ACTION) result = (IsKeyDown(actionInputs[action].key) || IsGamepadButtonDown(gamepadIndex, actionInputs[action].button));
+
+ return result;
+}
+
+// Set the "default" keyset
+// NOTE: Here WASD and gamepad buttons on the left side for movement
+static void SetActionsDefault(void)
+{
+ actionInputs[ACTION_UP].key = KEY_W;
+ actionInputs[ACTION_DOWN].key = KEY_S;
+ actionInputs[ACTION_LEFT].key = KEY_A;
+ actionInputs[ACTION_RIGHT].key = KEY_D;
+ actionInputs[ACTION_FIRE].key = KEY_SPACE;
+
+ actionInputs[ACTION_UP].button = GAMEPAD_BUTTON_LEFT_FACE_UP;
+ actionInputs[ACTION_DOWN].button = GAMEPAD_BUTTON_LEFT_FACE_DOWN;
+ actionInputs[ACTION_LEFT].button = GAMEPAD_BUTTON_LEFT_FACE_LEFT;
+ actionInputs[ACTION_RIGHT].button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT;
+ actionInputs[ACTION_FIRE].button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN;
+}
+
+// Set the "alternate" keyset
+// NOTE: Here cursor keys and gamepad buttons on the right side for movement
+static void SetActionsCursor(void)
+{
+ actionInputs[ACTION_UP].key = KEY_UP;
+ actionInputs[ACTION_DOWN].key = KEY_DOWN;
+ actionInputs[ACTION_LEFT].key = KEY_LEFT;
+ actionInputs[ACTION_RIGHT].key = KEY_RIGHT;
+ actionInputs[ACTION_FIRE].key = KEY_SPACE;
+
+ actionInputs[ACTION_UP].button = GAMEPAD_BUTTON_RIGHT_FACE_UP;
+ actionInputs[ACTION_DOWN].button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN;
+ actionInputs[ACTION_LEFT].button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT;
+ actionInputs[ACTION_RIGHT].button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT;
+ actionInputs[ACTION_FIRE].button = GAMEPAD_BUTTON_LEFT_FACE_DOWN;
+}
diff --git a/examples/core/core_input_actions.png b/examples/core/core_input_actions.png
new file mode 100644
index 000000000..757b725f6
Binary files /dev/null and b/examples/core/core_input_actions.png differ
diff --git a/examples/examples_list.txt b/examples/examples_list.txt
index ead1bf220..b5682123f 100644
--- a/examples/examples_list.txt
+++ b/examples/examples_list.txt
@@ -44,6 +44,7 @@ core;core_automation_events;★★★☆;5.0;5.0;2023;2025;"Ramon Santamaria";@r
core;core_high_dpi;★★☆☆;5.0;5.5;2025;2025;"Jonathan Marler";@marler8997
core;core_render_texture;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5
core;core_undo_redo;★★★☆;5.5;5.6;2025;2025;"Ramon Santamaria";@raysan5
+core;core_input_actions;★★☆☆;5.5;5.6;2025;2025;"Jett";@JettMonstersGoBoom
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_colors_palette;★★☆☆;1.0;2.5;2014;2025;"Ramon Santamaria";@raysan5
@@ -127,6 +128,7 @@ models;models_textured_cube;★★☆☆;4.5;4.5;2022;2025;"Ramon Santamaria";@r
models;models_animation_gpu_skinning;★★★☆;4.5;4.5;2024;2025;"Daniel Holden";@orangeduck
models;models_bone_socket;★★★★;4.5;4.5;2024;2025;"iP";@ipzaur
models;models_tesseract_view;★★☆☆;5.6-dev;5.6-dev;2024;2025;"Timothy van der Valk";@arceryz
+models;models_basic_voxel;★★☆☆;5.5;5.5;2025;2025;"Tim Little";@timlittle
shaders;shaders_basic_lighting;★★★★;3.0;4.2;2019;2025;"Chris Camacho";@chriscamacho
shaders;shaders_model_shader;★★☆☆;1.3;3.7;2014;2025;"Ramon Santamaria";@raysan5
shaders;shaders_shapes_textures;★★☆☆;1.7;3.7;2015;2025;"Ramon Santamaria";@raysan5
diff --git a/examples/models/models_basic_voxel.c b/examples/models/models_basic_voxel.c
new file mode 100644
index 000000000..8df15d73d
--- /dev/null
+++ b/examples/models/models_basic_voxel.c
@@ -0,0 +1,152 @@
+/*******************************************************************************************
+*
+* raylib [models] example - basic voxel
+*
+* Example complexity rating: [★★☆☆] 2/4
+*
+* Example originally created with raylib 5.5, last time updated with raylib 5.5
+*
+* Example contributed by Tim Little (@timlittle) 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 Tim Little (@timlittle)
+*
+********************************************************************************************/
+
+#include "raylib.h"
+#include "raymath.h"
+
+#define WORLD_SIZE 8 // Size of our voxel world (8x8x8 cubes)
+
+//------------------------------------------------------------------------------------
+// Program main entry point
+//------------------------------------------------------------------------------------
+int main(void)
+{
+ // Initialization
+ //--------------------------------------------------------------------------------------
+ const int screenWidth = 800;
+ const int screenHeight = 450;
+
+ InitWindow(screenWidth, screenHeight, "raylib [models] example - basic voxel");
+
+ DisableCursor(); // Lock mouse to window center
+
+ // Define the camera to look into our 3d world (first person)
+ Camera3D camera = { 0 };
+ camera.position = (Vector3){ -2.0f, 0.0f, -2.0f }; // Camera position at ground level
+ camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
+ camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector
+ camera.fovy = 45.0f; // Camera field-of-view Y
+ camera.projection = CAMERA_PERSPECTIVE; // Camera projection type
+
+ // Create a cube model
+ Mesh cubeMesh = GenMeshCube(1.0f, 1.0f, 1.0f); // Create a unit cube mesh
+ Model cubeModel = LoadModelFromMesh(cubeMesh); // Convert mesh to a model
+ cubeModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = BEIGE;
+
+ // Initialize voxel world - fill with voxels
+ bool voxels[WORLD_SIZE][WORLD_SIZE][WORLD_SIZE] = { false };
+ for (int x = 0; x < WORLD_SIZE; x++)
+ {
+ for (int y = 0; y < WORLD_SIZE; y++)
+ {
+ for (int z = 0; z < WORLD_SIZE; z++)
+ {
+ voxels[x][y][z] = true;
+ }
+ }
+ }
+
+ SetTargetFPS(60);
+ //--------------------------------------------------------------------------------------
+
+ // Main game loop
+ while (!WindowShouldClose()) // Detect window close button or ESC key
+ {
+ // Update
+ //----------------------------------------------------------------------------------
+ UpdateCamera(&camera, CAMERA_FIRST_PERSON);
+
+ // Handle voxel removal with mouse click
+ if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
+ {
+ // Cast a ray from the screen center (where crosshair would be)
+ Vector2 screenCenter = { screenWidth/2.0f, screenHeight/2.0f };
+ Ray ray = GetMouseRay(screenCenter, camera);
+
+ // Check ray collision with all voxels
+ bool voxelRemoved = false;
+ for (int x = 0; (x < WORLD_SIZE) && !voxelRemoved; x++)
+ {
+ for (int y = 0; (y < WORLD_SIZE) && !voxelRemoved; y++)
+ {
+ for (int z = 0; (z < WORLD_SIZE) && !voxelRemoved; z++)
+ {
+ if (!voxels[x][y][z]) continue; // Skip empty voxels
+
+ // Build a bounding box for this voxel
+ Vector3 position = { x, y, z };
+ BoundingBox box = {
+ (Vector3){ position.x - 0.5f, position.y - 0.5f, position.z - 0.5f },
+ (Vector3){ position.x + 0.5f, position.y + 0.5f, position.z + 0.5f }
+ };
+
+ // Check ray-box collision
+ RayCollision collision = GetRayCollisionBox(ray, box);
+ if (collision.hit)
+ {
+ voxels[x][y][z] = false; // Remove this voxel
+ voxelRemoved = true; // Exit all loops
+ }
+ }
+ }
+ }
+ }
+ //----------------------------------------------------------------------------------
+
+ // Draw
+ //----------------------------------------------------------------------------------
+ BeginDrawing();
+
+ ClearBackground(RAYWHITE);
+
+ BeginMode3D(camera);
+
+ DrawGrid(10, 1.0f);
+
+ // Draw all voxels
+ for (int x = 0; x < WORLD_SIZE; x++)
+ {
+ for (int y = 0; y < WORLD_SIZE; y++)
+ {
+ for (int z = 0; z < WORLD_SIZE; z++)
+ {
+ if (!voxels[x][y][z]) continue;
+
+ Vector3 position = { x, y, z };
+ DrawModel(cubeModel, position, 1.0f, BEIGE);
+ DrawCubeWires(position, 1.0f, 1.0f, 1.0f, BLACK);
+ }
+ }
+ }
+
+ EndMode3D();
+
+ DrawText("Left-click a voxel to remove it!", 10, 10, 20, DARKGRAY);
+ DrawText("WASD to move, mouse to look around", 10, 35, 10, GRAY);
+
+ EndDrawing();
+ //----------------------------------------------------------------------------------
+ }
+
+ // De-Initialization
+ //--------------------------------------------------------------------------------------
+ UnloadModel(cubeModel);
+ CloseWindow();
+ //--------------------------------------------------------------------------------------
+
+ return 0;
+}
diff --git a/examples/models/models_basic_voxel.png b/examples/models/models_basic_voxel.png
new file mode 100644
index 000000000..8bbcaf8da
Binary files /dev/null and b/examples/models/models_basic_voxel.png differ
diff --git a/examples/shaders/resources/shaders/glsl330/palette_switch.fs b/examples/shaders/resources/shaders/glsl330/palette_switch.fs
index 6a82529b1..2db8880d1 100644
--- a/examples/shaders/resources/shaders/glsl330/palette_switch.fs
+++ b/examples/shaders/resources/shaders/glsl330/palette_switch.fs
@@ -17,7 +17,7 @@ out vec4 finalColor;
void main()
{
// Texel color fetching from texture sampler
- // NOTE: The texel is actually the a GRAYSCALE index color
+ // NOTE: The texel is actually the GRAYSCALE index color
vec4 texelColor = texture(texture0, fragTexCoord)*fragColor;
// Convert the (normalized) texel color RED component (GB would work, too)
diff --git a/examples/shaders/shaders_ascii_rendering.c b/examples/shaders/shaders_ascii_rendering.c
new file mode 100644
index 000000000..5444caeee
--- /dev/null
+++ b/examples/shaders/shaders_ascii_rendering.c
@@ -0,0 +1,117 @@
+/*******************************************************************************************
+*
+* raylib [shaders] example - ascii rendering
+*
+* Example complexity rating: [★★☆☆] 2/4
+*
+* Example originally created with raylib 5.5, last time updated with raylib 5.6
+*
+* Example contributed by Maicon Santana (@maiconpintoabreu) 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 Maicon Santana (@maiconpintoabreu)
+*
+********************************************************************************************/
+
+#include "raylib.h"
+
+#if defined(PLATFORM_DESKTOP)
+ #define GLSL_VERSION 330
+#else // PLATFORM_ANDROID, PLATFORM_WEB
+ #define GLSL_VERSION 100
+#endif
+
+//------------------------------------------------------------------------------------
+// Program main entry point
+//------------------------------------------------------------------------------------
+int main(void)
+{
+ // Initialization
+ //--------------------------------------------------------------------------------------
+ const int screenWidth = 800;
+ const int screenHeight = 450;
+
+ InitWindow(screenWidth, screenHeight, "raylib [shaders] example - ascii rendering");
+
+ // Texture to test static drawing
+ Texture2D fudesumi = LoadTexture("resources/fudesumi.png");
+ // Texture to test moving drawing
+ Texture2D raysan = LoadTexture("resources/raysan.png");
+
+ // Load shader to be used on postprocessing
+ Shader shader = LoadShader(0, TextFormat("resources/shaders/glsl%i/ascii.fs", GLSL_VERSION));
+
+ // These locations are used to send data to the GPU
+ int resolutionLoc = GetShaderLocation(shader, "resolution");
+ int fontSizeLoc = GetShaderLocation(shader, "fontSize");
+
+ // Set the character size for the ASCII effect
+ float fontSize = 4.0f;
+
+ // Send the updated values to the shader
+ float resolution[2] = { (float)screenWidth, (float)screenHeight };
+ SetShaderValue(shader, resolutionLoc, resolution, SHADER_UNIFORM_VEC2);
+ SetShaderValue(shader, fontSizeLoc, &fontSize, SHADER_UNIFORM_FLOAT);
+
+ Vector2 circlePos = (Vector2){40.0f, (float)screenHeight * 0.5f};
+ float circleSpeed = 1.0f;
+
+ // RenderTexture to apply the postprocessing later
+ RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight);
+
+ 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
+ //----------------------------------------------------------------------------------
+ circlePos.x += circleSpeed;
+
+ if ((circlePos.x > 200.0f) || (circlePos.x < 40.0f)) circleSpeed *= -1;
+ //----------------------------------------------------------------------------------
+
+ // Draw
+ //----------------------------------------------------------------------------------
+ // Draw our scene to a render texture first
+ BeginTextureMode(target);
+ ClearBackground(WHITE);
+
+ DrawTexture(fudesumi, 500, -30, WHITE);
+ DrawTextureV(raysan, circlePos, WHITE);
+
+ EndTextureMode();
+
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+
+ BeginShaderMode(shader);
+ // Draw the render texture containing scene
+ // The shader will process every pixel on the screen
+ DrawTextureRec(target.texture,
+ (Rectangle){ 0, 0, (float)target.texture.width, (float)-target.texture.height },
+ (Vector2){ 0, 0 }, WHITE);
+ EndShaderMode();
+
+ DrawRectangle(0, 0, screenWidth, 40, BLACK);
+ DrawText("Ascii effect", 120, 10, 20, LIGHTGRAY);
+ DrawFPS(10, 10);
+
+ EndDrawing();
+ //----------------------------------------------------------------------------------
+ }
+
+ // De-Initialization
+ //--------------------------------------------------------------------------------------
+ UnloadShader(shader); // Unload shader
+ UnloadTexture(fudesumi); // Unload texture
+ UnloadTexture(raysan); // Unload texture
+
+ CloseWindow(); // Close window and OpenGL context
+ //--------------------------------------------------------------------------------------
+
+ return 0;
+}
diff --git a/examples/shaders/shaders_ascii_rendering.png b/examples/shaders/shaders_ascii_rendering.png
new file mode 100644
index 000000000..eee5cc90b
Binary files /dev/null and b/examples/shaders/shaders_ascii_rendering.png differ
diff --git a/projects/VS2022/examples/core_input_actions.vcxproj b/projects/VS2022/examples/core_input_actions.vcxproj
new file mode 100644
index 000000000..9b43826f8
--- /dev/null
+++ b/projects/VS2022/examples/core_input_actions.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
+
+
+
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}
+ Win32Proj
+ core_input_actions
+ 10.0
+ core_input_actions
+
+
+
+ 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\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ WindowsLocalDebugger
+
+
+ $(SolutionDir)..\..\examples\core
+ 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;shcore.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 fe2241434..c0295d4c6 100644
--- a/projects/VS2022/raylib.sln
+++ b/projects/VS2022/raylib.sln
@@ -345,6 +345,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "text_inline_styling", "exam
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_undo_redo", "examples\core_undo_redo.vcxproj", "{3B27F358-2679-4F38-B297-17B536F580BB}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_input_actions", "examples\core_input_actions.vcxproj", "{718FCBD0-591D-448C-B7D5-9F1CA8544E7B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug.DLL|ARM64 = Debug.DLL|ARM64
@@ -4247,6 +4249,30 @@ Global
{3B27F358-2679-4F38-B297-17B536F580BB}.Release|x64.Build.0 = Release|x64
{3B27F358-2679-4F38-B297-17B536F580BB}.Release|x86.ActiveCfg = Release|Win32
{3B27F358-2679-4F38-B297-17B536F580BB}.Release|x86.Build.0 = Release|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug.DLL|x64.Build.0 = Debug.DLL|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug|ARM64.Build.0 = Debug|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug|x64.ActiveCfg = Debug|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug|x64.Build.0 = Debug|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug|x86.ActiveCfg = Debug|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Debug|x86.Build.0 = Debug|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release.DLL|x64.ActiveCfg = Release.DLL|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release.DLL|x64.Build.0 = Release.DLL|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release.DLL|x86.Build.0 = Release.DLL|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release|ARM64.ActiveCfg = Release|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release|ARM64.Build.0 = Release|ARM64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release|x64.ActiveCfg = Release|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release|x64.Build.0 = Release|x64
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release|x86.ActiveCfg = Release|Win32
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -4421,6 +4447,7 @@ Global
{49C67F03-1A56-4F96-B278-39B66EC93678} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}
{D496308F-3C3C-40B3-A3ED-EA327D244B3E} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A}
{3B27F358-2679-4F38-B297-17B536F580BB} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}
+ {718FCBD0-591D-448C-B7D5-9F1CA8544E7B} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29}
diff --git a/projects/VS2022/raylib/raylib.vcxproj b/projects/VS2022/raylib/raylib.vcxproj
index 64695d4de..7721a669a 100644
--- a/projects/VS2022/raylib/raylib.vcxproj
+++ b/projects/VS2022/raylib/raylib.vcxproj
@@ -504,6 +504,20 @@
true
true
+
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+
true
true
diff --git a/projects/VS2022/raylib/raylib.vcxproj.filters b/projects/VS2022/raylib/raylib.vcxproj.filters
index 182358229..4588f0521 100644
--- a/projects/VS2022/raylib/raylib.vcxproj.filters
+++ b/projects/VS2022/raylib/raylib.vcxproj.filters
@@ -46,6 +46,9 @@
Source Files\Platform Files
+
+ Source Files\Platform Files
+
diff --git a/src/Makefile b/src/Makefile
index f2bdd811e..e767d21d0 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -235,20 +235,22 @@ endif
# NOTE: By default use OpenGL 3.3 on desktop platforms
ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW)
GRAPHICS ?= GRAPHICS_API_OPENGL_33
- #GRAPHICS = GRAPHICS_API_OPENGL_11 # Uncomment to use OpenGL 1.1
- #GRAPHICS = GRAPHICS_API_OPENGL_21 # Uncomment to use OpenGL 2.1
- #GRAPHICS = GRAPHICS_API_OPENGL_43 # Uncomment to use OpenGL 4.3
- #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE)
+ #GRAPHICS = GRAPHICS_API_OPENGL_11_SOFTWARE # Uncomment to use software rendering
+ #GRAPHICS = GRAPHICS_API_OPENGL_11 # Uncomment to use OpenGL 1.1
+ #GRAPHICS = GRAPHICS_API_OPENGL_21 # Uncomment to use OpenGL 2.1
+ #GRAPHICS = GRAPHICS_API_OPENGL_43 # Uncomment to use OpenGL 4.3
+ #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE)
endif
ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL)
GRAPHICS ?= GRAPHICS_API_OPENGL_33
endif
ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_RGFW)
GRAPHICS ?= GRAPHICS_API_OPENGL_33
- #GRAPHICS = GRAPHICS_API_OPENGL_11 # Uncomment to use OpenGL 1.1
- #GRAPHICS = GRAPHICS_API_OPENGL_21 # Uncomment to use OpenGL 2.1
- #GRAPHICS = GRAPHICS_API_OPENGL_43 # Uncomment to use OpenGL 4.3
- #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE)
+ #GRAPHICS = GRAPHICS_API_OPENGL_11_SOFTWARE # Uncomment to use software rendering
+ #GRAPHICS = GRAPHICS_API_OPENGL_11 # Uncomment to use OpenGL 1.1
+ #GRAPHICS = GRAPHICS_API_OPENGL_21 # Uncomment to use OpenGL 2.1
+ #GRAPHICS = GRAPHICS_API_OPENGL_43 # Uncomment to use OpenGL 4.3
+ #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE)
endif
ifeq ($(TARGET_PLATFORM),PLATFORM_DRM)
# On DRM OpenGL ES 2.0 must be used
diff --git a/src/external/rlsw.h b/src/external/rlsw.h
new file mode 100644
index 000000000..49050f848
--- /dev/null
+++ b/src/external/rlsw.h
@@ -0,0 +1,4938 @@
+/**********************************************************************************************
+*
+* rlsw v1.0 - An OpenGL 1.1-style software renderer implementation
+*
+* DESCRIPTION:
+* 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.
+*
+* FEATURES:
+* - Rendering to custom internal framebuffer with multiple color modes supported:
+* - Color buffer: RGB - 8-bit (3:3:2) | RGB - 16-bit (5:6:5) | RGB - 24-bit (8:8:8)
+* - Depth buffer: D - 8-bit (unorm) | D - 16-bit (unorm) | D - 24-bit (unorm)
+* - Rendering modes supported: POINT, LINES, TRIANGLE, QUADS
+* - Additional features: Polygon modes, Point width, Line width
+* - Clipping support for all rendering modes
+* - Texture features supported:
+* - All uncompressed texture formats supported by raylib
+* - Texture Minification/Magnification checks
+* - Point and Bilinear filtering
+* - Texture Wrap Modes with separate checks for S/T coordinates
+* - Vertex Arrays support with direct primitive drawing mode
+* - Matrix Stack support (Matrix Push/Pop)
+* - Other GL misc features:
+* - GL-style getter functions
+* - Framebuffer resizing
+* - Perspective correction
+* - Scissor clipping
+* - Depth testing
+* - Blend modes
+* - Face culling
+*
+* ADDITIONAL NOTES:
+* Check PR for more info: https://github.com/raysan5/raylib/pull/4832
+*
+* CONFIGURATION:
+* #define RLSW_IMPLEMENTATION
+* Generates the implementation of the library into the included file
+* If not defined, the library is in header only mode and can be included in other headers
+* or source files without problems. But only ONE file should hold the implementation
+*
+* rlsw capabilities could be customized just defining some internal
+* values before library inclusion (default values listed):
+*
+* #define SW_GL_FRAMEBUFFER_COPY_BGRA true
+* #define SW_GL_BINDING_COPY_TEXTURE true
+* #define SW_COLOR_BUFFER_BITS 24
+* #define SW_DEPTH_BUFFER_BITS 16
+* #define SW_MAX_PROJECTION_STACK_SIZE 2
+* #define SW_MAX_MODELVIEW_STACK_SIZE 8
+* #define SW_MAX_TEXTURE_STACK_SIZE 2
+* #define SW_MAX_TEXTURES 128
+*
+*
+* LICENSE: MIT
+*
+* Copyright (c) 2025-2026 Le Juez Victor (@Bigfoot71), reviewed by Ramon Santamaria (@raysan5)
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*
+**********************************************************************************************/
+
+#ifndef RLSW_H
+#define RLSW_H
+
+#include
+#include
+
+//----------------------------------------------------------------------------------
+// Defines and Macros
+//----------------------------------------------------------------------------------
+// Function specifiers definition
+#ifndef SWAPI
+ #define SWAPI // Functions defined as 'extern' by default (implicit specifiers)
+#endif
+
+#ifndef SW_MALLOC
+ #define SW_MALLOC(sz) malloc(sz)
+#endif
+
+#ifndef SW_REALLOC
+ #define SW_REALLOC(ptr, newSz) realloc(ptr, newSz)
+#endif
+
+#ifndef SW_FREE
+ #define SW_FREE(ptr) free(ptr)
+#endif
+
+#ifndef SW_RESTRICT
+ #ifdef _MSC_VER
+ #define SW_RESTRICT __restrict
+ #else
+ #define SW_RESTRICT restrict
+ #endif
+#endif
+
+#ifndef SW_GL_FRAMEBUFFER_COPY_BGRA
+ #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
+#endif
+
+#ifndef SW_DEPTH_BUFFER_BITS
+ #define SW_DEPTH_BUFFER_BITS 16
+#endif
+
+#ifndef SW_MAX_PROJECTION_STACK_SIZE
+ #define SW_MAX_PROJECTION_STACK_SIZE 2
+#endif
+
+#ifndef SW_MAX_MODELVIEW_STACK_SIZE
+ #define SW_MAX_MODELVIEW_STACK_SIZE 8
+#endif
+
+#ifndef SW_MAX_TEXTURE_STACK_SIZE
+ #define SW_MAX_TEXTURE_STACK_SIZE 2
+#endif
+
+#ifndef SW_MAX_TEXTURES
+ #define SW_MAX_TEXTURES 128
+#endif
+
+// Under normal circumstances, clipping a polygon can add at most one vertex per clipping plane.
+// Considering the largest polygon involved is a quadrilateral (4 vertices),
+// and that clipping occurs against both the frustum (6 planes) and the scissors (4 planes),
+// the maximum number of vertices after clipping is:
+// 4 (original vertices) + 6 (frustum planes) + 4 (scissors planes) = 14
+#ifndef SW_MAX_CLIPPED_POLYGON_VERTICES
+ #define SW_MAX_CLIPPED_POLYGON_VERTICES 14
+#endif
+
+#ifndef SW_CLIP_EPSILON
+ #define SW_CLIP_EPSILON 1e-4f
+#endif
+
+//----------------------------------------------------------------------------------
+// OpenGL Compatibility Types
+//----------------------------------------------------------------------------------
+typedef unsigned int GLenum;
+typedef unsigned char GLboolean;
+typedef unsigned int GLbitfield;
+typedef void GLvoid;
+typedef signed char GLbyte;
+typedef short GLshort;
+typedef int GLint;
+typedef unsigned char GLubyte;
+typedef unsigned short GLushort;
+typedef unsigned int GLuint;
+typedef int GLsizei;
+typedef float GLfloat;
+typedef float GLclampf;
+typedef double GLdouble;
+typedef double GLclampd;
+
+//----------------------------------------------------------------------------------
+// OpenGL Definitions
+// NOTE: Not used/supported definitions are commented
+//----------------------------------------------------------------------------------
+#define GL_FALSE 0
+#define GL_TRUE 1
+
+#define GL_SCISSOR_TEST 0x0C11
+#define GL_TEXTURE_2D 0x0DE1
+#define GL_DEPTH_TEST 0x0B71
+#define GL_CULL_FACE 0x0B44
+#define GL_BLEND 0x0BE2
+
+#define GL_VENDOR 0x1F00
+#define GL_RENDERER 0x1F01
+#define GL_VERSION 0x1F02
+#define GL_EXTENSIONS 0x1F03
+
+//#define GL_ATTRIB_STACK_DEPTH 0x0BB0
+//#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1
+#define GL_COLOR_CLEAR_VALUE 0x0C22
+//#define GL_COLOR_WRITEMASK 0x0C23
+//#define GL_CURRENT_INDEX 0x0B01
+#define GL_CURRENT_COLOR 0x0B00
+//#define GL_CURRENT_NORMAL 0x0B02
+//#define GL_CURRENT_RASTER_COLOR 0x0B04
+//#define GL_CURRENT_RASTER_DISTANCE 0x0B09
+//#define GL_CURRENT_RASTER_INDEX 0x0B05
+//#define GL_CURRENT_RASTER_POSITION 0x0B07
+//#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06
+//#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08
+#define GL_CURRENT_TEXTURE_COORDS 0x0B03
+#define GL_POINT_SIZE 0x0B11
+#define GL_LINE_WIDTH 0x0B21
+//#define GL_INDEX_CLEAR_VALUE 0x0C20
+//#define GL_INDEX_MODE 0x0C30
+//#define GL_INDEX_WRITEMASK 0x0C21
+#define GL_MODELVIEW_MATRIX 0x0BA6
+#define GL_MODELVIEW_STACK_DEPTH 0x0BA3
+//#define GL_NAME_STACK_DEPTH 0x0D70
+#define GL_PROJECTION_MATRIX 0x0BA7
+#define GL_PROJECTION_STACK_DEPTH 0x0BA4
+//#define GL_RENDER_MODE 0x0C40
+//#define GL_RGBA_MODE 0x0C31
+#define GL_TEXTURE_MATRIX 0x0BA8
+#define GL_TEXTURE_STACK_DEPTH 0x0BA5
+#define GL_VIEWPORT 0x0BA2
+
+#define GL_COLOR_BUFFER_BIT 0x00004000
+#define GL_DEPTH_BUFFER_BIT 0x00000100
+
+#define GL_MODELVIEW 0x1700
+#define GL_PROJECTION 0x1701
+#define GL_TEXTURE 0x1702
+
+#define GL_VERTEX_ARRAY 0x8074
+#define GL_NORMAL_ARRAY 0x8075 // WARNING: Not implemented (defined for RLGL)
+#define GL_COLOR_ARRAY 0x8076
+//#define GL_INDEX_ARRAY 0x8077
+#define GL_TEXTURE_COORD_ARRAY 0x8078
+
+#define GL_POINTS 0x0000
+#define GL_LINES 0x0001
+//#define GL_LINE_LOOP 0x0002
+//#define GL_LINE_STRIP 0x0003
+#define GL_TRIANGLES 0x0004
+//#define GL_TRIANGLE_STRIP 0x0005
+//#define GL_TRIANGLE_FAN 0x0006
+#define GL_QUADS 0x0007
+//#define GL_QUAD_STRIP 0x0008
+//#define GL_POLYGON 0x0009
+
+#define GL_POINT 0x1B00
+#define GL_LINE 0x1B01
+#define GL_FILL 0x1B02
+
+#define GL_FRONT 0x0404
+#define GL_BACK 0x0405
+
+#define GL_ZERO 0
+#define GL_ONE 1
+#define GL_SRC_COLOR 0x0300
+#define GL_ONE_MINUS_SRC_COLOR 0x0301
+#define GL_SRC_ALPHA 0x0302
+#define GL_ONE_MINUS_SRC_ALPHA 0x0303
+#define GL_DST_ALPHA 0x0304
+#define GL_ONE_MINUS_DST_ALPHA 0x0305
+#define GL_DST_COLOR 0x0306
+#define GL_ONE_MINUS_DST_COLOR 0x0307
+#define GL_SRC_ALPHA_SATURATE 0x0308
+
+#define GL_NEAREST 0x2600
+#define GL_LINEAR 0x2601
+
+#define GL_REPEAT 0x2901
+#define GL_CLAMP 0x2900
+
+#define GL_TEXTURE_MAG_FILTER 0x2800
+#define GL_TEXTURE_MIN_FILTER 0x2801
+
+#define GL_TEXTURE_WRAP_S 0x2802
+#define GL_TEXTURE_WRAP_T 0x2803
+
+#define GL_NO_ERROR 0
+#define GL_INVALID_ENUM 0x0500
+#define GL_INVALID_VALUE 0x0501
+#define GL_INVALID_OPERATION 0x0502
+#define GL_STACK_OVERFLOW 0x0503
+#define GL_STACK_UNDERFLOW 0x0504
+#define GL_OUT_OF_MEMORY 0x0505
+
+#define GL_ALPHA 0x1906
+#define GL_LUMINANCE 0x1909
+#define GL_LUMINANCE_ALPHA 0x190A
+#define GL_RGB 0x1907
+#define GL_RGBA 0x1908
+
+#define GL_BYTE 0x1400
+#define GL_UNSIGNED_BYTE 0x1401
+#define GL_SHORT 0x1402
+#define GL_UNSIGNED_SHORT 0x1403
+#define GL_INT 0x1404
+#define GL_UNSIGNED_INT 0x1405
+#define GL_FLOAT 0x1406
+
+// OpenGL Definitions NOT USED
+#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50
+#define GL_PACK_ALIGNMENT 0x0D05
+#define GL_UNPACK_ALIGNMENT 0x0CF5
+#define GL_LINE_SMOOTH 0x0B20
+#define GL_SMOOTH 0x1D01
+#define GL_NICEST 0x1102
+#define GL_CCW 0x0901
+#define GL_CW 0x0900
+#define GL_NEVER 0x0200
+#define GL_LESS 0x0201
+#define GL_EQUAL 0x0202
+#define GL_LEQUAL 0x0203
+#define GL_GREATER 0x0204
+#define GL_NOTEQUAL 0x0205
+#define GL_GEQUAL 0x0206
+#define GL_ALWAYS 0x0207
+
+//----------------------------------------------------------------------------------
+// OpenGL Bindings to rlsw
+//----------------------------------------------------------------------------------
+#define glReadPixels(x, y, w, h, f, t, p) swCopyFramebuffer((x), (y), (w), (h), (f), (t), (p))
+#define glEnable(state) swEnable((state))
+#define glDisable(state) swDisable((state))
+#define glGetFloatv(pname, params) swGetFloatv((pname), (params))
+#define glGetString(pname) swGetString((pname))
+#define glGetError() swGetError()
+#define glViewport(x, y, w, h) swViewport((x), (y), (w), (h))
+#define glScissor(x, y, w, h) swScissor((x), (y), (w), (h))
+#define glClearColor(r, g, b, a) swClearColor((r), (g), (b), (a))
+#define glClear(bitmask) swClear((bitmask))
+#define glBlendFunc(sfactor, dfactor) swBlendFunc((sfactor), (dfactor))
+#define glPolygonMode(face, mode) swPolygonMode((mode))
+#define glCullFace(face) swCullFace((face))
+#define glPointSize(size) swPointSize((size))
+#define glLineWidth(width) swLineWidth((width))
+#define glMatrixMode(mode) swMatrixMode((mode))
+#define glPushMatrix() swPushMatrix()
+#define glPopMatrix() swPopMatrix()
+#define glLoadIdentity() swLoadIdentity()
+#define glTranslatef(x, y, z) swTranslatef((x), (y), (z))
+#define glRotatef(a, x, y, z) swRotatef((a), (x), (y), (z))
+#define glScalef(x, y, z) swScalef((x), (y), (z))
+#define glMultMatrixf(v) swMultMatrixf((v))
+#define glFrustum(l, r, b, t, n, f) swFrustum((l), (r), (b), (t), (n), (f))
+#define glOrtho(l, r, b, t, n, f) swOrtho((l), (r), (b), (t), (n), (f))
+#define glBegin(mode) swBegin((mode))
+#define glEnd() swEnd()
+#define glVertex2i(x, y) swVertex2i((x), (y))
+#define glVertex2f(x, y) swVertex2f((x), (y))
+#define glVertex2fv(v) swVertex2fv((v))
+#define glVertex3i(x, y, z) swVertex3i((x), (y), (z))
+#define glVertex3f(x, y, z) swVertex3f((x), (y), (z))
+#define glvertex3fv(v) swVertex3fv((v))
+#define glVertex4i(x, y, z, w) swVertex4i((x), (y), (z), (w))
+#define glVertex4f(x, y, z, w) swVertex4f((x), (y), (z), (w))
+#define glVertex4fv(v) swVertex4fv((v))
+#define glColor3ub(r, g, b) swColor3ub((r), (g), (b))
+#define glColor3ubv(v) swColor3ubv((v))
+#define glColor3f(r, g, b) swColor3f((r), (g), (b))
+#define glColor3fv(v) swColor3fv((v))
+#define glColor4ub(r, g, b, a) swColor4ub((r), (g), (b), (a))
+#define glColor4ubv(v) swColor4ubv((v))
+#define glColor4f(r, g, b, a) swColor4f((r), (g), (b), (a))
+#define glColor4fv(v) swColor4fv((v))
+#define glTexCoord2f(u, v) swTexCoord2f((u), (v))
+#define glTexCoord2fv(v) swTexCoord2fv((v))
+
+#define glEnableClientState(t) ((void)(t))
+#define glDisableClientState(t) swBindArray((t), 0)
+#define glVertexPointer(sz, t, s, p) swBindArray(SW_VERTEX_ARRAY, (p))
+#define glTexCoordPointer(sz, t, s, p) swBindArray(SW_TEXTURE_COORD_ARRAY, (p))
+#define glColorPointer(sz, t, s, p) swBindArray(SW_COLOR_ARRAY, (p))
+#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 glTexParameteri(tr, pname, param) swTexParameteri((pname), (param))
+#define glBindTexture(tr, id) swBindTexture((id))
+
+// OpenGL functions NOT IMPLEMENTED by rlsw
+#define glClearDepth(X) ((void)(X))
+#define glDepthMask(X) ((void)(X))
+#define glColorMask(X,Y,Z,W) ((void)(X),(void)(Y),(void)(Z),(void)(W))
+#define glPixelStorei(X,Y) ((void)(X),(void)(Y))
+#define glHint(X,Y) ((void)(X),(void)(Y))
+#define glShadeModel(X) ((void)(X))
+#define glFrontFace(X) ((void)(X))
+#define glDepthFunc(X) ((void)(X))
+#define glTexSubImage2D(X,Y,Z,W,A,B,C,D,E) ((void)(X),(void)(Y),(void)(Z),(void)(W),(void)(A),(void)(B),(void)(C),(void)(D),(void)(E))
+#define glGetTexImage(X,Y,Z,W,A) ((void)(X),(void)(Y),(void)(Z),(void)(W),(void)(A))
+#define glDrawElements(X,Y,Z,W) ((void)(X),(void)(Y),(void)(Z),(void)(W))
+#define glNormal3f(X,Y,Z) ((void)(X),(void)(Y),(void)(Z))
+#define glNormal3fv(X) ((void)(X))
+#define glNormalPointer(X,Y,Z) ((void)(X),(void)(Y),(void)(Z))
+
+//----------------------------------------------------------------------------------
+// Types and Structures Definition
+//----------------------------------------------------------------------------------
+typedef enum {
+ SW_SCISSOR_TEST = GL_SCISSOR_TEST,
+ SW_TEXTURE_2D = GL_TEXTURE_2D,
+ SW_DEPTH_TEST = GL_DEPTH_TEST,
+ SW_CULL_FACE = GL_CULL_FACE,
+ SW_BLEND = GL_BLEND
+} SWstate;
+
+typedef enum {
+ SW_VENDOR = GL_VENDOR,
+ SW_RENDERER = GL_RENDERER,
+ SW_VERSION = GL_VERSION,
+ SW_EXTENSIONS = GL_EXTENSIONS,
+ SW_COLOR_CLEAR_VALUE = GL_COLOR_CLEAR_VALUE,
+ SW_CURRENT_COLOR = GL_CURRENT_COLOR,
+ SW_CURRENT_TEXTURE_COORDS = GL_CURRENT_TEXTURE_COORDS,
+ SW_POINT_SIZE = GL_POINT_SIZE,
+ SW_LINE_WIDTH = GL_LINE_WIDTH,
+ SW_MODELVIEW_MATRIX = GL_MODELVIEW_MATRIX,
+ SW_MODELVIEW_STACK_DEPTH = GL_MODELVIEW_STACK_DEPTH,
+ SW_PROJECTION_MATRIX = GL_PROJECTION_MATRIX,
+ SW_PROJECTION_STACK_DEPTH = GL_PROJECTION_STACK_DEPTH,
+ SW_TEXTURE_MATRIX = GL_TEXTURE_MATRIX,
+ SW_TEXTURE_STACK_DEPTH = GL_TEXTURE_STACK_DEPTH,
+ SW_VIEWPORT = GL_VIEWPORT
+} SWget;
+
+typedef enum {
+ SW_COLOR_BUFFER_BIT = GL_COLOR_BUFFER_BIT,
+ SW_DEPTH_BUFFER_BIT = GL_DEPTH_BUFFER_BIT
+} SWbuffer;
+
+typedef enum {
+ SW_PROJECTION = GL_PROJECTION,
+ SW_MODELVIEW = GL_MODELVIEW,
+ SW_TEXTURE = GL_TEXTURE
+} SWmatrix;
+
+typedef enum {
+ SW_VERTEX_ARRAY = GL_VERTEX_ARRAY,
+ SW_TEXTURE_COORD_ARRAY = GL_TEXTURE_COORD_ARRAY,
+ SW_COLOR_ARRAY = GL_COLOR_ARRAY
+} SWarray;
+
+typedef enum {
+ SW_POINTS = GL_POINTS,
+ SW_LINES = GL_LINES,
+ SW_TRIANGLES = GL_TRIANGLES,
+ SW_QUADS = GL_QUADS
+} SWdraw;
+
+typedef enum {
+ SW_POINT = GL_POINT,
+ SW_LINE = GL_LINE,
+ SW_FILL = GL_FILL
+} SWpoly;
+
+typedef enum {
+ SW_FRONT = GL_FRONT,
+ SW_BACK = GL_BACK,
+} SWface;
+
+typedef enum {
+ SW_ZERO = GL_ZERO,
+ SW_ONE = GL_ONE,
+ SW_SRC_COLOR = GL_SRC_COLOR,
+ SW_ONE_MINUS_SRC_COLOR = GL_ONE_MINUS_SRC_COLOR,
+ SW_SRC_ALPHA = GL_SRC_ALPHA,
+ SW_ONE_MINUS_SRC_ALPHA = GL_ONE_MINUS_SRC_ALPHA,
+ SW_DST_ALPHA = GL_DST_ALPHA,
+ SW_ONE_MINUS_DST_ALPHA = GL_ONE_MINUS_DST_ALPHA,
+ SW_DST_COLOR = GL_DST_COLOR,
+ SW_ONE_MINUS_DST_COLOR = GL_ONE_MINUS_DST_COLOR,
+ SW_SRC_ALPHA_SATURATE = GL_SRC_ALPHA_SATURATE
+} SWfactor;
+
+typedef enum {
+ SW_LUMINANCE = GL_LUMINANCE,
+ SW_LUMINANCE_ALPHA = GL_LUMINANCE_ALPHA,
+ SW_RGB = GL_RGB,
+ SW_RGBA = GL_RGBA,
+} SWformat;
+
+typedef enum {
+ SW_UNSIGNED_BYTE = GL_UNSIGNED_BYTE,
+ SW_BYTE = GL_BYTE,
+ SW_UNSIGNED_SHORT = GL_UNSIGNED_SHORT,
+ SW_SHORT = GL_SHORT,
+ SW_UNSIGNED_INT = GL_UNSIGNED_INT,
+ SW_INT = GL_INT,
+ SW_FLOAT = GL_FLOAT
+} SWtype;
+
+typedef enum {
+ SW_NEAREST = GL_NEAREST,
+ SW_LINEAR = GL_LINEAR
+} SWfilter;
+
+typedef enum {
+ SW_REPEAT = GL_REPEAT,
+ SW_CLAMP = GL_CLAMP,
+} SWwrap;
+
+typedef enum {
+ SW_TEXTURE_MIN_FILTER = GL_TEXTURE_MIN_FILTER,
+ SW_TEXTURE_MAG_FILTER = GL_TEXTURE_MAG_FILTER,
+ SW_TEXTURE_WRAP_S = GL_TEXTURE_WRAP_S,
+ SW_TEXTURE_WRAP_T = GL_TEXTURE_WRAP_T
+} SWtexparam;
+
+typedef enum {
+ SW_NO_ERROR = GL_NO_ERROR,
+ SW_INVALID_ENUM = GL_INVALID_ENUM,
+ SW_INVALID_VALUE = GL_INVALID_VALUE,
+ SW_STACK_OVERFLOW = GL_STACK_OVERFLOW,
+ SW_STACK_UNDERFLOW = GL_STACK_UNDERFLOW,
+ SW_INVALID_OPERATION = GL_INVALID_OPERATION,
+} SWerrcode;
+
+//------------------------------------------------------------------------------------
+// Functions Declaration - Public API
+//------------------------------------------------------------------------------------
+SWAPI bool swInit(int w, int h);
+SWAPI void swClose(void);
+
+SWAPI bool swResizeFramebuffer(int w, int h);
+SWAPI void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels);
+SWAPI void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels);
+SWAPI void *swGetColorBuffer(int *w, int *h);
+
+SWAPI void swEnable(SWstate state);
+SWAPI void swDisable(SWstate state);
+
+SWAPI void swGetFloatv(SWget name, float *v);
+SWAPI const char *swGetString(SWget name);
+SWAPI SWerrcode swGetError(void);
+
+SWAPI void swViewport(int x, int y, int width, int height);
+SWAPI void swScissor(int x, int y, int width, int height);
+
+SWAPI void swClearColor(float r, float g, float b, float a);
+SWAPI void swClear(uint32_t bitmask);
+
+SWAPI void swBlendFunc(SWfactor sfactor, SWfactor dfactor);
+SWAPI void swPolygonMode(SWpoly mode);
+SWAPI void swCullFace(SWface face);
+
+SWAPI void swPointSize(float size);
+SWAPI void swLineWidth(float width);
+
+SWAPI void swMatrixMode(SWmatrix mode);
+SWAPI void swPushMatrix(void);
+SWAPI void swPopMatrix(void);
+SWAPI void swLoadIdentity(void);
+SWAPI void swTranslatef(float x, float y, float z);
+SWAPI void swRotatef(float angle, float x, float y, float z);
+SWAPI void swScalef(float x, float y, float z);
+SWAPI void swMultMatrixf(const float *mat);
+SWAPI void swFrustum(double left, double right, double bottom, double top, double znear, double zfar);
+SWAPI void swOrtho(double left, double right, double bottom, double top, double znear, double zfar);
+
+SWAPI void swBegin(SWdraw mode);
+SWAPI void swEnd(void);
+
+SWAPI void swVertex2i(int x, int y);
+SWAPI void swVertex2f(float x, float y);
+SWAPI void swVertex2fv(const float *v);
+SWAPI void swVertex3i(int x, int y, int z);
+SWAPI void swVertex3f(float x, float y, float z);
+SWAPI void swVertex3fv(const float *v);
+SWAPI void swVertex4i(int x, int y, int z, int w);
+SWAPI void swVertex4f(float x, float y, float z, float w);
+SWAPI void swVertex4fv(const float *v);
+
+SWAPI void swColor3ub(uint8_t r, uint8_t g, uint8_t b);
+SWAPI void swColor3ubv(const uint8_t *v);
+SWAPI void swColor3f(float r, float g, float b);
+SWAPI void swColor3fv(const float *v);
+SWAPI void swColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+SWAPI void swColor4ubv(const uint8_t *v);
+SWAPI void swColor4f(float r, float g, float b, float a);
+SWAPI void swColor4fv(const float *v);
+
+SWAPI void swTexCoord2f(float u, float v);
+SWAPI void swTexCoord2fv(const float *v);
+
+SWAPI void swBindArray(SWarray type, void *buffer);
+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 swTexParameteri(int param, int value);
+SWAPI void swBindTexture(uint32_t id);
+
+#endif // RLSW_H
+
+/***********************************************************************************
+*
+* RLSW IMPLEMENTATION
+*
+************************************************************************************/
+#define RLSW_IMPLEMENTATION
+#if defined(RLSW_IMPLEMENTATION)
+
+#include
+#include
+#include // Required for: floorf(), fabsf()
+
+//----------------------------------------------------------------------------------
+// Defines and Macros
+//----------------------------------------------------------------------------------
+#define SW_PI 3.14159265358979323846f
+#define SW_DEG2RAD (SW_PI/180.0f)
+#define SW_RAD2DEG (180.0f/SW_PI)
+
+#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))
+
+#define SW_STATE_SCISSOR_TEST (1 << 0)
+#define SW_STATE_TEXTURE_2D (1 << 1)
+#define SW_STATE_DEPTH_TEST (1 << 2)
+#define SW_STATE_CULL_FACE (1 << 3)
+#define SW_STATE_BLEND (1 << 4)
+
+//----------------------------------------------------------------------------------
+// Module Types and Structures Definition
+//----------------------------------------------------------------------------------
+// Pixel data format type
+// NOTE: Enum aligned with raylib PixelFormat
+typedef enum {
+ SW_PIXELFORMAT_UNKNOWN = 0,
+ SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, // 8 bit per pixel (no alpha)
+ SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels)
+ SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp
+ SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp
+ SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha)
+ SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha)
+ SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp
+ SW_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float)
+ SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float)
+ SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float)
+ SW_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float)
+ SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float)
+ SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float)
+} sw_pixelformat_t;
+
+typedef void (*sw_factor_f)(
+ float *SW_RESTRICT factor,
+ const float *SW_RESTRICT src,
+ const float *SW_RESTRICT dst
+);
+
+typedef float sw_matrix_t[4*4];
+typedef uint16_t sw_half_t;
+
+typedef struct {
+ float position[4]; // Position coordinates
+ float texcoord[2]; // Texture coordinates
+ float color[4]; // Color value (RGBA)
+
+ float homogeneous[4]; // Homogeneous coordinates
+ float screen[2]; // Screen coordinates
+} 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;
+
+ 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
+
+ SWwrap sWrap; // texcoord.x wrap mode
+ SWwrap tWrap; // texcoord.y wrap mode
+
+ float tx; // Texel width
+ float ty; // Texel height
+
+ bool copy; // Flag indicating whether memory has been allocated
+
+} sw_texture_t;
+
+typedef struct {
+ void *color;
+ void *depth;
+ int width;
+ int height;
+ int allocSz;
+} sw_framebuffer_t;
+
+typedef struct {
+ sw_framebuffer_t framebuffer; // Main framebuffer
+ float clearColor[4]; // Color used to clear the screen
+ float clearDepth; // Depth value used to clear the screen
+
+ int vpCenter[2]; // Viewport center
+ int vpHalfSize[2]; // Viewport half dimensions
+ int vpSize[2]; // Viewport dimensions (minus one)
+ int vpMin[2]; // Viewport minimum renderable point (top-left)
+ int vpMax[2]; // Viewport maximum renderable point (bottom-right)
+
+ int scMin[2]; // Scissor rectangle minimum renderable point (top-left)
+ int scMax[2]; // Scissor rectangle maximum renderable point (bottom-right)
+ float scClipMin[2]; // Scissor rectangle minimum renderable point in clip space
+ float scClipMax[2]; // Scissor rectangle maximum renderable point in clip space
+
+ uint32_t currentTexture; // Current active texture id
+
+ struct {
+ float *positions;
+ float *texcoords;
+ uint8_t *colors;
+ } array;
+
+ struct {
+ float texcoord[2];
+ float color[4];
+ } current;
+
+ sw_vertex_t vertexBuffer[SW_MAX_CLIPPED_POLYGON_VERTICES]; // Buffer used for storing primitive vertices, used for processing and rendering
+ int vertexCounter; // Number of vertices in 'ctx.vertexBuffer'
+
+ SWdraw drawMode; // Current primitive mode (e.g., lines, triangles)
+ SWpoly polyMode; // Current polygon filling mode (e.g., lines, triangles)
+ int reqVertices; // Number of vertices required for the primitive being drawn
+ float pointRadius; // Rasterized point radius
+ float lineWidth; // Rasterized line width
+
+ sw_matrix_t stackProjection[SW_MAX_PROJECTION_STACK_SIZE]; // Projection matrix stack for push/pop operations
+ sw_matrix_t stackModelview[SW_MAX_MODELVIEW_STACK_SIZE]; // Modelview matrix stack for push/pop operations
+ sw_matrix_t stackTexture[SW_MAX_TEXTURE_STACK_SIZE]; // Texture matrix stack for push/pop operations
+ uint32_t stackProjectionCounter; // Counter for matrix stack operations
+ uint32_t stackModelviewCounter; // Counter for matrix stack operations
+ uint32_t stackTextureCounter; // Counter for matrix stack operations
+ SWmatrix currentMatrixMode; // Current matrix mode (e.g., sw_MODELVIEW, sw_PROJECTION)
+ sw_matrix_t *currentMatrix; // Pointer to the currently used matrix according to the mode
+ sw_matrix_t matMVP; // Model view projection matrix, calculated and used internally
+ bool isDirtyMVP; // Indicates if the MVP matrix should be rebuilt
+
+ SWfactor srcFactor;
+ SWfactor dstFactor;
+
+ sw_factor_f srcFactorFunc;
+ sw_factor_f dstFactorFunc;
+
+ SWface cullFace; // Faces to cull
+ SWerrcode errCode; // Last error code
+
+ sw_texture_t *loadedTextures;
+ int loadedTextureCount;
+
+ uint32_t *freeTextureIds;
+ int freeTextureIdCount;
+
+ uint32_t stateFlags;
+
+} sw_context_t;
+
+//----------------------------------------------------------------------------------
+// Global Variables Definition
+//----------------------------------------------------------------------------------
+static sw_context_t RLSW = { 0 };
+
+//----------------------------------------------------------------------------------
+// Module Functions Declaration
+//----------------------------------------------------------------------------------
+static inline void sw_matrix_id(sw_matrix_t dst)
+{
+ dst[0] = 1, dst[1] = 0, dst[2] = 0, dst[3] = 0;
+ dst[4] = 0, dst[5] = 1, dst[6] = 0, dst[7] = 0;
+ dst[8] = 0, dst[9] = 0, dst[10] = 1, dst[11] = 0;
+ dst[12] = 0, dst[13] = 0, dst[14] = 0, dst[15] = 1;
+}
+
+static inline void sw_matrix_mul_rst(float *SW_RESTRICT dst, const float *SW_RESTRICT left, const float *SW_RESTRICT right)
+{
+ float l00 = left[0], l01 = left[1], l02 = left[2], l03 = left[3];
+ float l10 = left[4], l11 = left[5], l12 = left[6], l13 = left[7];
+ float l20 = left[8], l21 = left[9], l22 = left[10], l23 = left[11];
+ float l30 = left[12], l31 = left[13], l32 = left[14], l33 = left[15];
+
+ dst[0] = l00*right[0] + l01*right[4] + l02*right[8] + l03*right[12];
+ dst[4] = l10*right[0] + l11*right[4] + l12*right[8] + l13*right[12];
+ dst[8] = l20*right[0] + l21*right[4] + l22*right[8] + l23*right[12];
+ dst[12] = l30*right[0] + l31*right[4] + l32*right[8] + l33*right[12];
+
+ dst[1] = l00*right[1] + l01*right[5] + l02*right[9] + l03*right[13];
+ dst[5] = l10*right[1] + l11*right[5] + l12*right[9] + l13*right[13];
+ dst[9] = l20*right[1] + l21*right[5] + l22*right[9] + l23*right[13];
+ dst[13] = l30*right[1] + l31*right[5] + l32*right[9] + l33*right[13];
+
+ dst[2] = l00*right[2] + l01*right[6] + l02*right[10] + l03*right[14];
+ dst[6] = l10*right[2] + l11*right[6] + l12*right[10] + l13*right[14];
+ dst[10] = l20*right[2] + l21*right[6] + l22*right[10] + l23*right[14];
+ dst[14] = l30*right[2] + l31*right[6] + l32*right[10] + l33*right[14];
+
+ dst[3] = l00*right[3] + l01*right[7] + l02*right[11] + l03*right[15];
+ dst[7] = l10*right[3] + l11*right[7] + l12*right[11] + l13*right[15];
+ dst[11] = l20*right[3] + l21*right[7] + l22*right[11] + l23*right[15];
+ dst[15] = l30*right[3] + l31*right[7] + l32*right[11] + l33*right[15];
+}
+
+static inline void sw_matrix_mul(sw_matrix_t dst, const sw_matrix_t left, const sw_matrix_t right)
+{
+ float result[16];
+
+ sw_matrix_mul_rst(result, left, right);
+
+ for (int i = 0; i < 16; i++) dst[i] = result[i];
+}
+
+static inline float sw_saturate(float x)
+{
+ union { float f; uint32_t u; } fb;
+ fb.f = x;
+
+ // Check if x < 0.0f
+ // If sign bit is set (MSB), x is negative
+ if ((fb.u & 0x80000000) != 0) return 0.0f;
+
+ // Check if x > 1.0f
+ // Works for positive floats: IEEE 754 ordering matches integer ordering
+ if (fb.u > 0x3F800000) return 1.0f;
+
+ // x is in [0.0f, 1.0f]
+ return x;
+}
+
+static inline float sw_fract(float x)
+{
+ return (x - floorf(x));
+}
+
+static inline int sw_clampi(int v, int min, int max)
+{
+ if (v < min) return min;
+ if (v > max) return 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)
+{
+ const float tInv = 1.0f - t;
+
+ // Position interpolation (4 components)
+ out->position[0] = a->position[0]*tInv + b->position[0]*t;
+ out->position[1] = a->position[1]*tInv + b->position[1]*t;
+ out->position[2] = a->position[2]*tInv + b->position[2]*t;
+ out->position[3] = a->position[3]*tInv + b->position[3]*t;
+
+ // Texture coordinate interpolation (2 components)
+ out->texcoord[0] = a->texcoord[0]*tInv + b->texcoord[0]*t;
+ out->texcoord[1] = a->texcoord[1]*tInv + b->texcoord[1]*t;
+
+ // Color interpolation (4 components)
+ out->color[0] = a->color[0]*tInv + b->color[0]*t;
+ out->color[1] = a->color[1]*tInv + b->color[1]*t;
+ out->color[2] = a->color[2]*tInv + b->color[2]*t;
+ out->color[3] = a->color[3]*tInv + b->color[3]*t;
+
+ // Homogeneous coordinate interpolation (4 components)
+ out->homogeneous[0] = a->homogeneous[0]*tInv + b->homogeneous[0]*t;
+ out->homogeneous[1] = a->homogeneous[1]*tInv + b->homogeneous[1]*t;
+ out->homogeneous[2] = a->homogeneous[2]*tInv + b->homogeneous[2]*t;
+ 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)
+{
+ // Calculate gradients for Position
+ out->position[0] = (b->position[0] - a->position[0])*scale;
+ out->position[1] = (b->position[1] - a->position[1])*scale;
+ out->position[2] = (b->position[2] - a->position[2])*scale;
+ out->position[3] = (b->position[3] - a->position[3])*scale;
+
+ // Calculate gradients for Texture coordinates
+ out->texcoord[0] = (b->texcoord[0] - a->texcoord[0])*scale;
+ out->texcoord[1] = (b->texcoord[1] - a->texcoord[1])*scale;
+
+ // Calculate gradients for Color
+ out->color[0] = (b->color[0] - a->color[0])*scale;
+ out->color[1] = (b->color[1] - a->color[1])*scale;
+ out->color[2] = (b->color[2] - a->color[2])*scale;
+ out->color[3] = (b->color[3] - a->color[3])*scale;
+
+ // Calculate gradients for Homogeneous coordinates
+ out->homogeneous[0] = (b->homogeneous[0] - a->homogeneous[0])*scale;
+ out->homogeneous[1] = (b->homogeneous[1] - a->homogeneous[1])*scale;
+ out->homogeneous[2] = (b->homogeneous[2] - a->homogeneous[2])*scale;
+ 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)
+{
+ // Add gradients to Position
+ out->position[0] += gradients->position[0];
+ out->position[1] += gradients->position[1];
+ out->position[2] += gradients->position[2];
+ out->position[3] += gradients->position[3];
+
+ // Add gradients to Texture coordinates
+ out->texcoord[0] += gradients->texcoord[0];
+ out->texcoord[1] += gradients->texcoord[1];
+
+ // Add gradients to Color
+ out->color[0] += gradients->color[0];
+ out->color[1] += gradients->color[1];
+ out->color[2] += gradients->color[2];
+ out->color[3] += gradients->color[3];
+
+ // Add gradients to Homogeneous coordinates
+ out->homogeneous[0] += gradients->homogeneous[0];
+ out->homogeneous[1] += gradients->homogeneous[1];
+ out->homogeneous[2] += gradients->homogeneous[2];
+ out->homogeneous[3] += gradients->homogeneous[3];
+}
+
+// Half floating point management functions
+static inline uint32_t sw_f16_to_f32_ui(uint16_t h)
+{
+ uint32_t s = (uint32_t)(h & 0x8000) << 16;
+ int32_t em = h & 0x7fff;
+
+ // bias exponent and pad mantissa with 0; 112 is relative exponent bias (127-15)
+ int32_t r = (em + (112 << 10)) << 13;
+
+ // denormal: flush to zero
+ r = (em < (1 << 10))? 0 : r;
+
+ // infinity/NaN; note that we preserve NaN payload as a byproduct of unifying inf/nan cases
+ // 112 is an exponent bias fixup; since we already applied it once, applying it twice converts 31 to 255
+ r += (em >= (31 << 10))? (112 << 23) : 0;
+
+ return s | r;
+}
+
+static inline float sw_f16_to_f32(sw_half_t y)
+{
+ union { float f; uint32_t i; } v = { .i = sw_f16_to_f32_ui(y) };
+
+ return v.f;
+}
+
+static inline uint16_t sw_f16_from_f32_ui(uint32_t ui)
+{
+ int32_t s = (ui >> 16) & 0x8000;
+ int32_t em = ui & 0x7fffffff;
+
+ // Bias exponent and round to nearest; 112 is relative exponent bias (127-15)
+ int32_t h = (em - (112 << 23) + (1 << 12)) >> 13;
+
+ // Underflow: flush to zero; 113 encodes exponent -14
+ h = (em < (113 << 23))? 0 : h;
+
+ // Overflow: infinity; 143 encodes exponent 16
+ h = (em >= (143 << 23))? 0x7c00 : h;
+
+ // NaN; note that we convert all types of NaN to qNaN
+ h = (em > (255 << 23))? 0x7e00 : h;
+
+ return (uint16_t)(s | h);
+}
+
+static inline sw_half_t sw_f16_from_f32(float i)
+{
+ union { float f; uint32_t i; } v;
+ v.f = i;
+ return sw_f16_from_f32_ui(v.i);
+}
+
+// Framebuffer management functions
+static inline bool sw_framebuffer_load(int w, int h)
+{
+ int size = w*h;
+
+ RLSW.framebuffer.color = SW_MALLOC(SW_COLOR_PIXEL_SIZE*size);
+ if (RLSW.framebuffer.color == NULL) return false;
+
+ RLSW.framebuffer.depth = SW_MALLOC(SW_DEPTH_PIXEL_SIZE*size);
+ if (RLSW.framebuffer.depth == NULL) return false;
+
+ RLSW.framebuffer.width = w;
+ RLSW.framebuffer.height = h;
+ RLSW.framebuffer.allocSz = size;
+
+ return true;
+}
+
+static inline bool sw_framebuffer_resize(int w, int h)
+{
+ int newSize = w*h;
+
+ if (newSize <= RLSW.framebuffer.allocSz)
+ {
+ RLSW.framebuffer.width = w;
+ RLSW.framebuffer.height = h;
+ return true;
+ }
+
+ void *newColor = SW_REALLOC(RLSW.framebuffer.color, newSize);
+ if (newColor == NULL) return false;
+
+ void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, newSize);
+ if (newDepth == NULL) return false;
+
+ RLSW.framebuffer.color = newColor;
+ RLSW.framebuffer.depth = newDepth;
+
+ RLSW.framebuffer.width = w;
+ RLSW.framebuffer.height = h;
+ RLSW.framebuffer.allocSz = newSize;
+
+ 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);
+ dst[3] = 1.0f;
+}
+
+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;
+ dst[3] = 255;
+}
+
+static inline void sw_framebuffer_write_color(void *dst, const float color[3])
+{
+ 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;
+}
+
+static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3])
+{
+ 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 (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 + 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++)
+ {
+ *curPtr++ = r;
+ *curPtr++ = g;
+ *curPtr++ = b;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ *p++ = r;
+ *p++ = g;
+ *p++ = b;
+ }
+ }
+}
+
+#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);
+}
+
+static inline void sw_framebuffer_write_depth(void *dst, float depth)
+{
+ ((uint8_t *)dst)[0] = (uint8_t)(depth*UINT8_MAX);
+}
+
+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 (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 + 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++)
+ {
+ *curPtr++ = d_byte0;
+ *curPtr++ = d_byte1;
+ *curPtr++ = d_byte2;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ *p++ = d_byte0;
+ *p++ = d_byte1;
+ *p++ = d_byte2;
+ }
+ }
+}
+
+#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) \
+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++) { \
+ 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); \
+ } \
+ } \
+}
+
+DEFINE_FRAMEBUFFER_COPY_U32_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);
+ *dst++ = gray;
+}
+DEFINE_FRAMEBUFFER_COPY_END()
+
+DEFINE_FRAMEBUFFER_COPY_U32_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);
+
+ dst[0] = gray;
+ dst[1] = color[3]; // alpha
+
+ dst += 2;
+}
+DEFINE_FRAMEBUFFER_COPY_END()
+
+DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G6B5, uint16_t)
+{
+ // Convert 8-bit RGB to 5:6:5 format
+ uint8_t r5 = (color[0]*31 + 127)/255;
+ uint8_t g6 = (color[1]*63 + 127)/255;
+ uint8_t b5 = (color[2]*31 + 127)/255;
+
+#if SW_GL_FRAMEBUFFER_COPY_BGRA
+ uint16_t rgb565 = (b5 << 11) | (g6 << 5) | r5;
+#else // RGBA
+ uint16_t rgb565 = (r5 << 11) | (g6 << 5) | b5;
+#endif
+
+ *dst++ = rgb565;
+}
+DEFINE_FRAMEBUFFER_COPY_END()
+
+DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8, uint8_t)
+{
+#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_U32_BEGIN(R5G5B5A1, uint16_t)
+{
+ uint8_t r5 = (color[0]*31 + 127)/255;
+ uint8_t g5 = (color[1]*31 + 127)/255;
+ uint8_t b5 = (color[2]*31 + 127)/255;
+ uint8_t a1 = color[3] >= 128 ? 1 : 0;
+
+#if SW_GL_FRAMEBUFFER_COPY_BGRA
+ uint16_t pixel = (b5 << 11) | (g5 << 6) | (r5 << 1) | a1;
+#else // RGBA
+ uint16_t pixel = (r5 << 11) | (g5 << 6) | (b5 << 1) | a1;
+#endif
+
+ *dst++ = pixel;
+}
+DEFINE_FRAMEBUFFER_COPY_END()
+
+DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R4G4B4A4, uint16_t)
+{
+ uint8_t r4 = (color[0]*15 + 127)/255;
+ uint8_t g4 = (color[1]*15 + 127)/255;
+ uint8_t b4 = (color[2]*15 + 127)/255;
+ uint8_t a4 = (color[3]*15 + 127)/255;
+
+#if SW_GL_FRAMEBUFFER_COPY_BGRA
+ uint16_t pixel = (b4 << 12) | (g4 << 8) | (r4 << 4) | a4;
+#else // RGBA
+ uint16_t pixel = (r4 << 12) | (g4 << 8) | (b4 << 4) | a4;
+#endif
+
+ *dst++ = pixel;
+}
+DEFINE_FRAMEBUFFER_COPY_END()
+
+DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8A8, uint8_t)
+{
+#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(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) \
+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 = 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); \
+ 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 = 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)
+{
+ 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)
+{
+ uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000);
+
+ dst[0] = gray;
+ dst[1] = color[3]; // alpha
+
+ dst += 2;
+}
+DEFINE_FRAMEBUFFER_BLIT_END()
+
+DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G6B5, uint16_t)
+{
+ uint8_t r5 = (color[0]*31 + 127)/255;
+ uint8_t g6 = (color[1]*63 + 127)/255;
+ uint8_t b5 = (color[2]*31 + 127)/255;
+
+#if SW_GL_FRAMEBUFFER_COPY_BGRA
+ uint16_t rgb565 = (b5 << 11) | (g6 << 5) | r5;
+#else // RGBA
+ uint16_t rgb565 = (r5 << 11) | (g6 << 5) | b5;
+#endif
+
+ *dst++ = rgb565;
+}
+DEFINE_FRAMEBUFFER_BLIT_END()
+
+DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8, uint8_t)
+{
+#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_U32_BEGIN(R5G5B5A1, uint16_t)
+{
+ uint8_t r5 = (color[0]*31 + 127)/255;
+ uint8_t g5 = (color[1]*31 + 127)/255;
+ uint8_t b5 = (color[2]*31 + 127)/255;
+ uint8_t a1 = color[3] >= 128 ? 1 : 0;
+
+#if SW_GL_FRAMEBUFFER_COPY_BGRA
+ uint16_t pixel = (b5 << 11) | (g5 << 6) | (r5 << 1) | a1;
+#else // RGBA
+ uint16_t pixel = (r5 << 11) | (g5 << 6) | (b5 << 1) | a1;
+#endif
+
+ *dst++ = pixel;
+}
+DEFINE_FRAMEBUFFER_BLIT_END()
+
+DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R4G4B4A4, uint16_t)
+{
+ uint8_t r4 = (color[0]*15 + 127)/255;
+ uint8_t g4 = (color[1]*15 + 127)/255;
+ uint8_t b4 = (color[2]*15 + 127)/255;
+ uint8_t a4 = (color[3]*15 + 127)/255;
+
+#if SW_GL_FRAMEBUFFER_COPY_BGRA
+ uint16_t pixel = (b4 << 12) | (g4 << 8) | (r4 << 4) | a4;
+#else // RGBA
+ uint16_t pixel = (r4 << 12) | (g4 << 8) | (b4 << 4) | a4;
+#endif
+
+ *dst++ = pixel;
+}
+DEFINE_FRAMEBUFFER_BLIT_END()
+
+DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8A8, uint8_t)
+{
+#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(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)
+{
+ int channels = 0;
+ int bitsPerChannel = 8; // Default: 8 bits per channel
+
+ // Determine the number of channels (format)
+ switch (format)
+ {
+ case SW_LUMINANCE: channels = 1; break;
+ case SW_LUMINANCE_ALPHA: channels = 2; break;
+ case SW_RGB: channels = 3; break;
+ case SW_RGBA: channels = 4; break;
+ default: return SW_PIXELFORMAT_UNKNOWN;
+ }
+
+ // Determine the depth of each channel (type)
+ switch (type)
+ {
+ case SW_UNSIGNED_BYTE: bitsPerChannel = 8; break;
+ case SW_BYTE: bitsPerChannel = 8; break;
+ case SW_UNSIGNED_SHORT: bitsPerChannel = 16; break;
+ case SW_SHORT: bitsPerChannel = 16; break;
+ case SW_UNSIGNED_INT: bitsPerChannel = 32; break;
+ case SW_INT: bitsPerChannel = 32; break;
+ case SW_FLOAT: bitsPerChannel = 32; break;
+ default: return SW_PIXELFORMAT_UNKNOWN;
+ }
+
+ // Map the format and type to the correct internal format
+ if (bitsPerChannel == 8)
+ {
+ if (channels == 1) return SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
+ if (channels == 2) return SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA;
+ if (channels == 3) return SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8;
+ if (channels == 4) return SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+ }
+ else if (bitsPerChannel == 16)
+ {
+ if (channels == 1) return SW_PIXELFORMAT_UNCOMPRESSED_R16;
+ if (channels == 3) return SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16;
+ if (channels == 4) return SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16;
+ }
+ else if (bitsPerChannel == 32)
+ {
+ if (channels == 1) return SW_PIXELFORMAT_UNCOMPRESSED_R32;
+ if (channels == 3) return SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32;
+ if (channels == 4) return SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32;
+ }
+
+ return SW_PIXELFORMAT_UNKNOWN;
+}
+
+int sw_get_pixel_bytes(sw_pixelformat_t format)
+{
+ int bpp = 0;
+
+ switch (format)
+ {
+ case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 1; break;
+ case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA:
+ case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5:
+ 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;
+ }
+}
+
+// Texture sampling functionality
+
+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;
+
+ sw_get_pixel(color, tex->pixels.cptr, y*tex->width + x, tex->format);
+}
+
+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
+ // number of operations here, but for now it works fine.
+
+ 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 = xf - x0;
+ float fy = yf - y0;
+
+ int x1 = x0 + 1;
+ int y1 = y0 + 1;
+
+ if (tex->sWrap == SW_CLAMP)
+ {
+ x0 = (x0 > tex->wMinus1)? tex->wMinus1 : x0;
+ x1 = (x1 > tex->wMinus1)? tex->wMinus1 : x1;
+ }
+ else
+ {
+ x0 = (x0%tex->width + tex->width)%tex->width;
+ x1 = (x1%tex->width + tex->width)%tex->width;
+ }
+
+ if (tex->tWrap == SW_CLAMP)
+ {
+ y0 = (y0 > tex->hMinus1)? tex->hMinus1 : y0;
+ y1 = (y1 > tex->hMinus1)? tex->hMinus1 : y1;
+ }
+ else
+ {
+ y0 = (y0%tex->height + tex->height)%tex->height;
+ y1 = (y1%tex->height + tex->height)%tex->height;
+ }
+
+ 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);
+
+ for (int i = 0; i < 4; i++)
+ {
+ float t = c00[i] + fx*(c10[i] - c00[i]);
+ float b = c01[i] + fx*(c11[i] - c01[i]);
+ color[i] = t + fy*(b - t);
+ }
+}
+
+static inline void sw_texture_sample(float *color, const sw_texture_t *tex, float u, float v, float duDx, float duDy, float dvDx, float dvDy)
+{
+ // Previous method: There is no need to compute the square root
+ // because using the squared value, the comparison remains `L2 > 1.0f*1.0f`
+ //float du = sqrtf(duDx*duDx + duDy*duDy);
+ //float dv = sqrtf(dvDx*dvDx + dvDy*dvDy);
+ //float L = (du > dv)? du : dv;
+
+ // Calculate the derivatives for each axis
+ float du2 = duDx*duDx + duDy*duDy;
+ float dv2 = dvDx*dvDx + dvDy*dvDy;
+ float L2 = (du2 > dv2)? du2 : dv2;
+
+ SWfilter filter = (L2 > 1.0f)? tex->minFilter : tex->magFilter;
+
+ switch (filter)
+ {
+ case SW_NEAREST: sw_texture_sample_nearest(color, tex, u, v); break;
+ case SW_LINEAR: sw_texture_sample_linear(color, tex, u, v); break;
+ default: break;
+ }
+}
+
+// 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;
+}
+
+static inline void sw_factor_one(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = factor[1] = factor[2] = factor[3] = 1.0f;
+}
+
+static inline void sw_factor_src_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = src[0]; factor[1] = src[1]; factor[2] = src[2]; factor[3] = src[3];
+}
+
+static inline void sw_factor_one_minus_src_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = 1.0f - src[0]; factor[1] = 1.0f - src[1];
+ factor[2] = 1.0f - src[2]; factor[3] = 1.0f - src[3];
+}
+
+static inline void sw_factor_src_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = factor[1] = factor[2] = factor[3] = src[3];
+}
+
+static inline void sw_factor_one_minus_src_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ float invAlpha = 1.0f - src[3];
+ factor[0] = factor[1] = factor[2] = factor[3] = invAlpha;
+}
+
+static inline void sw_factor_dst_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = factor[1] = factor[2] = factor[3] = dst[3];
+}
+
+static inline void sw_factor_one_minus_dst_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ float invAlpha = 1.0f - dst[3];
+ factor[0] = factor[1] = factor[2] = factor[3] = invAlpha;
+}
+
+static inline void sw_factor_dst_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = dst[0]; factor[1] = dst[1]; factor[2] = dst[2]; factor[3] = dst[3];
+}
+
+static inline void sw_factor_one_minus_dst_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = 1.0f - dst[0]; factor[1] = 1.0f - dst[1];
+ factor[2] = 1.0f - dst[2]; factor[3] = 1.0f - dst[3];
+}
+
+static inline void sw_factor_src_alpha_saturate(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst)
+{
+ factor[0] = factor[1] = factor[2] = 1.0f;
+ factor[3] = (src[3] < 1.0f)? src[3] : 1.0f;
+}
+
+static inline void sw_blend_colors(float *SW_RESTRICT dst/*[4]*/, const float *SW_RESTRICT src/*[4]*/)
+{
+ float srcFactor[4], dstFactor[4];
+
+ RLSW.srcFactorFunc(srcFactor, src, dst);
+ RLSW.dstFactorFunc(dstFactor, src, dst);
+
+ dst[0] = srcFactor[0]*src[0] + dstFactor[0]*dst[0];
+ dst[1] = srcFactor[1]*src[1] + dstFactor[1]*dst[1];
+ 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
+
+#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], \
+ const sw_vertex_t input[SW_MAX_CLIPPED_POLYGON_VERTICES], \
+ int n) \
+{ \
+ const sw_vertex_t *prev = &input[n - 1]; \
+ int prevInside = FUNC_IS_INSIDE(prev->homogeneous); \
+ int outputCount = 0; \
+ \
+ for (int i = 0; i < n; i++) { \
+ const sw_vertex_t *curr = &input[i]; \
+ int currInside = FUNC_IS_INSIDE(curr->homogeneous); \
+ \
+ /* If transition between interior/exterior, calculate intersection point */ \
+ if (prevInside != currInside) { \
+ float t = FUNC_COMPUTE_T(prev->homogeneous, curr->homogeneous); \
+ sw_lerp_vertex_PTCH(&output[outputCount++], prev, curr, t); \
+ } \
+ \
+ /* If current vertex inside, add it */ \
+ if (currInside) { \
+ output[outputCount++] = *curr; \
+ } \
+ \
+ prev = curr; \
+ prevInside = currInside; \
+ } \
+ \
+ 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])
+#define IS_INSIDE_PLANE_Y_POS(h) ((h)[1] <= (h)[3])
+#define IS_INSIDE_PLANE_Y_NEG(h) (-(h)[1] <= (h)[3])
+#define IS_INSIDE_PLANE_Z_POS(h) ((h)[2] <= (h)[3])
+#define IS_INSIDE_PLANE_Z_NEG(h) (-(h)[2] <= (h)[3])
+
+#define COMPUTE_T_PLANE_W(hPrev, hCurr) ((SW_CLIP_EPSILON - (hPrev)[3])/((hCurr)[3] - (hPrev)[3]))
+#define COMPUTE_T_PLANE_X_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[0])/(((hPrev)[3] - (hPrev)[0]) - ((hCurr)[3] - (hCurr)[0])))
+#define COMPUTE_T_PLANE_X_NEG(hPrev, hCurr) (((hPrev)[3] + (hPrev)[0])/(((hPrev)[3] + (hPrev)[0]) - ((hCurr)[3] + (hCurr)[0])))
+#define COMPUTE_T_PLANE_Y_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[1])/(((hPrev)[3] - (hPrev)[1]) - ((hCurr)[3] - (hCurr)[1])))
+#define COMPUTE_T_PLANE_Y_NEG(hPrev, hCurr) (((hPrev)[3] + (hPrev)[1])/(((hPrev)[3] + (hPrev)[1]) - ((hCurr)[3] + (hCurr)[1])))
+#define COMPUTE_T_PLANE_Z_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[2])/(((hPrev)[3] - (hPrev)[2]) - ((hCurr)[3] - (hCurr)[2])))
+#define COMPUTE_T_PLANE_Z_NEG(hPrev, hCurr) (((hPrev)[3] + (hPrev)[2])/(((hPrev)[3] + (hPrev)[2]) - ((hCurr)[3] + (hCurr)[2])))
+
+DEFINE_CLIP_FUNC(w, IS_INSIDE_PLANE_W, COMPUTE_T_PLANE_W)
+DEFINE_CLIP_FUNC(x_pos, IS_INSIDE_PLANE_X_POS, COMPUTE_T_PLANE_X_POS)
+DEFINE_CLIP_FUNC(x_neg, IS_INSIDE_PLANE_X_NEG, COMPUTE_T_PLANE_X_NEG)
+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])))
+#define COMPUTE_T_SCISSOR_Y_MAX(hPrev, hCurr) (((RLSW.scClipMax[1])*(hPrev)[3] - (hPrev)[1])/(((hCurr)[1] - (RLSW.scClipMax[1])*(hCurr)[3]) - ((hPrev)[1] - (RLSW.scClipMax[1])*(hPrev)[3])))
+
+#define IS_INSIDE_SCISSOR_X_MIN(h) ((h)[0] >= (RLSW.scClipMin[0])*(h)[3])
+#define IS_INSIDE_SCISSOR_X_MAX(h) ((h)[0] <= (RLSW.scClipMax[0])*(h)[3])
+#define IS_INSIDE_SCISSOR_Y_MIN(h) ((h)[1] >= (RLSW.scClipMin[1])*(h)[3])
+#define IS_INSIDE_SCISSOR_Y_MAX(h) ((h)[1] <= (RLSW.scClipMax[1])*(h)[3])
+
+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
+
+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];
+
+ int n = *vertexCounter;
+
+ #define CLIP_AGAINST_PLANE(FUNC_CLIP) \
+ { \
+ n = FUNC_CLIP(tmp, polygon, n); \
+ if (n < 3) \
+ { \
+ *vertexCounter = 0; \
+ return false; \
+ } \
+ for (int i = 0; i < n; i++) polygon[i] = tmp[i]; \
+ }
+
+ CLIP_AGAINST_PLANE(sw_clip_w);
+ CLIP_AGAINST_PLANE(sw_clip_x_pos);
+ CLIP_AGAINST_PLANE(sw_clip_x_neg);
+ CLIP_AGAINST_PLANE(sw_clip_y_pos);
+ CLIP_AGAINST_PLANE(sw_clip_y_neg);
+ CLIP_AGAINST_PLANE(sw_clip_z_pos);
+ CLIP_AGAINST_PLANE(sw_clip_z_neg);
+
+ if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST)
+ {
+ CLIP_AGAINST_PLANE(sw_clip_scissor_x_min);
+ CLIP_AGAINST_PLANE(sw_clip_scissor_x_max);
+ CLIP_AGAINST_PLANE(sw_clip_scissor_y_min);
+ CLIP_AGAINST_PLANE(sw_clip_scissor_y_max);
+ }
+
+ *vertexCounter = n;
+
+ return (n >= 3);
+}
+
+// Triangle rendering logic
+
+static inline bool sw_triangle_face_culling(void)
+{
+ // NOTE: Face culling is done before clipping to avoid unnecessary computations.
+ // To handle triangles crossing the w=0 plane correctly,
+ // we perform the winding order test in homogeneous coordinates directly,
+ // before the perspective division (division by w).
+ // This test determines the orientation of the triangle in the (x,y,w) plane,
+ // which corresponds to the projected 2D winding order sign,
+ // even with negative w values.
+
+ // Preload homogeneous coordinates into local variables
+ const float *h0 = RLSW.vertexBuffer[0].homogeneous;
+ const float *h1 = RLSW.vertexBuffer[1].homogeneous;
+ const float *h2 = RLSW.vertexBuffer[2].homogeneous;
+
+ // Compute a value proportional to the signed area in the projected 2D plane,
+ // calculated directly using homogeneous coordinates BEFORE division by w.
+ // This is the determinant of the matrix formed by the (x, y, w) components
+ // of the vertices, which correctly captures the winding order in homogeneous
+ // space and its relationship to the projected 2D winding order, even with
+ // negative w values.
+ // The determinant formula used here is:
+ // h0.x*(h1.y*h2.w - h2.y*h1.w) +
+ // h1.x*(h2.y*h0.w - h0.y*h2.w) +
+ // h2.x*(h0.y*h1.w - h1.y*h0.w)
+
+ const float hSgnArea =
+ h0[0]*(h1[1]*h2[3] - h2[1]*h1[3]) +
+ h1[0]*(h2[1]*h0[3] - h0[1]*h2[3]) +
+ h2[0]*(h0[1]*h1[3] - h1[1]*h0[3]);
+
+ // Discard the triangle if its winding order (determined by the sign
+ // of the homogeneous area/determinant) matches the culled direction.
+ // A positive hSgnArea typically corresponds to a counter-clockwise
+ // winding in the projected space when all w > 0.
+ // This test is robust for points with w > 0 or w < 0, correctly
+ // capturing the change in orientation when crossing the w=0 plane.
+
+ // The culling logic remains the same based on the signed area/determinant.
+ // A value of 0 for hSgnArea means the points are collinear in (x, y, w)
+ // space, which corresponds to a degenerate triangle projection.
+ // Such triangles are typically not culled by this test (0 < 0 is false, 0 > 0 is false)
+ // and should be handled by the clipper if necessary.
+ return (RLSW.cullFace == SW_FRONT)
+ ? (hSgnArea < 0) // Cull if winding is "clockwise" in the projected sense
+ : (hSgnArea > 0); // Cull if winding is "counter-clockwise" in the projected sense
+}
+
+static inline void sw_triangle_clip_and_project(void)
+{
+ sw_vertex_t *polygon = RLSW.vertexBuffer;
+ int *vertexCounter = &RLSW.vertexCounter;
+
+ if (sw_polygon_clip(polygon, vertexCounter))
+ {
+ // Transformation to screen space and normalization
+ for (int i = 0; i < *vertexCounter; i++)
+ {
+ sw_vertex_t *v = &polygon[i];
+
+ // 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;
+
+ // Division of XYZ coordinates by weight
+ v->homogeneous[0] *= invW;
+ v->homogeneous[1] *= invW;
+ v->homogeneous[2] *= invW;
+
+ // Division of texture coordinates (perspective-correct)
+ v->texcoord[0] *= invW;
+ v->texcoord[1] *= invW;
+
+ // Division of colors (perspective-correct)
+ v->color[0] *= invW;
+ v->color[1] *= invW;
+ v->color[2] *= invW;
+ v->color[3] *= invW;
+
+ // Transformation to screen space
+ sw_project_ndc_to_screen(v->screen, v->homogeneous);
+ }
+ }
+}
+
+#define DEFINE_TRIANGLE_RASTER_SCANLINE(FUNC_NAME, ENABLE_TEXTURE, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \
+static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \
+ const sw_vertex_t *end, float duDy, float dvDy) \
+{ \
+ /* Convert and center the screen coordinates */ \
+ int xStart = (int)(start->screen[0] + 0.5f); \
+ int xEnd = (int)(end->screen[0] + 0.5f); \
+ int y = (int)start->screen[1]; \
+ \
+ /* Compute the inverse horizontal distance along the X axis */ \
+ float dx = end->screen[0] - start->screen[0]; \
+ if (fabsf(dx) < 1e-6f) return; \
+ float dxRcp = 1.0f/dx; \
+ \
+ /* Compute the interpolation steps along the X axis */ \
+ float dzDx = (end->homogeneous[2] - start->homogeneous[2])*dxRcp; \
+ float dwDx = (end->homogeneous[3] - start->homogeneous[3])*dxRcp; \
+ \
+ float dcDx[4] = { 0 }; \
+ dcDx[0] = (end->color[0] - start->color[0])*dxRcp; \
+ dcDx[1] = (end->color[1] - start->color[1])*dxRcp; \
+ dcDx[2] = (end->color[2] - start->color[2])*dxRcp; \
+ dcDx[3] = (end->color[3] - start->color[3])*dxRcp; \
+ \
+ float duDx = 0.0f, dvDx = 0.0f; \
+ if (ENABLE_TEXTURE) { \
+ duDx = (end->texcoord[0] - start->texcoord[0])*dxRcp; \
+ dvDx = (end->texcoord[1] - start->texcoord[1])*dxRcp; \
+ } \
+ \
+ /* Initializing the interpolation starting values */ \
+ float z = start->homogeneous[2]; \
+ float w = start->homogeneous[3]; \
+ \
+ float color[4] = { 0 }; \
+ color[0] = start->color[0]; \
+ color[1] = start->color[1]; \
+ color[2] = start->color[2]; \
+ color[3] = start->color[3]; \
+ \
+ float u = 0.0f, v = 0.0f; \
+ if (ENABLE_TEXTURE) { \
+ u = start->texcoord[0]; \
+ v = start->texcoord[1]; \
+ } \
+ \
+ /* 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); \
+ \
+ /* Scanline rasterization */ \
+ for (int x = xStart; x < xEnd; x++) \
+ { \
+ /* Test and write depth */ \
+ if (ENABLE_DEPTH_TEST) \
+ { \
+ /* TODO: Implement different depth funcs? */ \
+ float depth = sw_framebuffer_read_depth(dptr); \
+ if (z > depth) goto discard; \
+ } \
+ \
+ 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]; \
+ float s = u*wRcp; \
+ float t = v*wRcp; \
+ sw_texture_sample(texColor, tex, s, t, duDx, duDy, dvDx, dvDy); \
+ srcColor[0] *= texColor[0]; \
+ srcColor[1] *= texColor[1]; \
+ srcColor[2] *= texColor[2]; \
+ srcColor[3] *= texColor[3]; \
+ } \
+ \
+ if (ENABLE_COLOR_BLEND) \
+ { \
+ 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 \
+ { \
+ sw_framebuffer_write_color(cptr, srcColor); \
+ } \
+ \
+ /* Increment the interpolation parameter, UVs, and pointers */ \
+ discard: \
+ z += dzDx; \
+ w += dwDx; \
+ color[0] += dcDx[0]; \
+ color[1] += dcDx[1]; \
+ color[2] += dcDx[2]; \
+ color[3] += dcDx[3]; \
+ if (ENABLE_TEXTURE) \
+ { \
+ u += duDx; \
+ v += dvDx; \
+ } \
+ \
+ sw_framebuffer_inc_color_addr(&cptr); \
+ sw_framebuffer_inc_depth_addr(&dptr); \
+ } \
+}
+
+#define DEFINE_TRIANGLE_RASTER(FUNC_NAME, FUNC_SCANLINE, ENABLE_TEXTURE) \
+static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1, \
+ const sw_vertex_t *v2, const sw_texture_t *tex) \
+{ \
+ /* Swap vertices by increasing y */ \
+ if (v0->screen[1] > v1->screen[1]) { const sw_vertex_t *tmp = v0; v0 = v1; v1 = tmp; } \
+ if (v1->screen[1] > v2->screen[1]) { const sw_vertex_t *tmp = v1; v1 = v2; v2 = tmp; } \
+ if (v0->screen[1] > v1->screen[1]) { const sw_vertex_t *tmp = v0; v0 = v1; v1 = tmp; } \
+ \
+ /* Extracting coordinates from the sorted vertices */ \
+ float x0 = v0->screen[0], y0 = v0->screen[1]; \
+ float x1 = v1->screen[0], y1 = v1->screen[1]; \
+ float x2 = v2->screen[0], y2 = v2->screen[1]; \
+ \
+ /* Compute height differences */ \
+ float h02 = y2 - y0; \
+ float h01 = y1 - y0; \
+ float h12 = y2 - y1; \
+ \
+ if (h02 < 1e-6f) return; \
+ \
+ /* Precompute the inverse values without additional checks */ \
+ float invH02 = 1.0f/h02; \
+ float invH01 = (h01 > 1e-6f)? 1.0f/h01 : 0.0f; \
+ float invH12 = (h12 > 1e-6f)? 1.0f/h12 : 0.0f; \
+ \
+ /* Pre-calculation of slopes */ \
+ float dx02 = (x2 - x0)*invH02; \
+ float dx01 = (x1 - x0)*invH01; \
+ float dx12 = (x2 - x1)*invH12; \
+ \
+ /* Y bounds (vertical clipping) */ \
+ int yTop = (int)(y0 + 0.5f); \
+ int yMiddle = (int)(y1 + 0.5f); \
+ int yBottom = (int)(y2 + 0.5f); \
+ \
+ /* Compute gradients for each side of the triangle */ \
+ sw_vertex_t vDy02, vDy01, vDy12; \
+ sw_get_vertex_grad_PTCH(&vDy02, v0, v2, invH02); \
+ sw_get_vertex_grad_PTCH(&vDy01, v0, v1, invH01); \
+ sw_get_vertex_grad_PTCH(&vDy12, v1, v2, invH12); \
+ \
+ /* Initializing scanline variables */ \
+ sw_vertex_t vLeft = *v0; \
+ vLeft.screen[0] = x0; \
+ sw_vertex_t vRight = *v0; \
+ vRight.screen[0] = x0; \
+ \
+ /* Scanline for the upper part of the triangle */ \
+ for (int y = yTop; y < yMiddle; y++) \
+ { \
+ vLeft.screen[1] = vRight.screen[1] = y; \
+ \
+ if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, vDy02.texcoord[0], vDy02.texcoord[1]); \
+ else FUNC_SCANLINE(tex, &vRight, &vLeft, vDy02.texcoord[0], vDy02.texcoord[1]); \
+ \
+ sw_add_vertex_grad_PTCH(&vLeft, &vDy02); \
+ vLeft.screen[0] += dx02; \
+ sw_add_vertex_grad_PTCH(&vRight, &vDy01); \
+ vRight.screen[0] += dx01; \
+ } \
+ \
+ /* Scanline for the lower part of the triangle */ \
+ vRight = *v1, vRight.screen[0] = x1; \
+ \
+ for (int y = yMiddle; y < yBottom; y++) \
+ { \
+ vLeft.screen[1] = vRight.screen[1] = y; \
+ \
+ if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, vDy02.texcoord[0], vDy02.texcoord[1]); \
+ else FUNC_SCANLINE(tex, &vRight, &vLeft, vDy02.texcoord[0], vDy02.texcoord[1]); \
+ \
+ sw_add_vertex_grad_PTCH(&vLeft, &vDy02); \
+ vLeft.screen[0] += dx02; \
+ sw_add_vertex_grad_PTCH(&vRight, &vDy12); \
+ vRight.screen[0] += dx12; \
+ } \
+}
+
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline, 0, 0, 0)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX, 1, 0, 0)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_DEPTH, 0, 1, 0)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_BLEND, 0, 0, 1)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX_DEPTH, 1, 1, 0)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX_BLEND, 1, 0, 1)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_DEPTH_BLEND, 0, 1, 1)
+DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX_DEPTH_BLEND, 1, 1, 1)
+
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster, sw_triangle_raster_scanline, false)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX, sw_triangle_raster_scanline_TEX, true)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_DEPTH, sw_triangle_raster_scanline_DEPTH, false)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_BLEND, sw_triangle_raster_scanline_BLEND, false)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH, sw_triangle_raster_scanline_TEX_DEPTH, true)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND, sw_triangle_raster_scanline_TEX_BLEND, true)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND, sw_triangle_raster_scanline_DEPTH_BLEND, false)
+DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND, sw_triangle_raster_scanline_TEX_DEPTH_BLEND, true)
+
+static inline void sw_triangle_render(void)
+{
+ if (RLSW.stateFlags & SW_STATE_CULL_FACE)
+ {
+ if (!sw_triangle_face_culling()) return;
+ }
+
+ sw_triangle_clip_and_project();
+
+ if (RLSW.vertexCounter < 3) return;
+
+ #define TRIANGLE_RASTER(RASTER_FUNC) \
+ { \
+ for (int i = 0; i < RLSW.vertexCounter - 2; i++) \
+ { \
+ RASTER_FUNC( \
+ &RLSW.vertexBuffer[0], \
+ &RLSW.vertexBuffer[i + 1], \
+ &RLSW.vertexBuffer[i + 2], \
+ &RLSW.loadedTextures[RLSW.currentTexture] \
+ ); \
+ } \
+ }
+
+ 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)
+ else TRIANGLE_RASTER(sw_triangle_raster)
+
+ #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.
+ // To handle quads crossing the w=0 plane correctly,
+ // we perform the winding order test in homogeneous coordinates directly,
+ // before the perspective division (division by w).
+ // For a convex quad with vertices P0, P1, P2, P3 in sequential order,
+ // the winding order of the quad is the same as the winding order
+ // of the triangle P0 P1 P2. We use the homogeneous triangle
+ // winding test on this first triangle.
+
+ // Preload homogeneous coordinates into local variables
+ const float *h0 = RLSW.vertexBuffer[0].homogeneous;
+ const float *h1 = RLSW.vertexBuffer[1].homogeneous;
+ const float *h2 = RLSW.vertexBuffer[2].homogeneous;
+
+ // NOTE: h3 is not needed for this test
+ // const float *h3 = RLSW.vertexBuffer[3].homogeneous;
+
+ // Compute a value proportional to the signed area of the triangle P0 P1 P2
+ // in the projected 2D plane, calculated directly using homogeneous coordinates
+ // BEFORE division by w.
+ // This is the determinant of the matrix formed by the (x, y, w) components
+ // of the vertices P0, P1, and P2. Its sign correctly indicates the winding order
+ // in homogeneous space and its relationship to the projected 2D winding order,
+ // even with negative w values.
+ // The determinant formula used here is:
+ // h0.x*(h1.y*h2.w - h2.y*h1.w) +
+ // h1.x*(h2.y*h0.w - h0.y*h2.w) +
+ // h2.x*(h0.y*h1.w - h1.y*h0.w)
+
+ const float hSgnArea =
+ h0[0]*(h1[1]*h2[3] - h2[1]*h1[3]) +
+ h1[0]*(h2[1]*h0[3] - h0[1]*h2[3]) +
+ h2[0]*(h0[1]*h1[3] - h1[1]*h0[3]);
+
+ // Perform face culling based on the winding order determined by the sign
+ // of the homogeneous area/determinant of triangle P0 P1 P2.
+ // This test is robust for points with w > 0 or w < 0 within the triangle,
+ // correctly capturing the change in orientation when crossing the w=0 plane.
+
+ // A positive hSgnArea typically corresponds to a counter-clockwise
+ // winding in the projected space when all w > 0.
+ // A value of 0 for hSgnArea means P0, P1, P2 are collinear in (x, y, w)
+ // space, which corresponds to a degenerate triangle projection.
+ // Such quads might also be degenerate or non-planar. They are typically
+ // not culled by this test (0 < 0 is false, 0 > 0 is false)
+ // and should be handled by the clipper if necessary.
+
+ return (RLSW.cullFace == SW_FRONT)
+ ? (hSgnArea < 0.0f) // Cull if winding is "clockwise" in the projected sense
+ : (hSgnArea > 0.0f); // Cull if winding is "counter-clockwise" in the projected sense
+}
+
+static inline void sw_quad_clip_and_project(void)
+{
+ sw_vertex_t *polygon = RLSW.vertexBuffer;
+ int *vertexCounter = &RLSW.vertexCounter;
+
+ if (sw_polygon_clip(polygon, vertexCounter))
+ {
+ // Transformation to screen space and normalization
+ for (int i = 0; i < *vertexCounter; i++)
+ {
+ sw_vertex_t *v = &polygon[i];
+
+ // 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;
+
+ // Division of XYZ coordinates by weight
+ v->homogeneous[0] *= invW;
+ v->homogeneous[1] *= invW;
+ v->homogeneous[2] *= invW;
+
+ // Division of texture coordinates (perspective-correct)
+ v->texcoord[0] *= invW;
+ v->texcoord[1] *= invW;
+
+ // Division of colors (perspective-correct)
+ v->color[0] *= invW;
+ v->color[1] *= invW;
+ v->color[2] *= invW;
+ v->color[3] *= invW;
+
+ // Transformation to screen space
+ sw_project_ndc_to_screen(v->screen, v->homogeneous);
+ }
+ }
+}
+
+static inline bool sw_quad_is_axis_aligned(void)
+{
+ int horizontal = 0;
+ int vertical = 0;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (RLSW.vertexBuffer[i].homogeneous[3] != 1.0f) return false;
+
+ const float *v0 = RLSW.vertexBuffer[i].position;
+ const float *v1 = RLSW.vertexBuffer[(i + 1)%4].position;
+
+ 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++;
+ else return false; // Diagonal edge -> not axis-aligned
+ }
+
+ return ((horizontal == 2) && (vertical == 2));
+}
+
+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;
+
+ // Find minimum and maximum Y
+ float minY = input[0].screen[1];
+ float maxY = input[0].screen[1];
+
+ 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];
+ }
+
+ // 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
+}
+
+// TODO: REVIEW: Could a perfectly aligned quad, where one of the four points has a different depth,
+// still appear perfectly aligned from a certain point of view?
+// Because in that case, we would still need to perform perspective division for textures and colors...
+#define DEFINE_QUAD_RASTER_AXIS_ALIGNED(FUNC_NAME, ENABLE_TEXTURE, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \
+static inline void FUNC_NAME(void) \
+{ \
+ const sw_vertex_t *sortedVerts[4]; \
+ sw_quad_sort_cw(sortedVerts); \
+ \
+ const sw_vertex_t *v0 = sortedVerts[0]; \
+ const sw_vertex_t *v1 = sortedVerts[1]; \
+ const sw_vertex_t *v2 = sortedVerts[2]; \
+ const sw_vertex_t *v3 = sortedVerts[3]; \
+ \
+ /* Screen bounds (axis-aligned) */ \
+ int xMin = (int)(v0->screen[0] + 0.5f); \
+ int yMin = (int)(v0->screen[1] + 0.5f); \
+ int xMax = (int)(v2->screen[0] + 0.5f); \
+ int yMax = (int)(v2->screen[1] + 0.5f); \
+ \
+ int width = xMax - xMin; \
+ int height = yMax - yMin; \
+ \
+ 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; \
+ \
+ /* Calculation of vertex gradients in X and Y */ \
+ float tcDx[2], tcDy[2]; \
+ if (ENABLE_TEXTURE) { \
+ tcDx[0] = (v1->texcoord[0] - v0->texcoord[0])*wRcp; \
+ tcDx[1] = (v1->texcoord[1] - v0->texcoord[1])*wRcp; \
+ tcDy[0] = (v3->texcoord[0] - v0->texcoord[0])*hRcp; \
+ tcDy[1] = (v3->texcoord[1] - v0->texcoord[1])*hRcp; \
+ } \
+ \
+ float cDx[4], cDy[4]; \
+ cDx[0] = (v1->color[0] - v0->color[0])*wRcp; \
+ cDx[1] = (v1->color[1] - v0->color[1])*wRcp; \
+ cDx[2] = (v1->color[2] - v0->color[2])*wRcp; \
+ cDx[3] = (v1->color[3] - v0->color[3])*wRcp; \
+ cDy[0] = (v3->color[0] - v0->color[0])*hRcp; \
+ cDy[1] = (v3->color[1] - v0->color[1])*hRcp; \
+ cDy[2] = (v3->color[2] - v0->color[2])*hRcp; \
+ cDy[3] = (v3->color[3] - v0->color[3])*hRcp; \
+ \
+ float zDx, zDy; \
+ zDx = (v1->homogeneous[2] - v0->homogeneous[2])*wRcp; \
+ zDy = (v3->homogeneous[2] - v0->homogeneous[2])*hRcp; \
+ \
+ /* Start of quad rasterization */ \
+ const sw_texture_t *tex; \
+ if (ENABLE_TEXTURE) tex = &RLSW.loadedTextures[RLSW.currentTexture]; \
+ \
+ void *cDstBase = RLSW.framebuffer.color; \
+ void *dDstBase = RLSW.framebuffer.depth; \
+ int wDst = RLSW.framebuffer.width; \
+ \
+ float zScanline = v0->homogeneous[2]; \
+ float uScanline = v0->texcoord[0]; \
+ float vScanline = v0->texcoord[1]; \
+ \
+ float colorScanline[4] = { \
+ v0->color[0], \
+ v0->color[1], \
+ v0->color[2], \
+ v0->color[3] \
+ }; \
+ \
+ 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); \
+ \
+ float z = zScanline; \
+ float u = uScanline; \
+ float v = vScanline; \
+ \
+ float color[4] = { \
+ colorScanline[0], \
+ colorScanline[1], \
+ colorScanline[2], \
+ colorScanline[3] \
+ }; \
+ \
+ /* Scanline rasterization */ \
+ for (int x = xMin; x < xMax; x++) \
+ { \
+ /* Test and write depth */ \
+ if (ENABLE_DEPTH_TEST) \
+ { \
+ /* TODO: Implement different depth funcs? */ \
+ float depth = sw_framebuffer_read_depth(dptr); \
+ if (z > depth) goto discard; \
+ } \
+ \
+ 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]; \
+ sw_texture_sample(texColor, tex, u, v, tcDx[0], tcDy[0], tcDx[1], tcDy[1]); \
+ srcColor[0] *= texColor[0]; \
+ srcColor[1] *= texColor[1]; \
+ srcColor[2] *= texColor[2]; \
+ srcColor[3] *= texColor[3]; \
+ } \
+ \
+ if (ENABLE_COLOR_BLEND) \
+ { \
+ 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); \
+ \
+ discard: \
+ z += zDx; \
+ color[0] += cDx[0]; \
+ color[1] += cDx[1]; \
+ color[2] += cDx[2]; \
+ color[3] += cDx[3]; \
+ \
+ if (ENABLE_TEXTURE) \
+ { \
+ u += tcDx[0]; \
+ v += tcDx[1]; \
+ } \
+ \
+ sw_framebuffer_inc_color_addr(&cptr); \
+ sw_framebuffer_inc_depth_addr(&dptr); \
+ } \
+ \
+ zScanline += zDy; \
+ colorScanline[0] += cDy[0]; \
+ colorScanline[1] += cDy[1]; \
+ colorScanline[2] += cDy[2]; \
+ colorScanline[3] += cDy[3]; \
+ \
+ if (ENABLE_TEXTURE) \
+ { \
+ uScanline += tcDy[0]; \
+ vScanline += tcDy[1]; \
+ } \
+ } \
+}
+
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned, 0, 0, 0)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX, 1, 0, 0)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_DEPTH, 0, 1, 0)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_BLEND, 0, 0, 1)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX_DEPTH, 1, 1, 0)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX_BLEND, 1, 0, 1)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_DEPTH_BLEND, 0, 1, 1)
+DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND, 1, 1, 1)
+
+static inline void sw_quad_render(void)
+{
+ if (RLSW.stateFlags & SW_STATE_CULL_FACE)
+ {
+ if (!sw_quad_face_culling()) return;
+ }
+
+ sw_quad_clip_and_project();
+
+ if (RLSW.vertexCounter < 3) return;
+
+ 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();
+ else sw_quad_raster_axis_aligned();
+ return;
+ }
+
+ #define TRIANGLE_RASTER(RASTER_FUNC) \
+ { \
+ for (int i = 0; i < RLSW.vertexCounter - 2; i++) \
+ { \
+ RASTER_FUNC( \
+ &RLSW.vertexBuffer[0], \
+ &RLSW.vertexBuffer[i + 1], \
+ &RLSW.vertexBuffer[i + 2], \
+ &RLSW.loadedTextures[RLSW.currentTexture] \
+ ); \
+ } \
+ }
+
+ 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)
+ else TRIANGLE_RASTER(sw_triangle_raster)
+
+ #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)
+ {
+ // Check if the line is entirely outside the window
+ if (q < -SW_CLIP_EPSILON) return 0; // Completely outside
+ return 1; // Completely inside or on the edges
+ }
+
+ const float r = q/p;
+
+ if (p < 0)
+ {
+ if (r > *t1) return 0;
+ if (r > *t0) *t0 = r;
+ }
+ else
+ {
+ if (r < *t0) return 0;
+ if (r < *t1) *t1 = r;
+ }
+
+ return 1;
+}
+
+static inline bool sw_line_clip(sw_vertex_t *v0, sw_vertex_t *v1)
+{
+ float t0 = 0.0f, t1 = 1.0f;
+ float dH[4], dC[4];
+
+ for (int i = 0; i < 4; i++)
+ {
+ dH[i] = v1->homogeneous[i] - v0->homogeneous[i];
+ dC[i] = v1->color[i] - v0->color[i];
+ }
+
+ // Clipping Liang-Barsky
+ if (!sw_line_clip_coord(v0->homogeneous[3] - v0->homogeneous[0], -dH[3] + dH[0], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(v0->homogeneous[3] + v0->homogeneous[0], -dH[3] - dH[0], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(v0->homogeneous[3] - v0->homogeneous[1], -dH[3] + dH[1], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(v0->homogeneous[3] + v0->homogeneous[1], -dH[3] - dH[1], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(v0->homogeneous[3] - v0->homogeneous[2], -dH[3] + dH[2], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(v0->homogeneous[3] + v0->homogeneous[2], -dH[3] - dH[2], &t0, &t1)) return false;
+
+ // Clipping Scissor
+ if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST)
+ {
+ if (!sw_line_clip_coord(v0->homogeneous[0] - RLSW.scClipMin[0]*v0->homogeneous[3], RLSW.scClipMin[0]*dH[3] - dH[0], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(RLSW.scClipMax[0]*v0->homogeneous[3] - v0->homogeneous[0], dH[0] - RLSW.scClipMax[0]*dH[3], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(v0->homogeneous[1] - RLSW.scClipMin[1]*v0->homogeneous[3], RLSW.scClipMin[1]*dH[3] - dH[1], &t0, &t1)) return false;
+ if (!sw_line_clip_coord(RLSW.scClipMax[1]*v0->homogeneous[3] - v0->homogeneous[1], dH[1] - RLSW.scClipMax[1]*dH[3], &t0, &t1)) return false;
+ }
+
+ // Interpolation of new coordinates
+ if (t1 < 1.0f)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ v1->homogeneous[i] = v0->homogeneous[i] + t1*dH[i];
+ v1->color[i] = v0->color[i] + t1*dC[i];
+ }
+ }
+
+ if (t0 > 0.0f)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ v0->homogeneous[i] += t0*dH[i];
+ v0->color[i] += t0*dC[i];
+ }
+ }
+
+ return true;
+}
+
+static inline bool sw_line_clip_and_project(sw_vertex_t *v0, sw_vertex_t *v1)
+{
+ if (!sw_line_clip(v0, v1)) return false;
+
+ // Convert homogeneous coordinates to NDC
+ v0->homogeneous[3] = 1.0f/v0->homogeneous[3];
+ v1->homogeneous[3] = 1.0f/v1->homogeneous[3];
+ for (int i = 0; i < 3; i++)
+ {
+ v0->homogeneous[i] *= v0->homogeneous[3];
+ v1->homogeneous[i] *= v1->homogeneous[3];
+ }
+
+ // Convert NDC coordinates to screen space
+ sw_project_ndc_to_screen(v0->screen, v0->homogeneous);
+ sw_project_ndc_to_screen(v1->screen, v1->homogeneous);
+
+ return true;
+}
+
+#define DEFINE_LINE_RASTER(FUNC_NAME, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \
+static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \
+{ \
+ int x1 = (int)(v0->screen[0] + 0.5f); \
+ int y1 = (int)(v0->screen[1] + 0.5f); \
+ int x2 = (int)(v1->screen[0] + 0.5f); \
+ int y2 = (int)(v1->screen[1] + 0.5f); \
+ \
+ int dx = x2 - x1; \
+ int dy = y2 - y1; \
+ \
+ /* Handling of lines that are more horizontal or vertical */ \
+ if (dx == 0 && dy == 0) \
+ { \
+ /* TODO: A point should be rendered here */ \
+ return; \
+ } \
+ \
+ bool yLonger = (abs(dy) > abs(dx)); \
+ int longLen, shortLen; \
+ \
+ if (yLonger) \
+ { \
+ longLen = dy; \
+ shortLen = dx; \
+ } \
+ else \
+ { \
+ longLen = dx; \
+ shortLen = dy; \
+ } \
+ \
+ /* Handling of traversal direction */ \
+ int sgnInc = (longLen < 0)? -1 : 1; \
+ int abslongLen = abs(longLen); \
+ \
+ /* Calculation of the increment step for the shorter coordinate */ \
+ int decInc = 0; \
+ if (abslongLen != 0) decInc = (shortLen << 16)/abslongLen; \
+ \
+ float longLenRcp = (abslongLen != 0)? (1.0f/abslongLen) : 0.0f; \
+ \
+ /* Calculation of interpolation steps */ \
+ const float zStep = (v1->homogeneous[2] - v0->homogeneous[2])*longLenRcp; \
+ const float rStep = (v1->color[0] - v0->color[0])*longLenRcp; \
+ const float gStep = (v1->color[1] - v0->color[1])*longLenRcp; \
+ const float bStep = (v1->color[2] - v0->color[2])*longLenRcp; \
+ const float aStep = (v1->color[3] - v0->color[3])*longLenRcp; \
+ \
+ float z = v0->homogeneous[2]; \
+ \
+ float color[4] = { \
+ v0->color[0], \
+ v0->color[1], \
+ v0->color[2], \
+ v0->color[3] \
+ }; \
+ \
+ const int fbWidth = RLSW.framebuffer.width; \
+ void *cBuffer = RLSW.framebuffer.color; \
+ void *dBuffer = RLSW.framebuffer.depth; \
+ \
+ int j = 0; \
+ if (yLonger) \
+ { \
+ 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); \
+ \
+ if (ENABLE_DEPTH_TEST) \
+ { \
+ float depth = sw_framebuffer_read_depth(dptr); \
+ if (z > depth) goto discardA; \
+ } \
+ \
+ sw_framebuffer_write_depth(dptr, z); \
+ \
+ void *cptr = sw_framebuffer_get_color_addr(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); \
+ \
+ discardA: \
+ j += decInc; \
+ z += zStep; \
+ color[0] += rStep; \
+ color[1] += gStep; \
+ color[2] += bStep; \
+ color[3] += aStep; \
+ } \
+ } \
+ else \
+ { \
+ 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); \
+ \
+ if (ENABLE_DEPTH_TEST) \
+ { \
+ float depth = sw_framebuffer_read_depth(dptr); \
+ if (z > depth) goto discardB; \
+ } \
+ \
+ sw_framebuffer_write_depth(dptr, z); \
+ \
+ void *cptr = sw_framebuffer_get_color_addr(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); \
+ \
+ discardB: \
+ j += decInc; \
+ z += zStep; \
+ color[0] += rStep; \
+ color[1] += gStep; \
+ color[2] += bStep; \
+ color[3] += aStep; \
+ } \
+ } \
+}
+
+#define DEFINE_LINE_THICK_RASTER(FUNC_NAME, RASTER_FUNC) \
+void FUNC_NAME(const sw_vertex_t *v1, const sw_vertex_t *v2) \
+{ \
+ sw_vertex_t tv1, tv2; \
+ \
+ int x1 = (int)v1->screen[0]; \
+ int y1 = (int)v1->screen[1]; \
+ int x2 = (int)v2->screen[0]; \
+ int y2 = (int)v2->screen[1]; \
+ \
+ int dx = x2 - x1; \
+ int dy = y2 - y1; \
+ \
+ RASTER_FUNC(v1, v2); \
+ \
+ if (dx != 0 && abs(dy/dx) < 1) \
+ { \
+ int wy = (int)((RLSW.lineWidth - 1.0f)*abs(dx)/sqrtf(dx*dx + dy*dy)); \
+ wy >>= 1; \
+ for (int i = 1; i <= wy; i++) \
+ { \
+ tv1 = *v1, tv2 = *v2; \
+ tv1.screen[1] -= i; \
+ tv2.screen[1] -= i; \
+ RASTER_FUNC(&tv1, &tv2); \
+ tv1 = *v1, tv2 = *v2; \
+ tv1.screen[1] += i; \
+ tv2.screen[1] += i; \
+ RASTER_FUNC(&tv1, &tv2); \
+ } \
+ } \
+ else if (dy != 0) \
+ { \
+ int wx = (int)((RLSW.lineWidth - 1.0f)*abs(dy)/sqrtf(dx*dx + dy*dy)); \
+ wx >>= 1; \
+ for (int i = 1; i <= wx; i++) \
+ { \
+ tv1 = *v1, tv2 = *v2; \
+ tv1.screen[0] -= i; \
+ tv2.screen[0] -= i; \
+ RASTER_FUNC(&tv1, &tv2); \
+ tv1 = *v1, tv2 = *v2; \
+ tv1.screen[0] += i; \
+ tv2.screen[0] += i; \
+ RASTER_FUNC(&tv1, &tv2); \
+ } \
+ } \
+}
+
+DEFINE_LINE_RASTER(sw_line_raster, 0, 0)
+DEFINE_LINE_RASTER(sw_line_raster_DEPTH, 1, 0)
+DEFINE_LINE_RASTER(sw_line_raster_BLEND, 0, 1)
+DEFINE_LINE_RASTER(sw_line_raster_DEPTH_BLEND, 1, 1)
+
+DEFINE_LINE_THICK_RASTER(sw_line_thick_raster, sw_line_raster)
+DEFINE_LINE_THICK_RASTER(sw_line_thick_raster_DEPTH, sw_line_raster_DEPTH)
+DEFINE_LINE_THICK_RASTER(sw_line_thick_raster_BLEND, sw_line_raster_BLEND)
+DEFINE_LINE_THICK_RASTER(sw_line_thick_raster_DEPTH_BLEND, sw_line_raster_DEPTH_BLEND)
+
+static inline void sw_line_render(sw_vertex_t *vertices)
+{
+ if (!sw_line_clip_and_project(&vertices[0], &vertices[1])) return;
+
+ if (RLSW.lineWidth >= 2.0f)
+ {
+ if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_line_thick_raster_DEPTH_BLEND(&vertices[0], &vertices[1]);
+ else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_line_thick_raster_BLEND(&vertices[0], &vertices[1]);
+ else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_line_thick_raster_DEPTH(&vertices[0], &vertices[1]);
+ else sw_line_thick_raster(&vertices[0], &vertices[1]);
+ }
+ else
+ {
+ if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_line_raster_DEPTH_BLEND(&vertices[0], &vertices[1]);
+ else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_line_raster_BLEND(&vertices[0], &vertices[1]);
+ else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_line_raster_DEPTH(&vertices[0], &vertices[1]);
+ 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)
+ {
+ for (int_fast8_t i = 0; i < 3; i++)
+ {
+ if ((v->homogeneous[i] < -v->homogeneous[3]) || (v->homogeneous[i] > v->homogeneous[3])) return false;
+ }
+
+ v->homogeneous[3] = 1.0f/v->homogeneous[3];
+ v->homogeneous[0] *= v->homogeneous[3];
+ v->homogeneous[1] *= v->homogeneous[3];
+ v->homogeneous[2] *= v->homogeneous[3];
+ }
+
+ sw_project_ndc_to_screen(v->screen, v->homogeneous);
+
+ const int *min = NULL, *max = NULL;
+
+ if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST)
+ {
+ min = RLSW.scMin;
+ max = RLSW.scMax;
+ }
+ else
+ {
+ min = RLSW.vpMin;
+ max = RLSW.vpMax;
+ }
+
+ bool insideX = (v->screen[0] - RLSW.pointRadius < max[0]) && (v->screen[0] + RLSW.pointRadius > min[0]);
+ bool insideY = (v->screen[1] - RLSW.pointRadius < max[1]) && (v->screen[1] + RLSW.pointRadius > min[1]);
+
+ return (insideX && insideY);
+}
+
+#define DEFINE_POINT_RASTER(FUNC_NAME, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND, CHECK_BOUNDS) \
+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; \
+ } \
+ 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; \
+ } \
+ \
+ int offset = y*RLSW.framebuffer.width + x; \
+ \
+ void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, offset); \
+ \
+ if (ENABLE_DEPTH_TEST) \
+ { \
+ float depth = sw_framebuffer_read_depth(dptr); \
+ if (z > depth) return; \
+ } \
+ \
+ sw_framebuffer_write_depth(dptr, z); \
+ \
+ void *cptr = sw_framebuffer_get_color_addr(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); \
+ } \
+ else sw_framebuffer_write_color(cptr, color); \
+}
+
+#define DEFINE_POINT_THICK_RASTER(FUNC_NAME, RASTER_FUNC) \
+static inline void FUNC_NAME(sw_vertex_t *v) \
+{ \
+ int cx = v->screen[0]; \
+ int cy = v->screen[1]; \
+ float cz = v->homogeneous[2]; \
+ int radius = RLSW.pointRadius; \
+ const float *color = v->color; \
+ \
+ int x = 0; \
+ int y = radius; \
+ int d = 3 - 2*radius; \
+ \
+ while (x <= y) \
+ { \
+ for (int i = -x; i <= x; i++) \
+ { \
+ RASTER_FUNC(cx + i, cy + y, cz, color); \
+ RASTER_FUNC(cx + i, cy - y, cz, color); \
+ } \
+ for (int i = -y; i <= y; i++) \
+ { \
+ RASTER_FUNC(cx + i, cy + x, cz, color); \
+ RASTER_FUNC(cx + i, cy - x, cz, color); \
+ } \
+ if (d > 0) \
+ { \
+ y--; \
+ d = d + 4*(x - y) + 10; \
+ } \
+ else d = d + 4*x + 6; \
+ x++; \
+ } \
+}
+
+DEFINE_POINT_RASTER(sw_point_raster, 0, 0, 0)
+DEFINE_POINT_RASTER(sw_point_raster_DEPTH, 1, 0, 0)
+DEFINE_POINT_RASTER(sw_point_raster_BLEND, 0, 1, 0)
+DEFINE_POINT_RASTER(sw_point_raster_DEPTH_BLEND, 1, 1, 0)
+
+DEFINE_POINT_RASTER(sw_point_raster_CHECK, 0, 0, 1)
+DEFINE_POINT_RASTER(sw_point_raster_DEPTH_CHECK, 1, 0, 1)
+DEFINE_POINT_RASTER(sw_point_raster_BLEND_CHECK, 0, 1, 1)
+DEFINE_POINT_RASTER(sw_point_raster_DEPTH_BLEND_CHECK, 1, 1, 1)
+
+DEFINE_POINT_RASTER(sw_point_raster_CHECK_SCISSOR, 0, 0, SW_SCISSOR_TEST)
+DEFINE_POINT_RASTER(sw_point_raster_DEPTH_CHECK_SCISSOR, 1, 0, SW_SCISSOR_TEST)
+DEFINE_POINT_RASTER(sw_point_raster_BLEND_CHECK_SCISSOR, 0, 1, SW_SCISSOR_TEST)
+DEFINE_POINT_RASTER(sw_point_raster_DEPTH_BLEND_CHECK_SCISSOR, 1, 1, SW_SCISSOR_TEST)
+
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster, sw_point_raster_CHECK)
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH, sw_point_raster_DEPTH_CHECK)
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_BLEND, sw_point_raster_BLEND_CHECK)
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH_BLEND, sw_point_raster_DEPTH_BLEND_CHECK)
+
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_SCISSOR, sw_point_raster_CHECK_SCISSOR)
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH_SCISSOR, sw_point_raster_DEPTH_CHECK_SCISSOR)
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_BLEND_SCISSOR, sw_point_raster_BLEND_CHECK_SCISSOR)
+DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH_BLEND_SCISSOR, sw_point_raster_DEPTH_BLEND_CHECK_SCISSOR)
+
+static inline void sw_point_render(sw_vertex_t *v)
+{
+ if (!sw_point_clip_and_project(v)) return;
+
+ if (RLSW.pointRadius >= 1.0f)
+ {
+ if (SW_STATE_CHECK(SW_STATE_SCISSOR_TEST))
+ {
+ if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_point_thick_raster_DEPTH_BLEND_SCISSOR(v);
+ else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_point_thick_raster_BLEND_SCISSOR(v);
+ else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_point_thick_raster_DEPTH_SCISSOR(v);
+ else sw_point_thick_raster_SCISSOR(v);
+ }
+ else
+ {
+ if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_point_thick_raster_DEPTH_BLEND(v);
+ else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_point_thick_raster_BLEND(v);
+ else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_point_thick_raster_DEPTH(v);
+ else sw_point_thick_raster(v);
+ }
+ }
+ else
+ {
+ if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_point_raster_DEPTH_BLEND(v->screen[0], v->screen[1], v->homogeneous[2], v->color);
+ else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_point_raster_BLEND(v->screen[0], v->screen[1], v->homogeneous[2], v->color);
+ else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_point_raster_DEPTH(v->screen[0], v->screen[1], v->homogeneous[2], v->color);
+ 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]);
+}
+
+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] });
+
+ sw_line_render((sw_vertex_t[2]){ vertices[cm1], vertices[0] });
+}
+
+static inline void sw_poly_fill_render(void)
+{
+ switch (RLSW.drawMode)
+ {
+ case SW_POINTS: sw_point_render(&RLSW.vertexBuffer[0]); break;
+ case SW_LINES: sw_line_render(RLSW.vertexBuffer); break;
+ case SW_TRIANGLES: sw_triangle_render(); break;
+ case SW_QUADS: sw_quad_render(); break;
+ }
+}
+
+// Validity check helper functions
+
+static inline bool sw_is_texture_valid(uint32_t id)
+{
+ bool valid = true;
+
+ if (id == 0) valid = false;
+ else if (id >= SW_MAX_TEXTURES) valid = false;
+ else if (RLSW.loadedTextures[id].pixels.cptr == 0) valid = false;
+
+ return true;
+}
+
+static inline bool sw_is_texture_filter_valid(int filter)
+{
+ return (filter == SW_NEAREST || filter == SW_LINEAR);
+}
+
+static inline bool sw_is_texture_wrap_valid(int wrap)
+{
+ return (wrap == SW_REPEAT || wrap == SW_CLAMP);
+}
+
+static inline bool sw_is_draw_mode_valid(int mode)
+{
+ bool result = false;
+
+ switch (mode)
+ {
+ case SW_POINTS:
+ case SW_LINES:
+ case SW_TRIANGLES:
+ case SW_QUADS: result = true; break;
+ default: break;
+ }
+
+ return result;
+}
+
+static inline bool sw_is_poly_mode_valid(int mode)
+{
+ bool result = false;
+
+ switch (mode)
+ {
+ case SW_POINT:
+ case SW_LINE:
+ case SW_FILL: result = true; break;
+ default: break;
+ }
+
+ return result;
+}
+
+static inline bool sw_is_face_valid(int face)
+{
+ return (face == SW_FRONT || face == SW_BACK);
+}
+
+static inline bool sw_is_blend_src_factor_valid(int blend)
+{
+ bool result = false;
+
+ switch (blend)
+ {
+ case SW_ZERO:
+ case SW_ONE:
+ case SW_SRC_COLOR:
+ case SW_ONE_MINUS_SRC_COLOR:
+ case SW_SRC_ALPHA:
+ case SW_ONE_MINUS_SRC_ALPHA:
+ case SW_DST_ALPHA:
+ case SW_ONE_MINUS_DST_ALPHA:
+ case SW_DST_COLOR:
+ case SW_ONE_MINUS_DST_COLOR:
+ case SW_SRC_ALPHA_SATURATE: result = true; break;
+ default: break;
+ }
+
+ return result;
+}
+
+static inline bool sw_is_blend_dst_factor_valid(int blend)
+{
+ bool result = false;
+
+ switch (blend)
+ {
+ case SW_ZERO:
+ case SW_ONE:
+ case SW_SRC_COLOR:
+ case SW_ONE_MINUS_SRC_COLOR:
+ case SW_SRC_ALPHA:
+ case SW_ONE_MINUS_SRC_ALPHA:
+ case SW_DST_ALPHA:
+ case SW_ONE_MINUS_DST_ALPHA:
+ case SW_DST_COLOR:
+ case SW_ONE_MINUS_DST_COLOR: result = true; break;
+ default: break;
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------------------
+// Module Functions Definition
+//----------------------------------------------------------------------------------
+bool swInit(int w, int h)
+{
+ if (!sw_framebuffer_load(w, h)) { swClose(); return false; }
+
+ swViewport(0, 0, w, h);
+ swScissor(0, 0, w, h);
+
+ RLSW.loadedTextures = 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));
+ if (RLSW.loadedTextures == NULL) { swClose(); return false; }
+
+ RLSW.clearColor[0] = 0.0f;
+ RLSW.clearColor[1] = 0.0f;
+ RLSW.clearColor[2] = 0.0f;
+ RLSW.clearColor[3] = 1.0f;
+ RLSW.clearDepth = 1.0f;
+
+ RLSW.currentMatrixMode = SW_MODELVIEW;
+ RLSW.currentMatrix = &RLSW.stackModelview[0];
+
+ sw_matrix_id(RLSW.stackProjection[0]);
+ sw_matrix_id(RLSW.stackModelview[0]);
+ sw_matrix_id(RLSW.stackTexture[0]);
+ sw_matrix_id(RLSW.matMVP);
+
+ RLSW.stackProjectionCounter = 1;
+ RLSW.stackModelviewCounter = 1;
+ RLSW.stackTextureCounter = 1;
+ RLSW.isDirtyMVP = false;
+
+ RLSW.current.texcoord[0] = 0.0f;
+ RLSW.current.texcoord[1] = 0.0f;
+
+ RLSW.current.color[0] = 1.0f;
+ RLSW.current.color[1] = 1.0f;
+ RLSW.current.color[2] = 1.0f;
+ RLSW.current.color[3] = 1.0f;
+
+ RLSW.srcFactor = SW_SRC_ALPHA;
+ RLSW.dstFactor = SW_ONE_MINUS_SRC_ALPHA;
+
+ RLSW.srcFactorFunc = sw_factor_src_alpha;
+ RLSW.dstFactorFunc = sw_factor_one_minus_src_alpha;
+
+ 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,
+ };
+
+ RLSW.loadedTextures[0].pixels.cptr = defTex;
+ 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;
+
+ return true;
+}
+
+void swClose(void)
+{
+ 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);
+ }
+
+ SW_FREE(RLSW.framebuffer.color);
+ SW_FREE(RLSW.framebuffer.depth);
+ SW_FREE(RLSW.loadedTextures);
+ SW_FREE(RLSW.freeTextureIds);
+
+ RLSW = (sw_context_t) { 0 };
+}
+
+bool swResizeFramebuffer(int w, int h)
+{
+ return sw_framebuffer_resize(w, 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);
+
+ if (w <= 0)
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ if (h <= 0)
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ if (w > RLSW.framebuffer.width) w = RLSW.framebuffer.width;
+ if (h > RLSW.framebuffer.height) h = RLSW.framebuffer.height;
+
+ x = sw_clampi(x, 0, w);
+ y = sw_clampi(y, 0, h);
+
+ 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;
+ 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);
+
+ if (wSrc <= 0)
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ if (hSrc <= 0)
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ if (wSrc > RLSW.framebuffer.width) wSrc = RLSW.framebuffer.width;
+ if (hSrc > RLSW.framebuffer.height) hSrc = RLSW.framebuffer.height;
+
+ xSrc = sw_clampi(xSrc, 0, wSrc);
+ ySrc = sw_clampi(ySrc, 0, hSrc);
+
+ 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;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+}
+
+void *swGetColorBuffer(int *w, int *h)
+{
+ if (w) *w = RLSW.framebuffer.width;
+ if (h) *h = RLSW.framebuffer.height;
+
+ return RLSW.framebuffer.color;
+}
+
+void swEnable(SWstate state)
+{
+ switch (state)
+ {
+ case SW_SCISSOR_TEST: RLSW.stateFlags |= SW_STATE_SCISSOR_TEST; break;
+ case SW_TEXTURE_2D: RLSW.stateFlags |= SW_STATE_TEXTURE_2D; break;
+ case SW_DEPTH_TEST: RLSW.stateFlags |= SW_STATE_DEPTH_TEST; break;
+ case SW_CULL_FACE: RLSW.stateFlags |= SW_STATE_CULL_FACE; break;
+ case SW_BLEND: RLSW.stateFlags |= SW_STATE_BLEND; break;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+}
+
+void swDisable(SWstate state)
+{
+ switch (state)
+ {
+ case SW_SCISSOR_TEST: RLSW.stateFlags &= ~SW_STATE_SCISSOR_TEST; break;
+ case SW_TEXTURE_2D: RLSW.stateFlags &= ~SW_STATE_TEXTURE_2D; break;
+ case SW_DEPTH_TEST: RLSW.stateFlags &= ~SW_STATE_DEPTH_TEST; break;
+ case SW_CULL_FACE: RLSW.stateFlags &= ~SW_STATE_CULL_FACE; break;
+ case SW_BLEND: RLSW.stateFlags &= ~SW_STATE_BLEND; break;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+}
+
+void swGetIntegerv(SWget name, int *v)
+{
+ switch (name)
+ {
+ case SW_MODELVIEW_STACK_DEPTH: *v = SW_MODELVIEW_STACK_DEPTH; break;
+ case SW_PROJECTION_STACK_DEPTH: *v = SW_PROJECTION_STACK_DEPTH; break;
+ case SW_TEXTURE_STACK_DEPTH: *v = SW_TEXTURE_STACK_DEPTH; break;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+}
+
+void swGetFloatv(SWget name, float *v)
+{
+ switch (name)
+ {
+ case SW_COLOR_CLEAR_VALUE:
+ {
+ v[0] = RLSW.clearColor[0];
+ v[1] = RLSW.clearColor[1];
+ v[2] = RLSW.clearColor[2];
+ v[3] = RLSW.clearColor[3];
+ } break;
+ case SW_CURRENT_COLOR:
+ {
+ v[0] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[0];
+ v[1] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[1];
+ v[2] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[2];
+ v[3] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[3];
+ } break;
+ case SW_CURRENT_TEXTURE_COORDS:
+ {
+ v[0] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].texcoord[0];
+ v[1] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].texcoord[1];
+ } break;
+ case SW_POINT_SIZE:
+ {
+ v[0] = 2.0f*RLSW.pointRadius;
+ } break;
+ case SW_LINE_WIDTH:
+ {
+ v[0] = RLSW.lineWidth;
+ } break;
+ case SW_MODELVIEW_MATRIX:
+ {
+ for (int i = 0; i < 16; i++) v[i] = RLSW.stackModelview[RLSW.stackModelviewCounter - 1][i];
+
+ } break;
+ case SW_PROJECTION_MATRIX:
+ {
+ for (int i = 0; i < 16; i++) v[i] = RLSW.stackProjection[RLSW.stackProjectionCounter - 1][i];
+
+ } break;
+ case SW_TEXTURE_MATRIX:
+ {
+ for (int i = 0; i < 16; i++) v[i] = RLSW.stackTexture[RLSW.stackTextureCounter - 1][i];
+
+ } break;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+}
+
+const char *swGetString(SWget name)
+{
+ const char *result = NULL;
+
+ switch (name)
+ {
+ case SW_VENDOR: result = "RLSW Header"; break;
+ case SW_RENDERER: result = "RLSW Software Renderer"; break;
+ case SW_VERSION: result = "RLSW 1.0"; break;
+ case SW_EXTENSIONS: result = "None"; break;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+
+ return result;
+}
+
+SWerrcode swGetError(void)
+{
+ SWerrcode ret = RLSW.errCode;
+ RLSW.errCode = SW_NO_ERROR;
+ return ret;
+}
+
+void swViewport(int x, int y, int width, int height)
+{
+ if ((width < 0) || (height < 0))
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ RLSW.vpSize[0] = width;
+ RLSW.vpSize[1] = height;
+
+ RLSW.vpHalfSize[0] = (int)(width/2.0f + 0.5f);
+ RLSW.vpHalfSize[1] = (int)(height/2.0f + 0.5f);
+
+ RLSW.vpCenter[0] = x + RLSW.vpHalfSize[0];
+ RLSW.vpCenter[1] = y + RLSW.vpHalfSize[1];
+
+ RLSW.vpMin[0] = sw_clampi(x, 0, RLSW.framebuffer.width - 1);
+ RLSW.vpMin[1] = sw_clampi(y, 0, RLSW.framebuffer.height - 1);
+ RLSW.vpMax[0] = sw_clampi(x + width, 0, RLSW.framebuffer.width - 1);
+ RLSW.vpMax[1] = sw_clampi(y + height, 0, RLSW.framebuffer.height - 1);
+}
+
+void swScissor(int x, int y, int width, int height)
+{
+ if ((width < 0) || (height < 0))
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ RLSW.scMin[0] = sw_clampi(x, 0, RLSW.framebuffer.width - 1);
+ RLSW.scMin[1] = sw_clampi(y, 0, RLSW.framebuffer.height - 1);
+ RLSW.scMax[0] = sw_clampi(x + width, 0, RLSW.framebuffer.width - 1);
+ RLSW.scMax[1] = sw_clampi(y + height, 0, RLSW.framebuffer.height - 1);
+
+ RLSW.scClipMin[0] = (2.0f*(float)RLSW.scMin[0]/(float)RLSW.vpSize[0]) - 1.0f;
+ RLSW.scClipMax[0] = (2.0f*(float)RLSW.scMax[0]/(float)RLSW.vpSize[0]) - 1.0f;
+ RLSW.scClipMax[1] = 1.0f - (2.0f*(float)RLSW.scMin[1]/(float)RLSW.vpSize[1]);
+ RLSW.scClipMin[1] = 1.0f - (2.0f*(float)RLSW.scMax[1]/(float)RLSW.vpSize[1]);
+}
+
+void swClearColor(float r, float g, float b, float a)
+{
+ RLSW.clearColor[0] = r;
+ RLSW.clearColor[1] = g;
+ RLSW.clearColor[2] = b;
+ RLSW.clearColor[3] = a;
+}
+
+void swClear(uint32_t bitmask)
+{
+ int size = RLSW.framebuffer.width*RLSW.framebuffer.height;
+
+ if ((bitmask & (SW_COLOR_BUFFER_BIT | SW_DEPTH_BUFFER_BIT)) == (SW_COLOR_BUFFER_BIT | SW_DEPTH_BUFFER_BIT))
+ {
+ sw_framebuffer_fill(RLSW.framebuffer.color, RLSW.framebuffer.depth,size, RLSW.clearColor, RLSW.clearDepth);
+ }
+ else if (bitmask & (SW_COLOR_BUFFER_BIT))
+ {
+ sw_framebuffer_fill_color(RLSW.framebuffer.color, size, RLSW.clearColor);
+ }
+ else if (bitmask & SW_DEPTH_BUFFER_BIT)
+ {
+ sw_framebuffer_fill_depth(RLSW.framebuffer.depth, size, RLSW.clearDepth);
+ }
+}
+
+void swBlendFunc(SWfactor sfactor, SWfactor dfactor)
+{
+ if (!sw_is_blend_src_factor_valid(sfactor) ||
+ !sw_is_blend_dst_factor_valid(dfactor))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ RLSW.srcFactor = sfactor;
+ RLSW.dstFactor = dfactor;
+
+ switch (sfactor)
+ {
+ case SW_ZERO: RLSW.srcFactorFunc = sw_factor_zero; break;
+ case SW_ONE: RLSW.srcFactorFunc = sw_factor_one; break;
+ case SW_SRC_COLOR: RLSW.srcFactorFunc = sw_factor_src_color; break;
+ case SW_ONE_MINUS_SRC_COLOR: RLSW.srcFactorFunc = sw_factor_one_minus_src_color; break;
+ case SW_SRC_ALPHA: RLSW.srcFactorFunc = sw_factor_src_alpha; break;
+ case SW_ONE_MINUS_SRC_ALPHA: RLSW.srcFactorFunc = sw_factor_one_minus_src_alpha; break;
+ case SW_DST_ALPHA: RLSW.srcFactorFunc = sw_factor_dst_alpha; break;
+ case SW_ONE_MINUS_DST_ALPHA: RLSW.srcFactorFunc = sw_factor_one_minus_dst_alpha; break;
+ case SW_DST_COLOR: RLSW.srcFactorFunc = sw_factor_dst_color; break;
+ case SW_ONE_MINUS_DST_COLOR: RLSW.srcFactorFunc = sw_factor_one_minus_dst_color; break;
+ case SW_SRC_ALPHA_SATURATE: RLSW.srcFactorFunc = sw_factor_src_alpha_saturate; break;
+ default: break;
+ }
+
+ switch (dfactor)
+ {
+ case SW_ZERO: RLSW.dstFactorFunc = sw_factor_zero; break;
+ case SW_ONE: RLSW.dstFactorFunc = sw_factor_one; break;
+ case SW_SRC_COLOR: RLSW.dstFactorFunc = sw_factor_src_color; break;
+ case SW_ONE_MINUS_SRC_COLOR: RLSW.dstFactorFunc = sw_factor_one_minus_src_color; break;
+ case SW_SRC_ALPHA: RLSW.dstFactorFunc = sw_factor_src_alpha; break;
+ case SW_ONE_MINUS_SRC_ALPHA: RLSW.dstFactorFunc = sw_factor_one_minus_src_alpha; break;
+ case SW_DST_ALPHA: RLSW.dstFactorFunc = sw_factor_dst_alpha; break;
+ case SW_ONE_MINUS_DST_ALPHA: RLSW.dstFactorFunc = sw_factor_one_minus_dst_alpha; break;
+ case SW_DST_COLOR: RLSW.dstFactorFunc = sw_factor_dst_color; break;
+ case SW_ONE_MINUS_DST_COLOR: RLSW.dstFactorFunc = sw_factor_one_minus_dst_color; break;
+ case SW_SRC_ALPHA_SATURATE: break;
+ default: break;
+ }
+}
+
+void swPolygonMode(SWpoly mode)
+{
+ if (!sw_is_poly_mode_valid(mode))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ RLSW.polyMode = mode;
+}
+
+void swCullFace(SWface face)
+{
+ if (!sw_is_face_valid(face))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ RLSW.cullFace = face;
+}
+
+void swPointSize(float size)
+{
+ RLSW.pointRadius = floorf(size*0.5f);
+}
+
+void swLineWidth(float width)
+{
+ RLSW.lineWidth = roundf(width);
+}
+
+void swMatrixMode(SWmatrix mode)
+{
+ switch (mode)
+ {
+ case SW_PROJECTION: RLSW.currentMatrix = &RLSW.stackProjection[RLSW.stackProjectionCounter - 1]; break;
+ case SW_MODELVIEW: RLSW.currentMatrix = &RLSW.stackModelview[RLSW.stackModelviewCounter - 1]; break;
+ case SW_TEXTURE: RLSW.currentMatrix = &RLSW.stackTexture[RLSW.stackTextureCounter - 1]; break;
+ default: RLSW.errCode = SW_INVALID_ENUM; return;
+ }
+
+ RLSW.currentMatrixMode = mode;
+}
+
+void swPushMatrix(void)
+{
+ switch (RLSW.currentMatrixMode)
+ {
+ case SW_PROJECTION:
+ {
+ if (RLSW.stackProjectionCounter >= SW_MAX_PROJECTION_STACK_SIZE)
+ {
+ RLSW.errCode = SW_STACK_OVERFLOW;
+ return;
+ }
+
+ int iOld = RLSW.stackProjectionCounter - 1;
+ int iNew = RLSW.stackProjectionCounter++;
+
+ for (int i = 0; i < 16; i++)
+ {
+ RLSW.stackProjection[iNew][i] = RLSW.stackProjection[iOld][i];
+ }
+
+ RLSW.currentMatrix = &RLSW.stackProjection[iNew];
+ } break;
+ case SW_MODELVIEW:
+ {
+ if (RLSW.stackModelviewCounter >= SW_MAX_MODELVIEW_STACK_SIZE)
+ {
+ RLSW.errCode = SW_STACK_OVERFLOW;
+ return;
+ }
+
+ int iOld = RLSW.stackModelviewCounter - 1;
+ int iNew = RLSW.stackModelviewCounter++;
+
+ for (int i = 0; i < 16; i++)
+ {
+ RLSW.stackModelview[iNew][i] = RLSW.stackModelview[iOld][i];
+ }
+
+ RLSW.currentMatrix = &RLSW.stackModelview[iNew];
+ } break;
+ case SW_TEXTURE:
+ {
+ if (RLSW.stackTextureCounter >= SW_MAX_TEXTURE_STACK_SIZE)
+ {
+ RLSW.errCode = SW_STACK_OVERFLOW;
+ return;
+ }
+
+ int iOld = RLSW.stackTextureCounter - 1;
+ int iNew = RLSW.stackTextureCounter++;
+
+ for (int i = 0; i < 16; i++)
+ {
+ RLSW.stackTexture[iNew][i] = RLSW.stackTexture[iOld][i];
+ }
+
+ RLSW.currentMatrix = &RLSW.stackTexture[iNew];
+ } break;
+ default: break;
+ }
+}
+
+void swPopMatrix(void)
+{
+ switch (RLSW.currentMatrixMode)
+ {
+ case SW_PROJECTION:
+ {
+ if (RLSW.stackProjectionCounter <= 0)
+ {
+ RLSW.errCode = SW_STACK_UNDERFLOW;
+ return;
+ }
+
+ RLSW.currentMatrix = &RLSW.stackProjection[--RLSW.stackProjectionCounter];
+ RLSW.isDirtyMVP = true; //< The MVP is considered to have been changed
+ } break;
+ case SW_MODELVIEW:
+ {
+ if (RLSW.stackModelviewCounter <= 0)
+ {
+ RLSW.errCode = SW_STACK_UNDERFLOW;
+ return;
+ }
+
+ RLSW.currentMatrix = &RLSW.stackModelview[--RLSW.stackModelviewCounter];
+ RLSW.isDirtyMVP = true; //< The MVP is considered to have been changed
+ } break;
+ case SW_TEXTURE:
+ {
+ if (RLSW.stackTextureCounter <= 0)
+ {
+ RLSW.errCode = SW_STACK_UNDERFLOW;
+ return;
+ }
+
+ RLSW.currentMatrix = &RLSW.stackTexture[--RLSW.stackTextureCounter];
+ } break;
+ default: break;
+ }
+}
+
+void swLoadIdentity(void)
+{
+ sw_matrix_id(*RLSW.currentMatrix);
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swTranslatef(float x, float y, float z)
+{
+ sw_matrix_t mat;
+ sw_matrix_id(mat);
+
+ mat[12] = x;
+ mat[13] = y;
+ mat[14] = z;
+
+ sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix);
+
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swRotatef(float angle, float x, float y, float z)
+{
+ angle *= SW_DEG2RAD;
+
+ float lengthSq = x*x + y*y + z*z;
+
+ if (lengthSq != 1.0f && lengthSq != 0.0f)
+ {
+ float invLength = 1.0f/sqrtf(lengthSq);
+ x *= invLength;
+ y *= invLength;
+ z *= invLength;
+ }
+
+ float sinres = sinf(angle);
+ float cosres = cosf(angle);
+ float t = 1.0f - cosres;
+
+ sw_matrix_t mat;
+
+ mat[0] = x*x*t + cosres;
+ mat[1] = y*x*t + z*sinres;
+ mat[2] = z*x*t - y*sinres;
+ mat[3] = 0.0f;
+
+ mat[4] = x*y*t - z*sinres;
+ mat[5] = y*y*t + cosres;
+ mat[6] = z*y*t + x*sinres;
+ mat[7] = 0.0f;
+
+ mat[8] = x*z*t + y*sinres;
+ mat[9] = y*z*t - x*sinres;
+ mat[10] = z*z*t + cosres;
+ mat[11] = 0.0f;
+
+ mat[12] = 0.0f;
+ mat[13] = 0.0f;
+ mat[14] = 0.0f;
+ mat[15] = 1.0f;
+
+ sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix);
+
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swScalef(float x, float y, float z)
+{
+ sw_matrix_t mat;
+
+ mat[0] = x, mat[1] = 0, mat[2] = 0, mat[3] = 0;
+ mat[4] = 0, mat[5] = y, mat[6] = 0, mat[7] = 0;
+ mat[8] = 0, mat[9] = 0, mat[10] = z, mat[11] = 0;
+ mat[12] = 0, mat[13] = 0, mat[14] = 0, mat[15] = 1;
+
+ sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix);
+
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swMultMatrixf(const float *mat)
+{
+ sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix);
+
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swFrustum(double left, double right, double bottom, double top, double znear, double zfar)
+{
+ sw_matrix_t mat;
+
+ double rl = right - left;
+ double tb = top - bottom;
+ double fn = zfar - znear;
+
+ mat[0] = (float)(znear*2.0)/rl;
+ mat[1] = 0.0f;
+ mat[2] = 0.0f;
+ mat[3] = 0.0f;
+
+ mat[4] = 0.0f;
+ mat[5] = (float)(znear*2.0)/tb;
+ mat[6] = 0.0f;
+ mat[7] = 0.0f;
+
+ mat[8] = (float)(right + left)/rl;
+ mat[9] = (float)(top + bottom)/tb;
+ mat[10] = -(float)(zfar + znear)/fn;
+ mat[11] = -1.0f;
+
+ mat[12] = 0.0f;
+ mat[13] = 0.0f;
+ mat[14] = -(zfar*znear*2.0)/fn;
+ mat[15] = 0.0f;
+
+ sw_matrix_mul(*RLSW.currentMatrix, *RLSW.currentMatrix, mat);
+
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swOrtho(double left, double right, double bottom, double top, double znear, double zfar)
+{
+ sw_matrix_t mat;
+
+ double rl = right - left;
+ double tb = top - bottom;
+ double fn = zfar - znear;
+
+ mat[0] = 2.0f/(float)rl;
+ mat[1] = 0.0f;
+ mat[2] = 0.0f;
+ mat[3] = 0.0f;
+
+ mat[4] = 0.0f;
+ mat[5] = 2.0f/(float)tb;
+ mat[6] = 0.0f;
+ mat[7] = 0.0f;
+
+ mat[8] = 0.0f;
+ mat[9] = 0.0f;
+ mat[10] = -2.0f/(float)fn;
+ mat[11] = 0.0f;
+
+ mat[12] = -(float)(left + right)/rl;
+ mat[13] = -(float)(top + bottom)/tb;
+ mat[14] = -(float)(zfar + znear)/fn;
+ mat[15] = 1.0f;
+
+ sw_matrix_mul(*RLSW.currentMatrix, *RLSW.currentMatrix, mat);
+
+ if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true;
+}
+
+void swBegin(SWdraw mode)
+{
+ // Check if the draw mode is valid
+ if (!sw_is_draw_mode_valid(mode))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ // Recalculate the MVP if this is needed
+ if (RLSW.isDirtyMVP)
+ {
+ sw_matrix_mul_rst(RLSW.matMVP,
+ RLSW.stackModelview[RLSW.stackModelviewCounter - 1],
+ RLSW.stackProjection[RLSW.stackProjectionCounter - 1]);
+
+ RLSW.isDirtyMVP = false;
+ }
+
+ // Obtain the number of vertices needed for this primitive
+ switch (mode)
+ {
+ case SW_POINTS: RLSW.reqVertices = 1; break;
+ case SW_LINES: RLSW.reqVertices = 2; break;
+ case SW_TRIANGLES: RLSW.reqVertices = 3; break;
+ case SW_QUADS: RLSW.reqVertices = 4; break;
+ }
+
+ // Initialize required values
+ RLSW.vertexCounter = 0;
+ RLSW.drawMode = mode;
+}
+
+void swEnd(void)
+{
+ RLSW.drawMode = 0;
+}
+
+void swVertex2i(int x, int y)
+{
+ const float v[4] = { (float)x, (float)y, 0.0f, 1.0f };
+ swVertex4fv(v);
+}
+
+void swVertex2f(float x, float y)
+{
+ const float v[4] = { x, y, 0.0f, 1.0f };
+ swVertex4fv(v);
+}
+
+void swVertex2fv(const float *v)
+{
+ const float v4[4] = { v[0], v[1], 0.0f, 1.0f };
+ swVertex4fv(v4);
+}
+
+void swVertex3i(int x, int y, int z)
+{
+ const float v[4] = { (float)x, (float)y, (float)z, 1.0f };
+ swVertex4fv(v);
+}
+
+void swVertex3f(float x, float y, float z)
+{
+ const float v[4] = { x, y, z, 1.0f };
+ swVertex4fv(v);
+}
+
+void swVertex3fv(const float *v)
+{
+ const float v4[4] = { v[0], v[1], v[2], 1.0f };
+ swVertex4fv(v4);
+}
+
+void swVertex4i(int x, int y, int z, int w)
+{
+ const float v[4] = { (float)x, (float)y, (float)z, (float)w };
+ swVertex4fv(v);
+}
+
+void swVertex4f(float x, float y, float z, float w)
+{
+ const float v[4] = { x, y, z, w };
+ swVertex4fv(v);
+}
+
+void swVertex4fv(const float *v)
+{
+ // Copy the position in the current vertex
+ sw_vertex_t *vertex = &RLSW.vertexBuffer[RLSW.vertexCounter++];
+ for (int i = 0; i < 4; i++) vertex->position[i] = v[i];
+
+ // Copy additonal vertex data
+ for (int i = 0; i < 2; i++) vertex->texcoord[i] = RLSW.current.texcoord[i];
+ for (int i = 0; i < 4; i++) vertex->color[i] = RLSW.current.color[i];
+
+ // Calculation of homogeneous coordinates
+ const float *m = RLSW.matMVP;
+ vertex->homogeneous[0] = m[0]*v[0] + m[4]*v[1] + m[8]*v[2] + m[12]*v[3];
+ vertex->homogeneous[1] = m[1]*v[0] + m[5]*v[1] + m[9]*v[2] + m[13]*v[3];
+ vertex->homogeneous[2] = m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3];
+ vertex->homogeneous[3] = m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3];
+
+ // Immediate rendering of the primitive if the required number is reached
+ if (RLSW.vertexCounter == RLSW.reqVertices)
+ {
+ switch (RLSW.polyMode)
+ {
+ case SW_FILL: sw_poly_fill_render(); break;
+ case SW_LINE: sw_poly_line_render(); break;
+ case SW_POINT: sw_poly_point_render(); break;
+ default: break;
+ }
+
+ RLSW.vertexCounter = 0;
+ }
+}
+
+void swColor3ub(uint8_t r, uint8_t g, uint8_t b)
+{
+ float cv[4];
+ cv[0] = (float)r/255;
+ cv[1] = (float)g/255;
+ cv[2] = (float)b/255;
+ cv[3] = 1.0f;
+
+ swColor4fv(cv);
+}
+
+void swColor3ubv(const uint8_t *v)
+{
+ float cv[4];
+ cv[0] = (float)v[0]/255;
+ cv[1] = (float)v[1]/255;
+ cv[2] = (float)v[2]/255;
+ cv[3] = 1.0f;
+
+ swColor4fv(cv);
+}
+
+void swColor3f(float r, float g, float b)
+{
+ float cv[4];
+ cv[0] = r;
+ cv[1] = g;
+ cv[2] = b;
+ cv[3] = 1.0f;
+
+ swColor4fv(cv);
+}
+
+void swColor3fv(const float *v)
+{
+ float cv[4];
+ cv[0] = v[0];
+ cv[1] = v[1];
+ cv[2] = v[2];
+ cv[3] = 1.0f;
+
+ swColor4fv(cv);
+}
+
+void swColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ float cv[4];
+ cv[0] = (float)r/255;
+ cv[1] = (float)g/255;
+ cv[2] = (float)b/255;
+ cv[3] = (float)a/255;
+
+ swColor4fv(cv);
+}
+
+void swColor4ubv(const uint8_t *v)
+{
+ float cv[4];
+ cv[0] = (float)v[0]/255;
+ cv[1] = (float)v[1]/255;
+ cv[2] = (float)v[2]/255;
+ cv[3] = (float)v[3]/255;
+
+ swColor4fv(cv);
+}
+
+void swColor4f(float r, float g, float b, float a)
+{
+ float cv[4];
+ cv[0] = r;
+ cv[1] = g;
+ cv[2] = b;
+ cv[3] = a;
+
+ swColor4fv(cv);
+}
+
+void swColor4fv(const float *v)
+{
+ for (int i = 0; i < 4; i++) RLSW.current.color[i] = v[i];
+}
+
+void swTexCoord2f(float u, float v)
+{
+ const float *m = RLSW.stackTexture[RLSW.stackTextureCounter - 1];
+
+ RLSW.current.texcoord[0] = m[0]*u + m[4]*v + m[12];
+ RLSW.current.texcoord[1] = m[1]*u + m[5]*v + m[13];
+}
+
+void swTexCoord2fv(const float *v)
+{
+ const float *m = RLSW.stackTexture[RLSW.stackTextureCounter - 1];
+
+ RLSW.current.texcoord[0] = m[0]*v[0] + m[4]*v[1] + m[12];
+ RLSW.current.texcoord[1] = m[1]*v[0] + m[5]*v[1] + m[13];
+}
+
+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;
+ default: break;
+ }
+}
+
+void swDrawArrays(SWdraw mode, int offset, int count)
+{
+ if (RLSW.array.positions == 0)
+ {
+ RLSW.errCode = SW_INVALID_OPERATION;
+ return;
+ }
+
+ swBegin(mode);
+ {
+ swTexCoord2f(0.0f, 0.0f);
+ swColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ for (int i = offset; i < count; i++)
+ {
+ if (RLSW.array.texcoords) swTexCoord2fv(RLSW.array.texcoords + 2*i);
+ if (RLSW.array.colors) swColor4ubv(RLSW.array.colors + 4*i);
+ swVertex3fv(RLSW.array.positions + 3*i);
+ }
+ }
+ swEnd();
+}
+
+void swGenTextures(int count, uint32_t *textures)
+{
+ if ((count == 0) || (textures == NULL)) return;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (RLSW.loadedTextureCount >= SW_MAX_TEXTURES)
+ {
+ RLSW.errCode = SW_STACK_OVERFLOW; // WARNING: Out of memory, not really stack overflow
+ return;
+ }
+
+ uint32_t id = 0;
+ if (RLSW.freeTextureIdCount > 0) id = RLSW.freeTextureIds[--RLSW.freeTextureIdCount];
+ else id = RLSW.loadedTextureCount++;
+
+ RLSW.loadedTextures[id] = RLSW.loadedTextures[0];
+ textures[i] = id;
+ }
+}
+
+void swDeleteTextures(int count, uint32_t *textures)
+{
+ if ((count == 0) || (textures == NULL)) return;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (!sw_is_texture_valid(textures[i]))
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ continue;
+ }
+
+ if (RLSW.loadedTextures[textures[i]].copy)
+ {
+ SW_FREE(RLSW.loadedTextures[textures[i]].pixels.ptr);
+ }
+
+ RLSW.loadedTextures[textures[i]].pixels.cptr = NULL;
+ RLSW.freeTextureIds[RLSW.freeTextureIdCount++] = textures[i];
+ }
+}
+
+void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data)
+{
+ uint32_t id = RLSW.currentTexture;
+
+ if (!sw_is_texture_valid(id))
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ int pixelFormat = sw_get_pixel_format(format, type);
+
+ if (pixelFormat <= SW_PIXELFORMAT_UNKNOWN)
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ sw_texture_t *texture = &RLSW.loadedTextures[id];
+
+ if (copy)
+ {
+ int bytes = sw_get_pixel_bytes(pixelFormat);
+ int size = bytes*width*height;
+ texture->pixels.ptr = SW_MALLOC(size);
+
+ 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];
+ }
+ }
+ else texture->pixels.cptr = data;
+
+ texture->width = width;
+ texture->height = height;
+ texture->wMinus1 = width - 1;
+ texture->hMinus1 = height - 1;
+ texture->format = pixelFormat;
+ texture->tx = 1.0f/width;
+ texture->ty = 1.0f/height;
+ texture->copy = copy;
+}
+
+void swTexParameteri(int param, int value)
+{
+ uint32_t id = RLSW.currentTexture;
+
+ if (!sw_is_texture_valid(id))
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ sw_texture_t *texture = &RLSW.loadedTextures[id];
+
+ switch (param)
+ {
+ case SW_TEXTURE_MIN_FILTER:
+ {
+ if (!sw_is_texture_filter_valid(value))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ texture->minFilter = value;
+ } break;
+ case SW_TEXTURE_MAG_FILTER:
+ {
+ if (!sw_is_texture_filter_valid(value))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ texture->magFilter = value;
+ } break;
+ case SW_TEXTURE_WRAP_S:
+ {
+ if (!sw_is_texture_wrap_valid(value))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ texture->sWrap = value;
+ } break;
+ case SW_TEXTURE_WRAP_T:
+ {
+ if (!sw_is_texture_wrap_valid(value))
+ {
+ RLSW.errCode = SW_INVALID_ENUM;
+ return;
+ }
+
+ texture->tWrap = value;
+ } break;
+ default: RLSW.errCode = SW_INVALID_ENUM; break;
+ }
+}
+
+void swBindTexture(uint32_t id)
+{
+ if (id >= SW_MAX_TEXTURES)
+ {
+ RLSW.errCode = SW_INVALID_VALUE;
+ return;
+ }
+
+ if (RLSW.loadedTextures[id].pixels.cptr == NULL)
+ {
+ RLSW.errCode = SW_INVALID_OPERATION;
+ return;
+ }
+
+ RLSW.currentTexture = id;
+}
+
+#endif // RLSW_IMPLEMENTATION
diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c
index 6c67dc893..6ecc41888 100644
--- a/src/platforms/rcore_android.c
+++ b/src/platforms/rcore_android.c
@@ -447,8 +447,8 @@ int GetMonitorCount(void)
int GetCurrentMonitor(void)
{
int displayId = -1;
- JNIEnv* env = NULL;
- JavaVM* vm = platform.app->activity->vm;
+ JNIEnv *env = NULL;
+ JavaVM *vm = platform.app->activity->vm;
(*vm)->AttachCurrentThread(vm, &env, NULL);
jobject activity = platform.app->activity->clazz;
@@ -458,12 +458,15 @@ int GetCurrentMonitor(void)
jobject display = (*env)->CallObjectMethod(env, activity, getDisplayMethod);
- if (display == NULL) {
+ if (display == NULL)
+ {
TRACELOG(LOG_ERROR, "GetCurrentMonitor() couldn't get the display object");
- } else {
+ }
+ else
+ {
jclass displayClass = (*env)->FindClass(env, "android/view/Display");
jmethodID getDisplayIdMethod = (*env)->GetMethodID(env, displayClass, "getDisplayId", "()I");
- displayId = (int) (*env)->CallIntMethod(env, display, getDisplayIdMethod);
+ displayId = (int)(*env)->CallIntMethod(env, display, getDisplayIdMethod);
(*env)->DeleteLocalRef(env, displayClass);
}
diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c
index 71265e718..6655db460 100644
--- a/src/platforms/rcore_desktop_sdl.c
+++ b/src/platforms/rcore_desktop_sdl.c
@@ -63,18 +63,20 @@
#include "SDL.h"
#endif
-#if defined(GRAPHICS_API_OPENGL_ES2)
- // It seems it does not need to be included to work
- //#include "SDL_opengles2.h"
-#else
- // SDL OpenGL functionality (if required, instead of internal renderer)
- #ifdef USING_SDL3_PROJECT
- #include "SDL3/SDL_opengl.h"
- #elif USING_SDL2_PROJECT
- #include "SDL2/SDL_opengl.h"
- #else
- #include "SDL_opengl.h"
- #endif
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ #if defined(GRAPHICS_API_OPENGL_ES2)
+ // It seems it does not need to be included to work
+ //#include "SDL_opengles2.h"
+ #else
+ // SDL OpenGL functionality (if required, instead of internal renderer)
+ #ifdef USING_SDL3_PROJECT
+ #include "SDL3/SDL_opengl.h"
+ #elif USING_SDL2_PROJECT
+ #include "SDL2/SDL_opengl.h"
+ #else
+ #include "SDL_opengl.h"
+ #endif
+ #endif
#endif
//----------------------------------------------------------------------------------
@@ -1229,7 +1231,14 @@ void DisableCursor(void)
// Swap back buffer with front buffer (screen drawing)
void SwapScreenBuffer(void)
{
+#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ // NOTE: We use a preprocessor condition here because `rlCopyFramebuffer` is only declared for software rendering
+ SDL_Surface *surface = SDL_GetWindowSurface(platform.window);
+ rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, surface->pixels);
+ SDL_UpdateWindowSurface(platform.window);
+#else
SDL_GL_SwapWindow(platform.window);
+#endif
}
//----------------------------------------------------------------------------------
@@ -1577,13 +1586,15 @@ void PollInputEvents(void)
if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
{
// Add character (codepoint) to the queue
- #if defined(USING_VERSION_SDL3)
+
+ #if defined(USING_VERSION_SDL3)
size_t textLen = strlen(event.text.text);
unsigned int codepoint = (unsigned int)SDL_StepUTF8(&event.text.text, &textLen);
- #else
+ #else
int codepointSize = 0;
int codepoint = GetCodepointNextSDL(event.text.text, &codepointSize);
- #endif
+ #endif
+
CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = codepoint;
CORE.Input.Keyboard.charPressedQueueCount++;
}
@@ -1887,7 +1898,6 @@ int InitPlatform(void)
//----------------------------------------------------------------------------
unsigned int flags = 0;
flags |= SDL_WINDOW_SHOWN;
- flags |= SDL_WINDOW_OPENGL;
flags |= SDL_WINDOW_INPUT_FOCUS;
flags |= SDL_WINDOW_MOUSE_FOCUS;
flags |= SDL_WINDOW_MOUSE_CAPTURE; // Window has mouse captured
@@ -1922,44 +1932,50 @@ int InitPlatform(void)
// NOTE: Some OpenGL context attributes must be set before window creation
- // Check selection OpenGL version
- if (rlGetVersion() == RL_OPENGL_21)
+ if (rlGetVersion() != RL_OPENGL_11_SOFTWARE)
{
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
- }
- else if (rlGetVersion() == RL_OPENGL_33)
- {
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
- }
- else if (rlGetVersion() == RL_OPENGL_43)
- {
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
-#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT)
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); // Enable OpenGL Debug Context
-#endif
- }
- else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context
- {
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
- }
- else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context
- {
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
- }
+ // Add the flag telling the window to use an OpenGL context
+ flags |= SDL_WINDOW_OPENGL;
- if (CORE.Window.flags & FLAG_MSAA_4X_HINT)
- {
- SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
- SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
+ // Check selection OpenGL version
+ if (rlGetVersion() == RL_OPENGL_21)
+ {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+ }
+ else if (rlGetVersion() == RL_OPENGL_33)
+ {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ }
+ else if (rlGetVersion() == RL_OPENGL_43)
+ {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ #if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT)
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); // Enable OpenGL Debug Context
+ #endif
+ }
+ else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context
+ {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+ }
+ else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context
+ {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+ }
+
+ if (CORE.Window.flags & FLAG_MSAA_4X_HINT)
+ {
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
+ }
}
// Init window
@@ -1970,10 +1986,12 @@ int InitPlatform(void)
#endif
// Init OpenGL context
- platform.glContext = SDL_GL_CreateContext(platform.window);
+ if (rlGetVersion() != RL_OPENGL_11_SOFTWARE)
+ {
+ platform.glContext = SDL_GL_CreateContext(platform.window);
+ }
- // Check window and glContext have been initialized successfully
- if ((platform.window != NULL) && (platform.glContext != NULL))
+ if ((platform.window != NULL) && ((rlGetVersion() == RL_OPENGL_11_SOFTWARE) || (platform.glContext != NULL)))
{
CORE.Window.ready = true;
@@ -1994,8 +2012,14 @@ 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);
- if (CORE.Window.flags & FLAG_VSYNC_HINT) SDL_GL_SetSwapInterval(1);
- else SDL_GL_SetSwapInterval(0);
+ if (platform.glContext != NULL)
+ {
+ SDL_GL_SetSwapInterval((CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0);
+
+ // Load OpenGL extensions
+ // NOTE: GL procedures address loader is required to load extensions
+ rlLoadExtensions(SDL_GL_GetProcAddress);
+ }
}
else
{
@@ -2003,9 +2027,6 @@ int InitPlatform(void)
return -1;
}
- // Load OpenGL extensions
- // NOTE: GL procedures address loader is required to load extensions
- rlLoadExtensions(SDL_GL_GetProcAddress);
//----------------------------------------------------------------------------
// Initialize input events system
@@ -2077,7 +2098,7 @@ int InitPlatform(void)
void ClosePlatform(void)
{
SDL_FreeCursor(platform.cursor); // Free cursor
- SDL_GL_DeleteContext(platform.glContext); // Deinitialize OpenGL context
+ if (platform.glContext != NULL) SDL_GL_DeleteContext(platform.glContext); // Deinitialize OpenGL context
SDL_DestroyWindow(platform.window);
SDL_Quit(); // Deinitialize SDL internal global state
}
diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c
index a79fa692e..c7740ba2e 100644
--- a/src/platforms/rcore_desktop_win32.c
+++ b/src/platforms/rcore_desktop_win32.c
@@ -6,19 +6,23 @@
* - Windows (Win32, Win64)
*
* LIMITATIONS:
-* - currently in initial development stage, alot is missing
-* - unsure how to support MOUSE_BUTTON_FORWARD/MOUSE_BUTTON_BACK
+* - Initial development stage, lot of functionality missing
+* - No support for MOUSE_BUTTON_FORWARD/MOUSE_BUTTON_BACK
*
* POSSIBLE IMPROVEMENTS:
+* - Improvement 01
+* - Improvement 02
*
* ADDITIONAL NOTES:
+* - TRACELOG() function is located in raylib [utils] module
*
* CONFIGURATION:
* #define RCORE_PLATFORM_CUSTOM_FLAG
* Custom flag for rcore on target platform -not used-
*
* DEPENDENCIES:
-* - the win32 API, i.e. windows.h
+* - Win32 API (windows.h)
+*
*
* LICENSE: zlib/libpng
*
@@ -41,7 +45,7 @@
*
**********************************************************************************************/
-// move windows.h functions to new names to avoid redefining the same functions as raylib
+// Move windows.h symbols to new names to avoid redefining the same names as raylib
#define CloseWindow CloseWindowWin32
#define Rectangle RectangleWin32
#define ShowCursor ShowCursorWin32
@@ -55,16 +59,16 @@
#define WIN32_LEAN_AND_MEAN
#include
-#undef CloseWindow
-#undef Rectangle
-#undef ShowCursor
-#undef LoadImage
+#undef CloseWindow // raylib symbol collision
+#undef Rectangle // raylib symbol collision
+#undef ShowCursor // raylib symbol collision
+#undef LoadImage // raylib symbol collision
#undef LoadImageA
#undef LoadImageW
-#undef DrawText
+#undef DrawText // raylib symbol collision
#undef DrawTextA
#undef DrawTextW
-#undef DrawTextEx
+#undef DrawTextEx // raylib symbol collision
#undef DrawTextExA
#undef DrawTextExW
@@ -73,99 +77,187 @@
#include
#include
-#include
+//#include
+
+//----------------------------------------------------------------------------------
+// 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)
+// when the window isn't fullscreen/maximized/minimized
+typedef struct {
+ HWND hwnd;
+ HDC hdc;
+ HGLRC glContext;
+ LARGE_INTEGER timerFrequency;
+ unsigned int appScreenWidth;
+ unsigned int appScreenHeight;
+ unsigned int desiredFlags;
+ bool cursorEnabled;
+} 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);
+
+//----------------------------------------------------------------------------------
+// Global Variables Definition
+//----------------------------------------------------------------------------------
+extern CoreData CORE; // Global CORE state context
+
+static PlatformData platform = { 0 }; // Platform specific data
+
+// Required WGL functions
+static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
+static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL;
+static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
// --------------------------------------------------------------------------------
-// This part of the file contains pure functions that never access global state.
+// This part of the file contains pure functions that never access global state
// This distinction helps keep the backend maintainable as the inputs and outputs
-// of every function called in this section can be fully derived from the
-// call-site alone.
+// of every function called in this section can be fully derived from the call-site alone
// --------------------------------------------------------------------------------
// Prevent any code in this part of the file from accessing the global CORE state
#define CORE DONT_USE_CORE_HERE
+//----------------------------------------------------------------------------------
+// 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; \
+} while (0)
+
+#define STYLE_MASK_ALL 0xffffffff
+#define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE)
+#define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY)
+
+#define STYLE_FLAGS_RESIZABLE WS_THICKFRAME
+
+#define STYLE_FLAGS_UNDECORATED_OFF (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
+#define STYLE_FLAGS_UNDECORATED_ON WS_POPUP
+
+#define WINDOW_STYLE_EX 0
+
+#define CLASS_NAME L"RaylibWindow"
+
+#define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT)
+#define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL)
+
+// Flags that have no operations to perform during an update
+#define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT)
+
+#define WM_APP_UPDATE_WINDOW_SIZE (WM_APP + 1)
+
+#define WGL_DRAW_TO_WINDOW_ARB 0x2001
+#define WGL_ACCELERATION_ARB 0x2003
+#define WGL_SUPPORT_OPENGL_ARB 0x2010
+#define WGL_DOUBLE_BUFFER_ARB 0x2011
+#define WGL_PIXEL_TYPE_ARB 0x2013
+#define WGL_COLOR_BITS_ARB 0x2014
+#define WGL_RED_BITS_ARB 0x2015
+#define WGL_RED_SHIFT_ARB 0x2016
+#define WGL_GREEN_BITS_ARB 0x2017
+#define WGL_GREEN_SHIFT_ARB 0x2018
+#define WGL_BLUE_BITS_ARB 0x2019
+#define WGL_BLUE_SHIFT_ARB 0x201a
+#define WGL_ALPHA_BITS_ARB 0x201b
+#define WGL_ALPHA_SHIFT_ARB 0x201c
+#define WGL_DEPTH_BITS_ARB 0x2022
+#define WGL_STENCIL_BITS_ARB 0x2023
+#define WGL_TYPE_RGBA_ARB 0x202b
+
+// Context acceleration types
+#define WGL_NO_ACCELERATION_ARB 0x2025 // OpenGL 1.1 GDI software rasterizer
+#define WGL_GENERIC_ACCELERATION_ARB 0x2026
+#define WGL_FULL_ACCELERATION_ARB 0x2027 // OpenGL hardware-accelerated, using GPU-drivers provided by vendor
+
+// WGL_ARB_multisample extension supported
+#define WGL_SAMPLE_BUFFERS_ARB 0x2041 // Multisampling: 1 if multisample buffers are supported
+#define WGL_SAMPLES_ARB 0x2042 // Multisampling: Number of samples per pixel (4, 8, 16)
+
+// WGL_ARB_framebuffer_sRGB extension supported
+#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 // GL_TRUE if the framebuffer can do sRGB conversion
+
+#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
+#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
+#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
+#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
+#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
+
+//----------------------------------------------------------------------------------
+// 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;
+
+typedef struct {
+ DWORD set;
+ DWORD clear;
+} FlagsOp;
+
+//----------------------------------------------------------------------------------
+// Module Internal Functions Declaration
+//----------------------------------------------------------------------------------
static size_t AToWLen(const char *a)
{
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, a, -1, NULL, 0);
- if (sizeNeeded < 0)
- {
- TRACELOG(LOG_ERROR, "failed to calculate wide length, result=%d, error=%u", sizeNeeded, GetLastError());
- abort();
- }
+
+ if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "Failed to calculate wide length, result=%d, error=%u", sizeNeeded, GetLastError());
return sizeNeeded;
}
static void AToWCopy(wchar_t *outPtr, size_t outLen, const char *a)
{
- int size = MultiByteToWideChar(CP_UTF8, 0, a, -1, outPtr, outLen);
- if (size != outLen)
- {
- TRACELOG(LOG_ERROR, "expected to convert %zu utf8 chars to wide but converted %zu", outLen, size);
- abort();
- }
-}
-#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; \
-} while (0)
-
-static void LogFail(const char *what, DWORD error)
-{
- TRACELOG(LOG_ERROR, "%s failed, error=%lu", what, error);
-}
-static void LogFailHr(const char *what, HRESULT hr)
-{
- TRACELOG(LOG_ERROR, "%s failed, hresult=0x%lx", what, (DWORD)hr);
+ 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);
}
-#define STYLE_FLAGS_RESIZABLE WS_THICKFRAME
-
-#define STYLE_FLAGS_UNDECORATED_OFF (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
-#define STYLE_FLAGS_UNDECORATED_ON WS_POPUP
-
-static bool ResizableFromStyle(DWORD style)
-{
- return 0 != (style & STYLE_FLAGS_RESIZABLE);
-}
static bool DecoratedFromStyle(DWORD style)
{
if (style & STYLE_FLAGS_UNDECORATED_ON)
{
- if (style & STYLE_FLAGS_UNDECORATED_OFF)
- {
- TRACELOG(LOG_ERROR, "style 0x%x has both undecorated on/off flags", style);
- abort();
- }
-
- return false; // not decorated
+ if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "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, "style 0x%x is missing these flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF);
- abort();
- }
+ 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);
- return true; // decorated
-}
-static bool HiddenFromStyle(DWORD style)
-{
- return 0 == (style & WS_VISIBLE);
+ return true; // Decorated
}
-typedef enum { MIZED_NONE, MIZED_MIN, MIZED_MAX } Mized;
-Mized MizedFromStyle(DWORD style)
+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;
}
-Mized MizedFromFlags(unsigned flags)
+
+static Mized MizedFromFlags(unsigned flags)
{
// minimized takes precedence over maximized
if (flags & FLAG_WINDOW_MINIMIZED) return MIZED_MIN;
@@ -175,12 +267,11 @@ Mized MizedFromFlags(unsigned flags)
static DWORD MakeWindowStyle(unsigned flags)
{
- DWORD style =
- // 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.
- WS_CLIPSIBLINGS
- ;
+ // 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 ;
+
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;
@@ -190,15 +281,9 @@ static DWORD MakeWindowStyle(unsigned flags)
case MIZED_NONE: break;
case MIZED_MIN: style |= WS_MINIMIZE; break;
case MIZED_MAX: style |= WS_MAXIMIZE; break;
- default: abort();
+ default: break;
}
- // sanity checks, maybe remove later
- if (ResizableFromStyle(style) != !!(flags & FLAG_WINDOW_RESIZABLE)) abort();
- if (DecoratedFromStyle(style) != !(flags & FLAG_WINDOW_UNDECORATED)) abort();
- if (HiddenFromStyle(style) != !!(flags & FLAG_WINDOW_HIDDEN)) abort();
- if (MizedFromStyle(style) != MizedFromFlags(flags)) abort();
-
return style;
}
@@ -206,171 +291,91 @@ static bool IsMinimized2(HWND hwnd)
{
bool isIconic = IsIconic(hwnd);
bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE));
- if (isIconic != styleMinimized)
- {
- TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized);
- abort();
- }
+ if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized);
return isIconic;
}
-#define STYLE_MASK_ALL 0xffffffff
-#define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE)
-#define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY)
-
// 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
-) {
+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))
{
- 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)",
- context,
- flags,
- styleFromFlags & styleCheckMask,
- expectedStyle & styleCheckMask,
- (styleFromFlags & styleCheckMask) ^ (expectedStyle & styleCheckMask),
- styleCheckMask
- );
- abort();
- }
+ TRACELOG(LOG_ERROR, "%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);
}
SetLastError(0);
- LONG actualStyle = GetWindowLongPtrW(hwnd, GWL_STYLE);
+ 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)",
- context,
- expectedStyle & styleCheckMask,
- actualStyle & styleCheckMask,
+ TRACELOG(LOG_ERROR, "%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()
- );
- abort();
+ styleCheckMask, GetLastError());
}
if (styleCheckMask & WS_MINIMIZE)
{
bool isIconic = IsIconic(hwnd);
bool styleMinimized = !!(WS_MINIMIZE & actualStyle);
- if (isIconic != styleMinimized) {
- TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized);
- abort();
- }
+ if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized);
}
if (styleCheckMask & WS_MAXIMIZE)
{
WINDOWPLACEMENT placement;
placement.length = sizeof(placement);
- if (!GetWindowPlacement(hwnd, &placement)) {
- LogFail("GetWindowPlacement", GetLastError());
- abort();
+ if (!GetWindowPlacement(hwnd, &placement))
+ {
+ TRACELOG(LOG_ERROR, "%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",
- placementMaximized,
- placement.showCmd,
- styleMaximized
- );
- abort();
+ TRACELOG(LOG_ERROR, "maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d",
+ placementMaximized, placement.showCmd, styleMaximized);
}
}
}
-static float PtFromPx(float dpiScale, bool highdpiEnabled, int pt)
-{
- return highdpiEnabled? (((float)pt)/dpiScale) : pt;
-}
-static int PxFromPt(float dpiScale, bool highdpiEnabled, float pt)
-{
- return highdpiEnabled? roundf(pt*dpiScale) : roundf(pt);
-}
-static SIZE PxFromPt2(float dpiScale, bool highdpiEnabled, Vector2 screenSize)
+static SIZE PxFromPt2(float dpiScale, bool highdpiEnabled, int screenWidth, int screenHeight)
{
+ // Get size in pixels from points
return (SIZE){
- PxFromPt(dpiScale, highdpiEnabled, screenSize.x),
- PxFromPt(dpiScale, highdpiEnabled, screenSize.y),
+ highdpiEnabled? (int)((float)screenWidth*dpiScale) : screenWidth,
+ highdpiEnabled? (int)((float)screenHeight*dpiScale) : screenHeight,
};
}
static SIZE GetClientSize(HWND hwnd)
{
- RECT rect;
- if (0 == GetClientRect(hwnd, &rect))
- {
- LogFail("GetClientRect", GetLastError());
- abort();
- }
-
- if (rect.left != 0) abort(); // never happens AFAIK
- if (rect.top != 0) abort(); // never happens AFAIK
+ RECT rect = { 0 };
+
+ if (GetClientRect(hwnd, &rect) == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetClientRect", GetLastError());
+
return (SIZE){ rect.right, rect.bottom };
}
-static UINT GetWindowDpi(HWND hwnd)
-{
- UINT dpi = GetDpiForWindow(hwnd);
- if (dpi == 0)
- {
- LogFail("GetWindowDpi", GetLastError());
- abort();
- }
-
- return dpi;
-}
-
-static float ScaleFromDpi(UINT dpi)
-{
- return ((float)dpi)/96.0f;
-}
-
-#define WINDOW_STYLE_EX 0
-
static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style)
{
RECT rect = { 0, 0, clientSize.cx, clientSize.cy };
- if (!AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi))
- {
- LogFail("AdjustWindowRect", GetLastError());
- abort();
- }
+
+ int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi);
+
+ if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "AdjustWindowRect", GetLastError());
return (SIZE){ rect.right - rect.left, rect.bottom - rect.top };
}
-typedef enum {
- UPDATE_WINDOW_FIRST,
- UPDATE_WINDOW_NORMAL,
-} UpdateWindowKind;
-
// returns true if the window size was updated, false otherwise
-static bool UpdateWindowSize(
- UpdateWindowKind kind,
- HWND hwnd,
- Vector2 appScreenSize,
- unsigned flags
-) {
+static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int height, unsigned flags)
+{
if (flags & FLAG_WINDOW_MINIMIZED) return false;
if (flags & FLAG_WINDOW_MAXIMIZED)
@@ -381,79 +386,52 @@ static bool UpdateWindowSize(
if (flags & FLAG_BORDERLESS_WINDOWED_MODE)
{
+ MONITORINFO info = { 0 };
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
- MONITORINFO info;
info.cbSize = sizeof(info);
- if (!GetMonitorInfoW(monitor, &info))
- {
- LogFail("GetMonitorInfo", GetLastError());
- abort();
- }
+ if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError());
- RECT windowRect;
- if (!GetWindowRect(hwnd, &windowRect))
- {
- LogFail("GetWindowRect", GetLastError());
- abort();
- }
+ RECT windowRect = { 0 };
+ if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowRect", GetLastError());
- if (
- (windowRect.left == info.rcMonitor.left) &&
+ if ((windowRect.left == info.rcMonitor.left) &&
(windowRect.top == info.rcMonitor.top) &&
((windowRect.right - windowRect.left) == (info.rcMonitor.right - info.rcMonitor.left)) &&
- ((windowRect.bottom - windowRect.top) == (info.rcMonitor.bottom - info.rcMonitor.top))
- ) return false;
+ ((windowRect.bottom - windowRect.top) == (info.rcMonitor.bottom - info.rcMonitor.top))) return false;
- if (!SetWindowPos(
- hwnd,
- HWND_TOP,
+ if (!SetWindowPos(hwnd, HWND_TOP,
info.rcMonitor.left, info.rcMonitor.top,
info.rcMonitor.right - info.rcMonitor.left,
info.rcMonitor.bottom - info.rcMonitor.top,
- SWP_NOOWNERZORDER
- )) {
- LogFail("SetWindowPos", GetLastError());
- abort();
+ SWP_NOOWNERZORDER))
+ {
+ TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError());
}
return true;
}
- UINT dpi = GetWindowDpi(hwnd);
- float dpiScale = ScaleFromDpi(dpi);
+ UINT dpi = GetDpiForWindow(hwnd);
+ float dpiScale = ((float)dpi)/96.0f;
bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI;
- SIZE desired = PxFromPt2(dpiScale, dpiScaling, appScreenSize);
+ SIZE desired = PxFromPt2(dpiScale, dpiScaling, width, height);
SIZE actual = GetClientSize(hwnd);
- if (actual.cx == desired.cx || actual.cy == desired.cy)
- return false;
+ if ((actual.cx == desired.cx) || (actual.cy == desired.cy)) return false;
+
+ 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);
- TRACELOG(
- LOG_INFO,
- "restoring client size from %dx%d to %dx%d (dpi=%lu dpiScaling=%d app=%fx%f)",
- actual.cx, actual.cy,
- desired.cx, desired.cy,
- dpi, dpiScaling,
- appScreenSize.x, appScreenSize.y
- );
SIZE windowSize = CalcWindowSize(dpi, desired, MakeWindowStyle(flags));
POINT windowPos = (POINT){ 0, 0 };
UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED;
if (kind == UPDATE_WINDOW_FIRST)
{
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
- if (!monitor)
- {
- LogFail("MonitorFromWindow", GetLastError());
- abort();
- }
+ if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError());
MONITORINFO info;
info.cbSize = sizeof(info);
- if (!GetMonitorInfoW(monitor, &info))
- {
- LogFail("GetMonitorInfo", GetLastError());
- abort();
- }
+ if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError());
LONG monitorWidth = info.rcMonitor.right - info.rcMonitor.left;
LONG monitorHeight = info.rcMonitor.bottom - info.rcMonitor.top;
@@ -461,54 +439,26 @@ static bool UpdateWindowSize(
MAX(0, (monitorWidth - windowSize.cx)/2),
MAX(0, (monitorHeight - windowSize.cy)/2),
};
- } else {
- swpFlags |= SWP_NOMOVE;
}
+ else swpFlags |= SWP_NOMOVE;
- if (!SetWindowPos(
- hwnd, NULL,
- windowPos.x, windowPos.y,
- windowSize.cx, windowSize.cy,
- swpFlags
- )) {
- LogFail("SetWindowPos", GetLastError());
- abort();
+ if (!SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, windowSize.cx, windowSize.cy, swpFlags))
+ {
+ TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError());
}
return true;
}
-#define CLASS_NAME L"RaylibWindow"
-
-static void CreateWindowAlloca(const char *title, DWORD style)
-{
- WCHAR *titleWide;
- A_TO_W_ALLOCA(titleWide, title);
- CreateWindowExW(
- WINDOW_STYLE_EX,
- CLASS_NAME,
- titleWide,
- style,
- 0, 0,
- 0, 0,
- NULL,
- NULL,
- GetModuleHandleW(NULL),
- NULL
- );
-}
-
static BOOL IsWindows10Version1703OrGreaterWin32(void)
{
HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
- DWORD (*Verify)(
- RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG
- ) = (DWORD (*)(
- RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG
- ))GetProcAddress(ntdll, "RtlVerifyVersionInfo");
+
+ DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) =
+ (DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo");
if (!Verify)
{
- LogFail("GetProcAddress 'RtlVerifyVersionInfo'", GetLastError());
+ TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetProcAddress 'RtlVerifyVersionInfo'", GetLastError());
return 0;
}
@@ -521,42 +471,32 @@ 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);
}
-static void *FindProc(const char *name)
+// Get OpenGL function pointers
+static void *WglGetProcAddress(const char *procname)
{
+ void *proc = (void *)wglGetProcAddress(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 *)0x3) ||
+ (proc == (void *)-1))
{
- void *proc = wglGetProcAddress(name);
- if (proc) return proc;
+ // TODO: Keep gl module pointer as global platform data?
+ HMODULE glModule = LoadLibraryW(L"opengl32.dll");
+ proc = (void *)GetProcAddress(glModule, procname);
+
+ //if (proc == NULL) TRACELOG(LOG_ERROR, "GL: GetProcAddress() failed to get %s [%p], error=%u", procname, proc, GetLastError());
+ //else TRACELOG(LOG_INFO, "GL: Found entry point for %s [%p]", procname, proc);
}
- static HMODULE opengl = NULL;
- if (!opengl)
- {
- opengl = LoadLibraryW(L"opengl32");
- }
- if (opengl)
- {
- void *proc = GetProcAddress(opengl, name);
- if (proc) return proc;
- }
-
- return NULL;
-}
-static void *WglGetProcAddress(const char *name)
-{
- void *result = FindProc(name);
- if (result)
- {
- //TRACELOG(LOG_DEBUG, "GetProcAddress '%s' > %p", name, result);
- }
- else
- {
- TRACELOG(LOG_ERROR, "GetProcAddress '%s' > %p failed, error=%u", name, result, GetLastError());
- }
-
- return result;
+ return proc;
}
static KeyboardKey KeyFromWparam(WPARAM wparam)
@@ -744,40 +684,36 @@ static KeyboardKey KeyFromWparam(WPARAM wparam)
static LPCWSTR GetCursorName(int cursor)
{
+ LPCWSTR name = (LPCWSTR)IDC_ARROW;
+
switch (cursor)
{
- case MOUSE_CURSOR_DEFAULT : return (LPCWSTR)IDC_ARROW;
- case MOUSE_CURSOR_ARROW : return (LPCWSTR)IDC_ARROW;
- case MOUSE_CURSOR_IBEAM : return (LPCWSTR)IDC_IBEAM;
- case MOUSE_CURSOR_CROSSHAIR : return (LPCWSTR)IDC_CROSS;
- case MOUSE_CURSOR_POINTING_HAND: return (LPCWSTR)IDC_HAND;
- case MOUSE_CURSOR_RESIZE_EW : return (LPCWSTR)IDC_SIZEWE;
- case MOUSE_CURSOR_RESIZE_NS : return (LPCWSTR)IDC_SIZENS;
- case MOUSE_CURSOR_RESIZE_NWSE : return (LPCWSTR)IDC_SIZENWSE;
- case MOUSE_CURSOR_RESIZE_NESW : return (LPCWSTR)IDC_SIZENESW;
- case MOUSE_CURSOR_RESIZE_ALL : return (LPCWSTR)IDC_SIZEALL;
- case MOUSE_CURSOR_NOT_ALLOWED : return (LPCWSTR)IDC_NO;
- default: abort();
+ case MOUSE_CURSOR_DEFAULT: name = (LPCWSTR)IDC_ARROW; break;
+ case MOUSE_CURSOR_ARROW: name = (LPCWSTR)IDC_ARROW; break;
+ case MOUSE_CURSOR_IBEAM: name = (LPCWSTR)IDC_IBEAM; break;
+ case MOUSE_CURSOR_CROSSHAIR: name = (LPCWSTR)IDC_CROSS; break;
+ case MOUSE_CURSOR_POINTING_HAND: name = (LPCWSTR)IDC_HAND; break;
+ case MOUSE_CURSOR_RESIZE_EW: name = (LPCWSTR)IDC_SIZEWE; break;
+ case MOUSE_CURSOR_RESIZE_NS: name = (LPCWSTR)IDC_SIZENS; break;
+ case MOUSE_CURSOR_RESIZE_NWSE: name = (LPCWSTR)IDC_SIZENWSE; break;
+ case MOUSE_CURSOR_RESIZE_NESW: name = (LPCWSTR)IDC_SIZENESW; break;
+ case MOUSE_CURSOR_RESIZE_ALL: name = (LPCWSTR)IDC_SIZEALL; break;
+ case MOUSE_CURSOR_NOT_ALLOWED: name = (LPCWSTR)IDC_NO; break;
+ default: break;
}
-}
+ return name;
+}
static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam)
{
- int *count = (int*)lparam;
+ int *count = (int *)lparam;
*count += 1;
- // always return TRUE to continue the loop, otherwise, the caller
+ // Always return TRUE to continue the loop, otherwise, the caller
// can't distinguish between stopping the loop and an error
return TRUE;
}
-typedef struct {
- HMONITOR needle;
- int index;
- int matchIndex;
- RECT rect;
-} FindMonitorContext;
-
static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam)
{
FindMonitorContext *c = (FindMonitorContext*)lparam;
@@ -788,96 +724,48 @@ static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM
}
c->index += 1;
- // always return TRUE to continue the loop, otherwise, the caller
+ // Always return TRUE to continue the loop, otherwise, the caller
// can't distinguish between stopping the loop and an error
return TRUE;
}
-typedef struct {
- DWORD set;
- DWORD clear;
-} FlagsOp;
-
-static void GetStyleChangeFlagOps(
- DWORD coreWindowFlags,
- STYLESTRUCT *ss,
- FlagsOp *deferredFlags
-) {
+static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, 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);
+ if (resizableOld != resizableNew)
{
- bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE);
- bool resizableOld = ResizableFromStyle(ss->styleOld);
- bool resizableNew = ResizableFromStyle(ss->styleNew);
- if (resizable != resizableOld)
- {
- TRACELOG(LOG_ERROR, "expected resizable %u but got %u", resizable, resizableOld);
- abort();
- }
-
- if (resizableOld != resizableNew)
- {
- //TRACELOG(LOG_INFO, "resizable = %u", resizableNew);
- if (resizableNew)
- {
- deferredFlags->set |= FLAG_WINDOW_RESIZABLE;
- }
- else
- {
- deferredFlags->clear |= FLAG_WINDOW_RESIZABLE;
- }
- }
+ if (resizableNew) deferredFlags->set |= FLAG_WINDOW_RESIZABLE;
+ else deferredFlags->clear |= FLAG_WINDOW_RESIZABLE;
}
+ // 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);
+ if (decoratedOld != decoratedNew)
{
- 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);
- abort();
- }
-
- if (decoratedOld != decoratedNew)
- {
- //TRACELOG(LOG_INFO, "decorated = %u", decoratedNew);
- if (decoratedNew)
- {
- deferredFlags->clear |= FLAG_WINDOW_UNDECORATED;
- }
- else
- {
- deferredFlags->set |= FLAG_WINDOW_UNDECORATED;
- }
- }
+ if (decoratedNew) deferredFlags->clear |= FLAG_WINDOW_UNDECORATED;
+ else deferredFlags->set |= FLAG_WINDOW_UNDECORATED;
}
+ // 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);
+ if (hiddenOld != hiddenNew)
{
- bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN);
- bool hiddenOld = HiddenFromStyle(ss->styleOld);
- bool hiddenNew = HiddenFromStyle(ss->styleNew);
- if (hidden != hiddenOld)
- {
- TRACELOG(LOG_ERROR, "expected hidden %u but got %u", hidden, hiddenOld);
- abort();
- }
-
- if (hiddenOld != hiddenNew)
- {
- TRACELOG(LOG_INFO, "hidden = %u", hiddenNew);
- if (hiddenNew)
- {
- deferredFlags->set |= FLAG_WINDOW_HIDDEN;
- }
- else
- {
- deferredFlags->clear |= FLAG_WINDOW_HIDDEN;
- }
- }
+ if (hiddenNew) deferredFlags->set |= FLAG_WINDOW_HIDDEN;
+ else deferredFlags->clear |= FLAG_WINDOW_HIDDEN;
}
}
-// call when the window is rezised, returns true if the new window size
-// should update the desired app size
+// 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;
@@ -885,297 +773,58 @@ 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;
}
-// --------------------------------------------------------------------------------
-// Here's the end of the "pure function section", the rest of the file can access
-// global state.
-// --------------------------------------------------------------------------------
+// ---------------------------------------------------------------------------------------------
+// Here's the end of the "pure function section", the rest of the file can access global state
+// ---------------------------------------------------------------------------------------------
-// unlock the ability to use CORE in the rest of the file
+// Unlock the ability to use CORE in the rest of the file
#undef CORE
-// 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.
-static Vector2 globalAppScreenSize = (Vector2){0, 0};
-static unsigned globalDesiredFlags = 0;
-static HWND globalHwnd = NULL;
-static HDC globalHdc = NULL;
-static HGLRC globalGlContext = NULL;
-static LARGE_INTEGER globalTimerFrequency;
-static bool globalCursorEnabled = true;
+//----------------------------------------------------------------------------------
+// Module Internal Functions Declaration
+//----------------------------------------------------------------------------------
+int InitPlatform(void); // Initialize platform (graphics, inputs and more)
+void ClosePlatform(void); // Close platform
-static void HandleKey(WPARAM wparam, LPARAM lparam, char state)
-{
- KeyboardKey key = KeyFromWparam(wparam);
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- /* BYTE scancode = lparam >> 16; */
- /* TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state); */
- if (key != KEY_NULL)
- {
- /* TRACELOG(LOG_INFO, "KEY[%d] = %d (old=%d)\n", key, state, CORE.Input.Keyboard.currentKeyState[key]); */
- CORE.Input.Keyboard.currentKeyState[key] = state;
- if (key == KEY_ESCAPE && state == 1)
- {
- CORE.Window.shouldClose = 1;
- }
- }
- else
- {
- TRACELOG(LOG_WARNING, "unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam);
- }
+// Win32 process messages management function
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
- // TODO: add it to the queue as well?
-}
-static void HandleMouseButton(int button, char state)
-{
- CORE.Input.Mouse.currentButtonState[button] = state;
- CORE.Input.Touch.currentTouchState[button] = state;
-}
+// Win32: Handle inputs functions
+static void HandleKey(WPARAM wparam, LPARAM lparam, char state);
+static void HandleMouseButton(int button, char state);
+static void HandleRawInput(LPARAM lparam);
+static void HandleWindowResize(HWND hwnd, int *width, int *height);
-static void HandleRawInput(LPARAM lparam)
-{
- RAWINPUT input;
+static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags);
+static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags);
+static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height); // Update window flags
- {
- UINT inputSize = sizeof(input);
- UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER));
- if (size == (UINT)-1) {
- LogFail("GetRawInputData", GetLastError());
- abort();
- }
- if (size != inputSize) abort(); // bug?
- }
+//----------------------------------------------------------------------------------
+// Module Functions Declaration
+//----------------------------------------------------------------------------------
+// NOTE: Functions declaration is provided by raylib.h
- if (input.header.dwType != RIM_TYPEMOUSE)
- {
- TRACELOG(LOG_ERROR, "Unexpected WM_INPUT type %lu", input.header.dwType);
- abort();
- }
-
- if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
- {
- TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!");
- abort();
- }
-
- if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP)
- {
- TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!");
- abort();
- }
-
- // bit of a trick, we keep the mouse position at 0,0 and instead move
- // the previous position so we can still get a proper mouse delta
- CORE.Input.Mouse.previousPosition.x -= input.data.mouse.lLastX;
- CORE.Input.Mouse.previousPosition.y -= input.data.mouse.lLastY;
- if (CORE.Input.Mouse.currentPosition.x != 0) abort();
- if (CORE.Input.Mouse.currentPosition.y != 0) abort();
-}
-
-static void HandleWindowResize(HWND hwnd, Vector2 *appScreenSizeRef)
-{
- if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED)
- return;
-
- SIZE clientSize = GetClientSize(hwnd);
-
- // TODO: not sure if this function is doing what we need, leaving this disabled
- // call to workaround unused function error
- if (0) SetupFramebuffer(0, 0);
-
- TRACELOG(LOG_DEBUG, "NewClientSize %lux%lu", clientSize.cx, clientSize.cy);
- /* CORE.Window.currentFbo.width = clientSize.cx; */
- /* CORE.Window.currentFbo.height = clientSize.cy; */
- /* glViewport(0, 0, clientSize.cx, clientSize.cy); */
- SetupViewport(clientSize.cx, clientSize.cy);
- CORE.Window.resizedLastFrame = true;
- float dpiScale = ScaleFromDpi(GetWindowDpi(hwnd));
- bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI);
- Vector2 screenSize = (Vector2){
- PtFromPx(dpiScale, highdpi, clientSize.cx),
- PtFromPx(dpiScale, highdpi, clientSize.cy),
- };
- CORE.Window.screen.width = roundf(screenSize.x);
- CORE.Window.screen.height = roundf(screenSize.y);
- if (AdoptWindowResize(CORE.Window.flags))
- {
- TRACELOG(
- LOG_DEBUG,
- "updating app size to %fx%f from window resize",
- screenSize.x, screenSize.y
- );
- *appScreenSizeRef = screenSize;
- }
-
- CORE.Window.screenScale = MatrixScale(
- (float)CORE.Window.render.width/CORE.Window.screen.width,
- (float)CORE.Window.render.height/CORE.Window.screen.height,
- 1.0f
- );
-}
-
-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);
- 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)",
- previous,
- current,
- previous ^ current,
- GetLastError()
- );
- abort();
- }
-
- CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE);
- }
- }
-
- Mized currentMized = MizedFromStyle(MakeWindowStyle(CORE.Window.flags));
- Mized desiredMized = MizedFromStyle(MakeWindowStyle(desiredFlags));
- if (currentMized != desiredMized)
- {
- switch (desiredMized)
- {
- case MIZED_NONE: ShowWindow(hwnd, SW_RESTORE); break;
- case MIZED_MIN: ShowWindow(hwnd, SW_MINIMIZE); break;
- case MIZED_MAX: ShowWindow(hwnd, SW_MAXIMIZE); break;
- }
- }
-}
-
-typedef enum {
- SANITIZE_FLAGS_FIRST,
- SANITIZE_FLAGS_NORMAL,
-} SanitizeFlagsKind;
-
-static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags)
-{
- if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE))
- {
- TRACELOG(LOG_INFO, "borderless windows mode is overriding maximized");
- flags &= ~FLAG_WINDOW_MAXIMIZED;
- }
-
- switch (kind)
- {
- 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;
- }
-
- return flags;
-}
-
-#define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT)
-#define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL)
-
-// flags that have no operations to perform during an update
-#define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT)
-
-
-// All window state changes from raylib flags go through this function. It performs
-// whatever operations are needed to update the window state to match the desired flags.
-// In most cases this function should not update CORE.Window.flags directly, instead,
-// the window itself should update CORE.Window.flags in response to actual state changes.
-// This means that CORE.Window.flags should always represent the actual state of the
-// window. This function will continue to perform these update operations so long as
-// the state continues to change.
-//
-// This design takes care of many odd corner cases. For example, if you want to restore
-// a window that was previously maximized AND minimized and you want to remove both these
-// flags, you actually need to call ShowWindow with SW_RESTORE twice. Another example is
-// if you have a maximized window, if the undecorated flag is modified then we'd need to
-// update the window style, but updating the style would mean the window size would change
-// causing the window to lose its Maximized state which would mean we'd need to update the
-// window size and then update the window style a second time to restore that maximized
-// state. This implementation is able to handle any/all of these special situations with a
-// retry loop that continues until we either reach the desired state or the state stops
-// changing.
-static void UpdateFlags(HWND hwnd, unsigned desiredFlags, Vector2 appScreenSize)
-{
- // flags that just apply immediately without needing any operations
- CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE);
-
- {
- int vsync = (CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0;
- PFNWGLSWAPINTERVALEXTPROC f = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
- if (f)
- {
- (*f)(vsync);
- if (vsync)
- {
- CORE.Window.flags |= FLAG_VSYNC_HINT;
- }
- else
- {
- CORE.Window.flags &= ~FLAG_VSYNC_HINT;
- }
- }
- }
-
- DWORD previousStyle;
- for (unsigned attempt = 1; ; attempt++)
- {
- CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL);
-
- bool windowSizeUpdated = false;
- if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags))
- {
- windowSizeUpdated = UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, appScreenSize, desiredFlags);
- if ((FLAG_MASK_REQUIRED & desiredFlags) == (FLAG_MASK_REQUIRED & CORE.Window.flags))
- break;
- }
-
- if (
- (attempt > 1) &&
- (previousStyle == MakeWindowStyle(CORE.Window.flags)) &&
- !windowSizeUpdated
- ) {
- TRACELOG(
- LOG_ERROR,
- // TODO: print more information
- "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
- );
- abort();
- }
-
- previousStyle = MakeWindowStyle(CORE.Window.flags);
- UpdateWindowStyle(hwnd, desiredFlags);
- }
-}
+//----------------------------------------------------------------------------------
+// Module Functions Definition: Window and Graphics Device
+//----------------------------------------------------------------------------------
+// Check if application should close
bool WindowShouldClose(void)
{
return CORE.Window.shouldClose;
}
+// Toggle fullscreen mode
void ToggleFullscreen(void)
{
TRACELOG(LOG_WARNING, "ToggleFullscreen not implemented");
- assert(0); // crash debug builds only
}
+// Toggle borderless windowed mode
void ToggleBorderlessWindowed(void)
{
if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE)
@@ -1188,11 +837,13 @@ void ToggleBorderlessWindowed(void)
}
}
+// Set window state: maximized, if resizable
void MaximizeWindow(void)
{
SetWindowState(FLAG_WINDOW_MAXIMIZED);
}
+// Set window state: minimized
void MinimizeWindow(void)
{
SetWindowState(FLAG_WINDOW_MINIMIZED);
@@ -1215,62 +866,57 @@ void RestoreWindow(void)
// Set window configuration state using flags
void SetWindowState(unsigned int flags)
{
- globalDesiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags | flags);
- UpdateFlags(globalHwnd, globalDesiredFlags, globalAppScreenSize);
+ platform.desiredFlags = SanitizeFlags(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)
{
- globalDesiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags & ~flags);
- UpdateFlags(globalHwnd, globalDesiredFlags, globalAppScreenSize);
+ platform.desiredFlags = SanitizeFlags(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");
- assert(0); // crash debug builds only
}
// Set icon for window
void SetWindowIcons(Image *images, int count)
{
TRACELOG(LOG_WARNING, "SetWindowIcons not implemented");
- assert(0); // crash debug builds only
}
void SetWindowTitle(const char *title)
{
CORE.Window.title = title;
- WCHAR *titlew;
- A_TO_W_ALLOCA(titlew, CORE.Window.title);
- if (!SetWindowTextW(globalHwnd, titlew))
- {
- LogFail("SetWindowText", GetLastError());
- abort();
- }
+
+ WCHAR *titleWide = NULL;
+ 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());
}
// Set window position on screen (windowed mode)
void SetWindowPosition(int x, int y)
{
TRACELOG(LOG_WARNING, "SetWindowPosition not implemented");
- assert(0); // crash debug builds only
}
// Set monitor for the current window
void SetWindowMonitor(int monitor)
{
TRACELOG(LOG_WARNING, "SetWindowMonitor not implemented");
- assert(0); // crash debug builds only
}
// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
void SetWindowMinSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowMinSize not implemented");
- assert(0); // crash debug builds only
+
CORE.Window.screenMin.width = width;
CORE.Window.screenMin.height = height;
}
@@ -1279,7 +925,7 @@ void SetWindowMinSize(int width, int height)
void SetWindowMaxSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowMaxSize not implemented");
- assert(0); // crash debug builds only
+
CORE.Window.screenMax.width = width;
CORE.Window.screenMax.height = height;
}
@@ -1288,36 +934,32 @@ void SetWindowMaxSize(int width, int height)
void SetWindowSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowSize not implemented");
- assert(0); // crash debug builds only
}
// Set window opacity, value opacity is between 0.0 and 1.0
void SetWindowOpacity(float opacity)
{
TRACELOG(LOG_WARNING, "SetWindowOpacity not implemented");
- assert(0); // crash debug builds only
}
+// Set window focused
void SetWindowFocused(void)
{
TRACELOG(LOG_WARNING, "SetWindowFocused not implemented");
- assert(0); // crash debug builds only
}
// Get native window handle
void *GetWindowHandle(void)
{
- return globalHwnd;
+ return platform.hwnd;
}
int GetMonitorCount(void)
{
int count = 0;
- if (!EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count))
- {
- LogFail("EnumDisplayMonitors", GetLastError());
- abort();
- }
+
+ int result = EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count);
+ if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError());
return count;
}
@@ -1325,22 +967,16 @@ int GetMonitorCount(void)
// Get current monitor where window is placed
int GetCurrentMonitor(void)
{
- HMONITOR monitor = MonitorFromWindow(globalHwnd, MONITOR_DEFAULTTOPRIMARY);
- if (!monitor)
- {
- LogFail("MonitorFromWindow", GetLastError());
- abort();
- }
+ 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;
- if (!EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&context))
- {
- LogFail("EnumDisplayMonitors", GetLastError());
- abort();
- }
+
+ int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&context);
+ if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError());
return context.matchIndex;
}
@@ -1349,7 +985,6 @@ int GetCurrentMonitor(void)
Vector2 GetMonitorPosition(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPosition not implemented");
- assert(0); // crash debug builds only
return (Vector2){ 0, 0 };
}
@@ -1357,7 +992,6 @@ Vector2 GetMonitorPosition(int monitor)
int GetMonitorWidth(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorWidth not implemented");
- assert(0); // crash debug builds only
return 0;
}
@@ -1365,7 +999,6 @@ int GetMonitorWidth(int monitor)
int GetMonitorHeight(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorHeight not implemented");
- assert(0); // crash debug builds only
return 0;
}
@@ -1373,7 +1006,6 @@ int GetMonitorHeight(int monitor)
int GetMonitorPhysicalWidth(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth not implemented");
- assert(0); // crash debug builds only
return 0;
}
@@ -1381,7 +1013,6 @@ int GetMonitorPhysicalWidth(int monitor)
int GetMonitorPhysicalHeight(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight not implemented");
- assert(0); // crash debug builds only
return 0;
}
@@ -1389,7 +1020,6 @@ int GetMonitorPhysicalHeight(int monitor)
int GetMonitorRefreshRate(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorRefreshRate not implemented");
- assert(0); // crash debug builds only
return 0;
}
@@ -1397,7 +1027,6 @@ int GetMonitorRefreshRate(int monitor)
const char *GetMonitorName(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorName not implemented");
- assert(0); // crash debug builds only
return 0;
}
@@ -1405,35 +1034,36 @@ const char *GetMonitorName(int monitor)
Vector2 GetWindowPosition(void)
{
TRACELOG(LOG_WARNING, "GetWindowPosition not implemented");
- assert(0); // crash debug builds only
return (Vector2){ 0, 0 };
}
// Get window scale DPI factor for current monitor
Vector2 GetWindowScaleDPI(void)
{
- float scale = ScaleFromDpi(GetWindowDpi(globalHwnd));
+ float scale = ((float)GetDpiForWindow(platform.hwnd))/96.0f;
return (Vector2){ scale, scale };
}
+// Set clipboard text content
void SetClipboardText(const char *text)
{
TRACELOG(LOG_WARNING, "SetClipboardText not implemented");
- assert(0); // crash debug builds only
}
+// Get clipboard text content
const char *GetClipboardText(void)
{
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
- assert(0); // crash debug builds only
return NULL;
}
+// Get clipboard image
Image GetClipboardImage(void)
{
- TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
- assert(0); // crash debug builds only
Image image = { 0 };
+
+ TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
+
return image;
}
@@ -1448,7 +1078,7 @@ void ShowCursor(void)
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.
+ // to only hide the cursor while it's inside the client area
CORE.Input.Mouse.cursorHidden = true;
SetCursor(NULL);
}
@@ -1456,32 +1086,21 @@ void HideCursor(void)
// Enables cursor (unlock cursor)
void EnableCursor(void)
{
- if (globalCursorEnabled)
- {
- TRACELOG(LOG_INFO, "EnableCursor: already enabled");
- }
+ if (platform.cursorEnabled) TRACELOG(LOG_INFO, "EnableCursor: already enabled");
else
{
- if (!ClipCursor(NULL))
- {
- LogFail("ClipCursor", GetLastError());
- abort();
- }
+ if (!ClipCursor(NULL)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClipCursor", GetLastError());
- {
- RAWINPUTDEVICE rid;
- rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
- rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
- rid.dwFlags = RIDEV_REMOVE; // Add to this window even in background
- rid.hwndTarget = NULL;
- if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
- LogFail("RegisterRawInputDevices", GetLastError());
- abort();
- }
- }
+ RAWINPUTDEVICE rid = { 0 };
+ rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
+ rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
+ 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());
ShowCursor();
- globalCursorEnabled = true;
+ platform.cursorEnabled = true;
TRACELOG(LOG_INFO, "EnableCursor: enabled");
}
}
@@ -1489,95 +1108,67 @@ void EnableCursor(void)
// Disables cursor (lock cursor)
void DisableCursor(void)
{
- if (globalCursorEnabled)
+ if (platform.cursorEnabled)
{
+ RAWINPUTDEVICE rid = { 0 };
+ rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
+ rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
+ 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());
- {
- RAWINPUTDEVICE rid;
- rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
- rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
- rid.dwFlags = RIDEV_INPUTSINK; // Add to this window even in background
- rid.hwndTarget = globalHwnd;
- if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) {
- LogFail("RegisterRawInputDevices", GetLastError());
- abort();
- }
- }
-
- RECT clientRect;
- if (!GetClientRect(globalHwnd, &clientRect))
- {
- LogFail("GetClientRect", GetLastError());
- abort();
- }
+ RECT clientRect = { 0 };
+ if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetClientRect", GetLastError());
POINT topleft = { clientRect.left, clientRect.top };
- if (!ClientToScreen(globalHwnd, &topleft))
- {
- LogFail("ClientToScreen", GetLastError());
- abort();
- }
+ if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClientToScreen", 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)",
- clientRect.left,
- clientRect.top,
- clientRect.right,
- clientRect.bottom,
- topleft.x,
- topleft.y
- );
+ 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))
- {
- LogFail("ClipCursor", GetLastError());
- abort();
- }
+ if (!ClipCursor(&clipRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClipCursor", GetLastError());
CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 };
CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 };
HideCursor();
- globalCursorEnabled = false;
+ platform.cursorEnabled = false;
TRACELOG(LOG_INFO, "DisableCursor: disabled");
}
- else
- {
- TRACELOG(LOG_INFO, "DisableCursor: already disabled");
- }
+ else TRACELOG(LOG_INFO, "DisableCursor: already disabled");
}
+// Swap back buffer with front buffer (screen drawing)
void SwapScreenBuffer(void)
{
- if (!globalHdc) abort();
- if (!SwapBuffers(globalHdc))
- {
- LogFail("SwapBuffers", GetLastError());
- abort();
- }
-
- if (!ValidateRect(globalHwnd, NULL))
- {
- LogFail("ValidateRect", GetLastError());
- abort();
- }
+ if (!platform.hdc) abort();
+ 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());
}
+//----------------------------------------------------------------------------------
+// Module Functions Definition: Misc
+//----------------------------------------------------------------------------------
+
+// Get elapsed time measure in seconds
double GetTime(void)
{
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
- return (double)(now.QuadPart - CORE.Time.base)/(double)globalTimerFrequency.QuadPart;
+ return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart;
}
// Open URL with default system browser (if available)
-// NOTE: This function is only safe to use if you control the URL given.
-// A user could craft a malicious string performing another action.
-// Only call this function yourself not with user input or make sure to check the string yourself.
+// NOTE: This function is only safe to use if you control the URL given
+// A user could craft a malicious string performing another action
+// Only call this function yourself not with user input or make sure to check the string yourself
// Ref: https://github.com/raysan5/raylib/issues/686
void OpenURL(const char *url)
{
@@ -1586,7 +1177,6 @@ void OpenURL(const char *url)
else
{
TRACELOG(LOG_WARNING, "OpenURL not implemented");
- assert(0); // crash debug builds only
}
}
@@ -1594,53 +1184,47 @@ void OpenURL(const char *url)
// Module Functions Definition: Inputs
//----------------------------------------------------------------------------------
+// Set internal gamepad mappings
int SetGamepadMappings(const char *mappings)
{
TRACELOG(LOG_WARNING, "SetGamepadMappings not implemented");
- assert(0); // crash debug builds only
+
return -1;
}
+// Set gamepad vibration
void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
{
TRACELOG(LOG_WARNING, "SetGamepadVibration not implemented");
- assert(0); // crash debug builds only
}
+// Set mouse position XY
void SetMousePosition(int x, int y)
{
- if (globalCursorEnabled)
+ if (platform.cursorEnabled)
{
CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
TRACELOG(LOG_WARNING, "SetMousePosition not implemented");
- assert(0); // crash debug builds only
- }
- else
- {
- TRACELOG(LOG_ERROR, "Possible? Should we allow this?");
- abort();
}
+ else TRACELOG(LOG_WARNING, "INPUT: MOUSE: Cursor not enabled");
}
+// Set mouse cursor
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());
- abort();
- }
+ if (!hcursor) TRACELOG(LOG_ERROR, "LoadCursor %d (win32 %d) failed, error=%lu", cursor, (size_t)cursorName, GetLastError());
SetCursor(hcursor);
CORE.Input.Mouse.cursorHidden = false;
}
+// Get physical key name
const char *GetKeyName(int key)
{
TRACELOG(LOG_WARNING, "GetKeyName not implemented");
- assert(0); // crash debug builds only
return NULL;
}
@@ -1666,11 +1250,7 @@ void PollInputEvents(void)
// so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed!
//for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 };
- memcpy(
- CORE.Input.Keyboard.previousKeyState,
- CORE.Input.Keyboard.currentKeyState,
- sizeof(CORE.Input.Keyboard.previousKeyState)
- );
+ memcpy(CORE.Input.Keyboard.previousKeyState, CORE.Input.Keyboard.currentKeyState, sizeof(CORE.Input.Keyboard.previousKeyState));
memset(CORE.Input.Keyboard.keyRepeatInFrame, 0, sizeof(CORE.Input.Keyboard.keyRepeatInFrame));
// Register previous mouse wheel state
@@ -1680,11 +1260,11 @@ void PollInputEvents(void)
// Register previous mouse position
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
- MSG msg;
+ // Process windows messages
+ MSG msg = { 0 };
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
- if (msg.message == WM_PAINT)
- return;
+ if (msg.message == WM_PAINT) return;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
@@ -1694,457 +1274,197 @@ void PollInputEvents(void)
// Module Internal Functions Definition
//----------------------------------------------------------------------------------
-#define WM_APP_UPDATE_WINDOW_SIZE (WM_APP + 1)
-
-static LRESULT WndProc2(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, FlagsOp *deferredFlags)
+// Initialize modern OpenGL context
+// NOTE: We need to create a dummy context first to query requried extensions
+HGLRC InitOpenGL(HWND hwnd, HDC hdc)
{
- switch (msg)
+ // First, create a dummy context to get WGL extensions
+ PIXELFORMATDESCRIPTOR pixelFormatDesc = {
+ .nSize = sizeof(PIXELFORMATDESCRIPTOR),
+ .nVersion = 1,
+ .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
+ .iPixelType = PFD_TYPE_RGBA,
+ .cColorBits = 32,
+ .cAlphaBits = 8,
+ .cDepthBits = 24,
+ .iLayerType = PFD_MAIN_PLANE
+ };
+
+ int pixelFormat = ChoosePixelFormat(hdc, &pixelFormatDesc);
+ SetPixelFormat(hdc, pixelFormat, &pixelFormatDesc);
+ //int pixelFormat = ChoosePixelFormat(platform.hdc, &pixelFormatDesc);
+ //if (!pixelFormat) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ChoosePixelFormat", GetLastError()); return -1; }
+ //if (!SetPixelFormat(platform.hdc, pixelFormat, &pixelFormatDesc)) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetPixelFormat", GetLastError()); return -1; }
+
+ HGLRC tempContext = wglCreateContext(hdc);
+ //if (!tempContext) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglCreateContext", GetLastError()); return -1; }
+ BOOL result = wglMakeCurrent(hdc, tempContext);
+ //if (!result) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglMakeCurrent", GetLastError()); return -1; }
+
+ // Load WGL extension entry points
+ wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
+ wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
+ wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
+
+ // Setup modern pixel format if extension is available
+ if (wglChoosePixelFormatARB)
{
- case WM_CREATE:
+ int pixelFormatAttribs[] = {
+ WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
+ WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
+ WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
+ WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
+ WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
+ WGL_COLOR_BITS_ARB, 32,
+ //WGL_RED_BITS_ARB, 8,
+ //WGL_GREEN_BITS_ARB, 8,
+ //WGL_BLUE_BITS_ARB, 8,
+ //WGL_ALPHA_BITS_ARB, 8,
+ WGL_DEPTH_BITS_ARB, 24,
+ WGL_STENCIL_BITS_ARB, 8,
+ 0 // Terminator
+ };
+
+ int format = 0;
+ UINT numFormats = 0;
+ if (wglChoosePixelFormatARB(hdc, pixelFormatAttribs, NULL, 1, &format, &numFormats) && (numFormats > 0))
{
- globalHdc = GetDC(hwnd);
- if (!globalHdc)
- {
- LogFail("GetDC", GetLastError());
- return -1;
- }
-
- if (CORE.Window.flags & FLAG_MSAA_4X_HINT)
- {
- TRACELOG(LOG_ERROR, "TODO: implement FLAG_MSAA_4X_HINT");
- assert(0);
- }
-
- PIXELFORMATDESCRIPTOR pixelFormatDesc = {0};
- pixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
- pixelFormatDesc.nVersion = 1;
- pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
- pixelFormatDesc.iPixelType = PFD_TYPE_RGBA;
- pixelFormatDesc.cColorBits = 32;
- pixelFormatDesc.cAlphaBits = 8;
- pixelFormatDesc.cDepthBits = 24;
-
- int pixelFormat = ChoosePixelFormat(globalHdc, &pixelFormatDesc);
- if (!pixelFormat)
- {
- LogFail("ChoosePixelFormat", GetLastError());
- return -1;
- }
-
- if (!SetPixelFormat(globalHdc, pixelFormat, &pixelFormatDesc))
- {
- LogFail("SetPixelFormat", GetLastError());
- return -1;
- }
-
- globalGlContext = wglCreateContext(globalHdc);
- if (!globalGlContext)
- {
- LogFail("wglCreateContext", GetLastError());
- return -1;
- }
-
- if (!wglMakeCurrent(globalHdc, globalGlContext))
- {
- LogFail("wglMakeCurrent", GetLastError());
- return -1;
- }
-
- rlLoadExtensions(WglGetProcAddress);
-
- globalHwnd = hwnd;
-
- } return 0;
- case WM_DESTROY:
- wglMakeCurrent(globalHdc, NULL);
- if (globalGlContext)
- {
- if (!wglDeleteContext(globalGlContext)) abort();
- globalGlContext = NULL;
- }
-
- if (globalHdc)
- {
- if (!ReleaseDC(hwnd, globalHdc)) abort();
- globalHdc = NULL;
- }
-
- return 0;
- case WM_CLOSE:
- CORE.Window.shouldClose = true;
- return 0;
- case WM_KILLFOCUS:
- memset(CORE.Input.Keyboard.previousKeyState, 0, sizeof(CORE.Input.Keyboard.previousKeyState));
- memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState));
- return 0;
- case WM_SIZING:
- if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) {
- // TODO: enforce min/max size
- TRACELOG(LOG_WARNING, "TODO: enforce min/max size!");
- } else {
- TRACELOG(LOG_WARNING, "non-resizable window is being resized");
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- abort(); // TODO
- }
- return TRUE;
- case WM_STYLECHANGING:
- if (wparam == GWL_STYLE)
- {
- STYLESTRUCT *ss = (STYLESTRUCT*)lparam;
- GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags);
-
- UINT dpi = GetWindowDpi(hwnd);
- SIZE clientSize = GetClientSize(hwnd);
- 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
- );
- 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");
- deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED;
- }
- }
- }
- return 0;
- 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;
- }
-
- Mized mized = MIZED_NONE;
- if (IsMinimized2(hwnd))
- {
- mized = MIZED_MIN;
- }
- else
- {
- WINDOWPLACEMENT placement;
- placement.length = sizeof(placement);
- if (!GetWindowPlacement(hwnd, &placement))
- {
- LogFail("GetWindowPlacement", GetLastError());
- abort();
- }
-
- if (placement.showCmd == SW_SHOWMAXIMIZED) {
- mized = MIZED_MAX;
- }
- }
-
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- /* TRACELOG(LOG_INFO, "window pos=%d,%d size=%dx%d", pos->x, pos->y, pos->cx, pos->cy); */
-
- switch (mized)
- {
- case MIZED_NONE:
- {
- deferredFlags->clear |= (
- FLAG_WINDOW_MINIMIZED |
- FLAG_WINDOW_MAXIMIZED
- );
- HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
- MONITORINFO info;
- info.cbSize = sizeof(info);
- if (!GetMonitorInfoW(monitor, &info))
- {
- LogFail("GetMonitorInfo", GetLastError());
- abort();
- }
-
- 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;
- }
-
- } break;
- case MIZED_MIN:
- // !!! NOTE !!! Do not update the maximized/borderless
- // flags because when hwnd is minimized it temporarily overrides
- // the maximized state/flag which gets restored on SW_RESTORE
- deferredFlags->set |= FLAG_WINDOW_MINIMIZED;
- break;
- case MIZED_MAX:
- deferredFlags->clear |= FLAG_WINDOW_MINIMIZED;
- deferredFlags->set |= FLAG_WINDOW_MAXIMIZED;
- break;
- }
-
- return 0;
+ PIXELFORMATDESCRIPTOR newPixelFormatDescriptor = { 0 };
+ DescribePixelFormat(hdc, format, sizeof(newPixelFormatDescriptor), &newPixelFormatDescriptor);
+ SetPixelFormat(hdc, format, &newPixelFormatDescriptor);
}
- case WM_SIZE:
- // don't trust the docs, they say you won't get this message if you don't call DefWindowProc
- // in response to WM_WINDOWPOSCHANGED but looks like when a window is created you'll get this
- // message without getting WM_WINDOWPOSCHANGED.
- HandleWindowResize(hwnd, &globalAppScreenSize);
- return 0;
- case WM_WINDOWPOSCHANGED: {
- WINDOWPOS *pos = (WINDOWPOS*)lparam;
- if (!(pos->flags & SWP_NOSIZE))
- {
- HandleWindowResize(hwnd, &globalAppScreenSize);
- }
-
- return 0;
- }
- case WM_GETDPISCALEDSIZE:
- {
- SIZE *inoutSize = (SIZE*)lparam;
- UINT newDpi = wparam;
-
- // for any of these other cases, we might want to post a window
- // resize event after the dpi changes?
- if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return TRUE;
- if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) return TRUE;
- if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) return TRUE;
-
- float dpiScale = ScaleFromDpi(newDpi);
- bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI;
- SIZE desired = PxFromPt2(dpiScale, dpiScaling, globalAppScreenSize);
- inoutSize->cx = desired.cx;
- inoutSize->cy = desired.cy;
- } return TRUE;
- case WM_DPICHANGED:
- {
- 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
- )) {
- LogFail("SetWindowPos", GetLastError());
- abort();
- }
-
- } return 0;
- case WM_SETCURSOR:
- if (LOWORD(lparam) == HTCLIENT)
- {
- SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
- return 0;
- }
-
- return DefWindowProc(hwnd, msg, wparam, lparam);
- case WM_INPUT:
- if (globalCursorEnabled)
- {
- TRACELOG(LOG_ERROR, "Unexpected raw mouse input"); // impossible right?
- abort();
- }
-
- HandleRawInput(lparam);
- return 0;
- case WM_MOUSEMOVE:
- if (globalCursorEnabled)
- {
- CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam);
- CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam);
- CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
- }
-
- return 0;
- case WM_KEYDOWN: HandleKey(wparam, lparam, 1); return 0;
- case WM_KEYUP: HandleKey(wparam, lparam, 0); return 0;
- case WM_LBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_LEFT, 1); return 0;
- case WM_LBUTTONUP : HandleMouseButton(MOUSE_BUTTON_LEFT, 0); return 0;
- case WM_RBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_RIGHT, 1); return 0;
- case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); return 0;
- case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); return 0;
- case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); return 0;
- case WM_XBUTTONDOWN: switch (HIWORD(wparam))
- {
- case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 1); return 0;
- case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 1); return 0;
- default:
- TRACELOG(LOG_WARNING, "TODO: handle ex mouse button DOWN wparam=%u", HIWORD(wparam));
- return 0;
- }
- case WM_XBUTTONUP: switch (HIWORD(wparam))
- {
- case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 0); return 0;
- case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 0); return 0;
- default:
- TRACELOG(LOG_WARNING, "TODO: handle ex mouse button UP wparam=%u", HIWORD(wparam));
- return 0;
- }
- case WM_MOUSEWHEEL:
- CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA;
- return 0;
- case WM_MOUSEHWHEEL:
- CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA;
- return 0;
- case WM_APP_UPDATE_WINDOW_SIZE:
- UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, globalAppScreenSize, CORE.Window.flags);
- return 0;
- default:
- return DefWindowProcW(hwnd, msg, wparam, lparam);
}
+
+ // Create real modern OpenGL context (3.3 core)
+ HGLRC realContext = NULL;
+ if (wglCreateContextAttribsARB)
+ {
+ 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_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB [glDebugMessageCallback()]
+ 0 // Terminator
+ };
+
+ // NOTE: We are not sharing context resources so, second parameters is NULL
+ realContext = wglCreateContextAttribsARB(hdc, NULL, contextAttribs);
+
+ // Check for error context creation errors
+ // ERROR_INVALID_VERSION_ARB (0x2095)
+ // ERROR_INVALID_PROFILE_ARB (0x2096)
+ if (realContext == NULL) TRACELOG(LOG_ERROR, "GL: Error creating requested context: %lu", GetLastError());
+ }
+
+ // Cleanup dummy temp context
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(tempContext);
+
+ // Activate real context
+ if (realContext) wglMakeCurrent(hdc, realContext);
+
+ // Once we got a real modern OpenGL context,
+ // we can load required extensions (function pointers)
+ rlLoadExtensions(WglGetProcAddress);
+
+ return realContext;
}
-static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
-{
- // sanity check, should we remove this?
- DWORD mask = STYLE_MASK_ALL;
- if (globalHwnd == hwnd)
- {
- if (msg == WM_WINDOWPOSCHANGING) {
- mask &= ~(WS_MINIMIZE | WS_MAXIMIZE);
- }
- CheckFlags("WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
- }
-
- FlagsOp flagsOp;
- flagsOp.set = 0;
- flagsOp.clear = 0;
- LRESULT result = WndProc2(hwnd, msg, wparam, lparam, &flagsOp);
-
- // sanity check, should we remove this?
- if (globalHwnd == 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);
- abort();
- }
-
- {
- 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
- );
- }
- }
-
- return result;
-}
-
-
+// Initialize platform: graphics, inputs and more
int InitPlatform(void)
{
- globalDesiredFlags = SanitizeFlags(SANITIZE_FLAGS_FIRST, CORE.Window.flags);
- // from this point CORE.Window.flags should always reflect the actual state of the window
- CORE.Window.flags = FLAG_WINDOW_HIDDEN | (globalDesiredFlags & FLAG_MASK_NO_UPDATE);
+ platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_FIRST, CORE.Window.flags);
+ platform.appScreenWidth = CORE.Window.screen.width;
+ platform.appScreenHeight = CORE.Window.screen.height;
- globalAppScreenSize = (Vector2){ CORE.Window.screen.width, CORE.Window.screen.height };
+ // 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);
+/*
+ // TODO: Review SetProcessDpiAwarenessContext()
+ // NOTE: SetProcessDpiAwarenessContext() requires Windows 10, version 1703 and shcore.lib linkage
if (IsWindows10Version1703OrGreaterWin32())
{
TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators");
- if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
- LogFail("SetProcessDpiAwarenessContext", GetLastError());
- abort();
- }
+ if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
+ TRACELOG(LOG_ERROR, "%s failed, error %u", "SetProcessDpiAwarenessContext", GetLastError());
}
else
{
TRACELOG(LOG_INFO, "DpiAware: Use default value as 0
+ platform.cursorEnabled = true;
TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully");
+
return 0;
}
+// Close platform
void ClosePlatform(void)
{
- if (globalHwnd)
+ if (platform.hwnd)
{
- if (0 == DestroyWindow(globalHwnd))
- {
- LogFail("DestroyWindow", GetLastError());
- abort();
- }
-
- globalHwnd = NULL;
+ int result = DestroyWindow(platform.hwnd);
+ if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Error on window destroy: %u", GetLastError());
+ platform.hwnd = NULL;
+ }
+}
+
+// Window procedure, message processing callback
+// NOTE: All window event messages are processed here
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ LRESULT result = 0;
+
+ // Sanity check
+ DWORD mask = STYLE_MASK_ALL;
+ if (platform.hwnd == hwnd)
+ {
+ if (msg == WM_WINDOWPOSCHANGING) mask &= ~(WS_MINIMIZE | WS_MAXIMIZE);
+ CheckFlags("WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
+ }
+
+ FlagsOp flagsOp = { 0 };
+ FlagsOp *deferredFlags = &flagsOp;
+
+ // Message processing
+ //------------------------------------------------------------------------------------
+ switch (msg)
+ {
+ case WM_CREATE:
+ {
+ // WARNING: Not recommended to do OpenGL intialization at this point
+
+ } break;
+ //case WM_ACTIVATE
+ case WM_DESTROY:
+ {
+ // Clean up for window destruction
+ wglMakeCurrent(platform.hdc, NULL);
+ if (platform.glContext)
+ {
+ if (!wglDeleteContext(platform.glContext)) abort();
+ platform.glContext = NULL;
+ }
+
+ if (platform.hdc)
+ {
+ if (!ReleaseDC(hwnd, platform.hdc)) abort();
+ platform.hdc = NULL;
+ }
+
+ } break;
+ case WM_CLOSE: CORE.Window.shouldClose = true; break; // Window close button [x], ALT+F4
+ //case WM_QUIT: // Application closing, not related to window
+ case WM_KILLFOCUS:
+ {
+ memset(CORE.Input.Keyboard.previousKeyState, 0, sizeof(CORE.Input.Keyboard.previousKeyState));
+ memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState));
+ } break;
+ case WM_SIZING:
+ {
+ if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE)
+ {
+ // TODO: Enforce min/max size
+ }
+ else TRACELOG(LOG_WARNING, "WINDOW: Trying to resize a non-resizable window");
+
+ result = TRUE;
+ } break;
+ case WM_STYLECHANGING:
+ {
+ if (wparam == GWL_STYLE)
+ {
+ STYLESTRUCT *ss = (STYLESTRUCT *)lparam;
+ GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags);
+
+ UINT dpi = GetDpiForWindow(hwnd);
+ SIZE clientSize = GetClientSize(hwnd);
+ 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);
+
+ 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");
+ deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED;
+ }
+ }
+ }
+ } break;
+ 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;
+ }
+
+ 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) mized = MIZED_MIN;
+ else
+ {
+ WINDOWPLACEMENT placement;
+ placement.length = sizeof(placement);
+ if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowPlacement", GetLastError());
+
+ if (placement.showCmd == SW_SHOWMAXIMIZED) mized = MIZED_MAX;
+ }
+
+ switch (mized)
+ {
+ case MIZED_NONE:
+ {
+ deferredFlags->clear |= (FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED);
+ 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 ((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;
+ }
+
+ } break;
+ case MIZED_MIN:
+ {
+ // !!! NOTE !!! Do not update the maximized/borderless
+ // flags because when hwnd is minimized it temporarily overrides
+ // the maximized state/flag which gets restored on SW_RESTORE
+ deferredFlags->set |= FLAG_WINDOW_MINIMIZED;
+ } break;
+ case MIZED_MAX:
+ {
+ deferredFlags->clear |= FLAG_WINDOW_MINIMIZED;
+ deferredFlags->set |= FLAG_WINDOW_MAXIMIZED;
+ } break;
+ default: break;
+ }
+ } break;
+ case WM_SIZE:
+ {
+ // WARNING: Don't trust the docs, they say you won't get this message if you don't call DefWindowProc
+ // in response to WM_WINDOWPOSCHANGED but looks like when a window is created you'll get this
+ // message without getting WM_WINDOWPOSCHANGED
+ HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
+ } break;
+ //case WM_MOVE
+ case WM_WINDOWPOSCHANGED:
+ {
+ WINDOWPOS *pos = (WINDOWPOS*)lparam;
+ if (!(pos->flags & SWP_NOSIZE)) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
+ } break;
+ case WM_GETDPISCALEDSIZE:
+ {
+ SIZE *inoutSize = (SIZE *)lparam;
+ UINT newDpi = (UINT)wparam; // TODO: WARNING: Converting from WPARAM = UINT_PTR
+
+ // for any of these other cases, we might want to post a window
+ // resize event after the dpi changes?
+ if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return TRUE;
+ if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) return TRUE;
+ if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) return TRUE;
+
+ float dpiScale = ((float)newDpi)/96.0f;
+ bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI;
+ SIZE desired = PxFromPt2(dpiScale, dpiScaling, platform.appScreenWidth, platform.appScreenHeight);
+ inoutSize->cx = desired.cx;
+ inoutSize->cy = desired.cy;
+
+ result = TRUE;
+ } break;
+ case WM_DPICHANGED:
+ {
+ 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());
+ }
+ } break;
+ case WM_SETCURSOR:
+ {
+ if (LOWORD(lparam) == HTCLIENT)
+ {
+ SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
+ return 0;
+ }
+
+ result = DefWindowProc(hwnd, msg, wparam, lparam);
+ } break;
+ //case WM_PAINT
+ case WM_INPUT:
+ {
+ HandleRawInput(lparam);
+ } break;
+ case WM_MOUSEMOVE:
+ {
+ if (platform.cursorEnabled)
+ {
+ CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam);
+ CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam);
+ CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
+ }
+ } break;
+ case WM_KEYDOWN: HandleKey(wparam, lparam, 1); break;
+ case WM_KEYUP: HandleKey(wparam, lparam, 0); break;
+ case WM_LBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_LEFT, 1); break;
+ case WM_LBUTTONUP : HandleMouseButton(MOUSE_BUTTON_LEFT, 0); break;
+ case WM_RBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_RIGHT, 1); break;
+ 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:
+ {
+ switch (HIWORD(wparam))
+ {
+ case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 1); break;
+ case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 1); break;
+ default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button DOWN wparam=%u", HIWORD(wparam)); break;
+ }
+ } break;
+ case WM_XBUTTONUP:
+ {
+ switch (HIWORD(wparam))
+ {
+ case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 0); break;
+ case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 0); break;
+ default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button UP wparam=%u", HIWORD(wparam)); break;
+ }
+ } break;
+ case WM_MOUSEWHEEL: CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break;
+ case WM_MOUSEHWHEEL: CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break;
+ case WM_APP_UPDATE_WINDOW_SIZE:
+ {
+ UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, platform.appScreenWidth, platform.appScreenHeight, CORE.Window.flags);
+ } break;
+
+ default: result = DefWindowProcW(hwnd, msg, wparam, lparam); // Message passed directly for execution (default behaviour)
+ }
+ //------------------------------------------------------------------------------------
+
+ // Sanity check
+ 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);
+ }
+
+ 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);
+ }
+
+ return result;
+}
+
+static void HandleKey(WPARAM wparam, LPARAM lparam, char state)
+{
+ KeyboardKey key = KeyFromWparam(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;
+
+ if ((key == KEY_ESCAPE) && (state == 1)) CORE.Window.shouldClose = 1;
+ }
+ else TRACELOG(LOG_WARNING, "INPUT: Unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam);
+
+ // TODO: Add key to the queue as well?
+}
+
+static void HandleMouseButton(int button, char state)
+{
+ CORE.Input.Mouse.currentButtonState[button] = state;
+ CORE.Input.Touch.currentTouchState[button] = state;
+}
+
+static void HandleRawInput(LPARAM lparam)
+{
+ RAWINPUT input = { 0 };
+
+ 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 (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "Unexpected WM_INPUT type %lu", input.header.dwType);
+
+ if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!");
+
+ if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!");
+
+ // Trick to keep the mouse position at 0,0 and instead move
+ // the previous position so we can still get a proper mouse delta
+ //CORE.Input.Mouse.previousPosition.x -= input.data.mouse.lLastX;
+ //CORE.Input.Mouse.previousPosition.y -= input.data.mouse.lLastY;
+ //if (CORE.Input.Mouse.currentPosition.x != 0) abort();
+ //if (CORE.Input.Mouse.currentPosition.y != 0) abort();
+}
+
+static void HandleWindowResize(HWND hwnd, int *width, int *height)
+{
+ if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return;
+
+ SIZE clientSize = GetClientSize(hwnd);
+
+ //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;
+ //glViewport(0, 0, clientSize.cx, clientSize.cy);
+ //SetupFramebuffer(0, 0);
+
+ SetupViewport(clientSize.cx, clientSize.cy);
+ CORE.Window.resizedLastFrame = true;
+ float dpiScale = ((float)GetDpiForWindow(hwnd))/96.0f;
+ bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI);
+ unsigned int screenWidth = highdpi? (unsigned int)(((float)clientSize.cx)/dpiScale) : clientSize.cx;
+ 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, "WINDOW: Updating app size to %ix%i from window resize", screenWidth, screenHeight);
+ *width = screenWidth;
+ *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);
+}
+
+// Update window style
+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);
+ 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)",
+ 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));
+ if (currentMized != desiredMized)
+ {
+ switch (desiredMized)
+ {
+ case MIZED_NONE: ShowWindow(hwnd, SW_RESTORE); break;
+ case MIZED_MIN: ShowWindow(hwnd, SW_MINIMIZE); break;
+ case MIZED_MAX: ShowWindow(hwnd, SW_MAXIMIZE); break;
+ }
+ }
+}
+
+// Sanitize flags
+static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags)
+{
+ if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE))
+ {
+ TRACELOG(LOG_INFO, "borderless windows mode is overriding maximized");
+ flags &= ~FLAG_WINDOW_MAXIMIZED;
+ }
+
+ switch (kind)
+ {
+ 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;
+ }
+
+ return flags;
+}
+
+// All window state changes from raylib flags go through this function. It performs
+// whatever operations are needed to update the window state to match the desired flags
+// In most cases this function should not update CORE.Window.flags directly, instead,
+// the window itself should update CORE.Window.flags in response to actual state changes
+// This means that CORE.Window.flags should always represent the actual state of the
+// window. This function will continue to perform these update operations so long as
+// the state continues to change
+//
+// This design takes care of many odd corner cases. For example, if you want to restore
+// a window that was previously maximized AND minimized and you want to remove both these
+// flags, you actually need to call ShowWindow with SW_RESTORE twice. Another example is
+// if you have a maximized window, if the undecorated flag is modified then we'd need to
+// update the window style, but updating the style would mean the window size would change
+// causing the window to lose its Maximized state which would mean we'd need to update the
+// window size and then update the window style a second time to restore that maximized
+// state. This implementation is able to handle any/all of these special situations with a
+// retry loop that continues until we either reach the desired state or the state stops changing
+static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height)
+{
+ // Flags that just apply immediately without needing any operations
+ 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)
+ {
+ (*wglSwapInterval)(vsync);
+ if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT;
+ else CORE.Window.flags &= ~FLAG_VSYNC_HINT;
+ }
+
+ DWORD previousStyle;
+ for (unsigned attempt = 1; ; attempt++)
+ {
+ CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL);
+
+ bool windowSizeUpdated = false;
+ if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags))
+ {
+ windowSizeUpdated = UpdateWindowSize(UPDATE_WINDOW_NORMAL, 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)
+ {
+ TRACELOG(LOG_ERROR, "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);
+ }
+
+ previousStyle = MakeWindowStyle(CORE.Window.flags);
+ UpdateWindowStyle(hwnd, desiredFlags);
}
}
diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c
index a399092ad..e067fd82a 100644
--- a/src/platforms/rcore_drm.c
+++ b/src/platforms/rcore_drm.c
@@ -65,12 +65,17 @@
// so the enum KEY_F12 from raylib is used
#undef KEY_F12
-#include // Generic Buffer Management (native platform for EGL on DRM)
#include // Direct Rendering Manager user-level library interface
#include // Direct Rendering Manager mode setting (KMS) interface
-#include "EGL/egl.h" // Native platform windowing system interface
-#include "EGL/eglext.h" // EGL extensions
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ #include // Generic Buffer Management (native platform for EGL on DRM)
+ #include "EGL/egl.h" // Native platform windowing system interface
+ #include "EGL/eglext.h" // EGL extensions
+#else
+ #include // For mmap when copying to the dumb buffer
+ #include // For the conversion of certain error messages
+#endif
// NOTE: DRM cache enables triple buffered DRM caching
#if defined(SUPPORT_DRM_CACHE)
@@ -103,15 +108,19 @@ typedef struct {
drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector
drmModeCrtc *crtc; // CRT Controller
int modeIndex; // Index of the used mode of connector->modes
+ uint32_t prevFB; // Previous DRM framebufer (during frame swapping)
+
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
struct gbm_device *gbmDevice; // GBM device
struct gbm_surface *gbmSurface; // GBM surface
struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping)
- uint32_t prevFB; // Previous GBM framebufer (during frame swapping)
-
EGLDisplay device; // Native display device (physical screen connection)
EGLSurface surface; // Surface to draw on, framebuffers (connected to context)
EGLContext context; // Graphic context, mode in which drawing can be done
EGLConfig config; // Graphic config
+#else
+ uint32_t prevDumbHandle; // Handle to the previous dumb buffer (during frame swapping)
+#endif
// Keyboard data
int defaultKeyboardMode; // Default keyboard mode
@@ -780,6 +789,8 @@ void SwapScreenBuffer()
// Swap back buffer with front buffer (screen drawing)
void SwapScreenBuffer(void)
{
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ // Hardware rendering buffer swap with EGL
eglSwapBuffers(platform.device, platform.surface);
if (!platform.gbmSurface || (-1 == platform.fd) || !platform.connector || !platform.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
@@ -805,6 +816,196 @@ void SwapScreenBuffer(void)
if (platform.prevBO) gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO);
platform.prevBO = bo;
+#else
+ // Software rendering buffer swap
+ if ((platform.fd == -1) || !platform.connector || (platform.modeIndex < 0))
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap");
+ return;
+ }
+
+ // Get the software rendered color buffer
+ int bufferWidth = 0, bufferHeight = 0;
+ void *colorBuffer = swGetColorBuffer(&bufferWidth, &bufferHeight);
+ if (!colorBuffer)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: Failed to get software color buffer");
+ return;
+ }
+
+ // Retrieving the dimensions of the display mode used
+ drmModeModeInfo *mode = &platform.connector->modes[platform.modeIndex];
+ uint32_t width = mode->hdisplay;
+ uint32_t height = mode->vdisplay;
+
+ // Dumb buffers use a fixed format based on bpp
+#if SW_COLOR_BUFFER_BITS == 24
+ const uint32_t bpp = 32; // 32 bits per pixel (XRGB8888 format)
+ const uint32_t depth = 24; // Color depth, here only 24 bits, alpha is not used
+#else
+ // REVIEW: Not sure how it will be interpreted (RGB or RGBA?)
+ const uint32_t bpp = SW_COLOR_BUFFER_BITS;
+ const uint32_t depth = SW_COLOR_BUFFER_BITS;
+#endif
+
+ // Create a dumb buffer for software rendering
+ struct drm_mode_create_dumb creq = { 0 };
+ creq.width = width;
+ creq.height = height;
+ creq.bpp = bpp;
+
+ int result = drmIoctl(platform.fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+ if (result < 0)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: Failed to create dumb buffer: %s", strerror(errno));
+ return;
+ }
+
+ // 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)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d (%s)", result, strerror(errno));
+ struct drm_mode_destroy_dumb dreq = { 0 };
+ dreq.handle = creq.handle;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return;
+ }
+
+ // Map the dumb buffer to copy our software rendered buffer
+ struct drm_mode_map_dumb mreq = { 0 };
+ mreq.handle = creq.handle;
+ result = drmIoctl(platform.fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+ if (result != 0)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: Failed to map dumb buffer: %s", strerror(errno));
+ drmModeRmFB(platform.fd, fb);
+ struct drm_mode_destroy_dumb dreq = { 0 };
+ dreq.handle = creq.handle;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return;
+ }
+
+ // Map the buffer into userspace
+ void *dumbBuffer = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, platform.fd, mreq.offset);
+ if (dumbBuffer == MAP_FAILED)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: Failed to mmap dumb buffer: %s", strerror(errno));
+ drmModeRmFB(platform.fd, fb);
+ struct drm_mode_destroy_dumb dreq = { 0 };
+ dreq.handle = creq.handle;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return;
+ }
+
+ // Copy the software rendered buffer to the dumb buffer with scaling if needed
+ if (bufferWidth == width && bufferHeight == height)
+ {
+ // Direct copy if sizes match
+ swCopyFramebuffer(0, 0, bufferWidth, bufferHeight, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer);
+ }
+ else
+ {
+ // Scale the software buffer to match the display mode
+ swBlitFramebuffer(0, 0, width, height, 0, 0, bufferWidth, bufferHeight, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer);
+ }
+
+ // Unmap the buffer
+ munmap(dumbBuffer, creq.size);
+
+ // Find a CRTC compatible with the connector
+ uint32_t crtcId = 0;
+ if (platform.crtc) crtcId = platform.crtc->crtc_id;
+ else
+ {
+ // Find a CRTC that's compatible with this connector
+ drmModeRes *res = drmModeGetResources(platform.fd);
+ if (!res)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: Failed to get DRM resources");
+ drmModeRmFB(platform.fd, fb);
+ struct drm_mode_destroy_dumb dreq = {0};
+ dreq.handle = creq.handle;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return;
+ }
+
+ // Check which CRTCs are compatible with this connector
+ drmModeEncoder *encoder = NULL;
+ if (platform.connector->encoder_id) encoder = drmModeGetEncoder(platform.fd, platform.connector->encoder_id);
+
+ if (encoder && encoder->crtc_id)
+ {
+ crtcId = encoder->crtc_id;
+ platform.crtc = drmModeGetCrtc(platform.fd, crtcId);
+ }
+ else
+ {
+ // Find a free CRTC
+ for (int i = 0; i < res->count_crtcs; i++)
+ {
+ drmModeCrtc *crtc = drmModeGetCrtc(platform.fd, res->crtcs[i]);
+ if (crtc && !crtc->buffer_id) // CRTC is free
+ {
+ crtcId = res->crtcs[i];
+ if (platform.crtc) drmModeFreeCrtc(platform.crtc);
+ platform.crtc = crtc;
+ break;
+ }
+
+ if (crtc) drmModeFreeCrtc(crtc);
+ }
+ }
+
+ if (encoder) drmModeFreeEncoder(encoder);
+ drmModeFreeResources(res);
+
+ if (!crtcId)
+ {
+ TRACELOG(LOG_ERROR, "DISPLAY: No compatible CRTC found");
+ drmModeRmFB(platform.fd, fb);
+ struct drm_mode_destroy_dumb dreq = {0};
+ dreq.handle = creq.handle;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return;
+ }
+ }
+
+ // Set CRTC with better error handling
+ result = drmModeSetCrtc(platform.fd, crtcId, fb, 0, 0, &platform.connector->connector_id, 1, mode);
+ 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;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ return;
+ }
+
+ // Clean up previous framebuffer
+ if (platform.prevFB)
+ {
+ result = drmModeRmFB(platform.fd, platform.prevFB);
+ if (result != 0) TRACELOG(LOG_WARNING, "DISPLAY: drmModeRmFB() failed with result: %d", result);
+ }
+
+ platform.prevFB = fb;
+
+ // Clean up previous dumb buffer
+ if (platform.prevDumbHandle)
+ {
+ struct drm_mode_destroy_dumb dreq = {0};
+ dreq.handle = platform.prevDumbHandle;
+ drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+ }
+
+ platform.prevDumbHandle = creq.handle;
+#endif
}
#endif // SUPPORT_DRM_CACHE
@@ -950,10 +1151,15 @@ int InitPlatform(void)
platform.connector = NULL;
platform.modeIndex = -1;
platform.crtc = NULL;
+ platform.prevFB = 0;
+
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
platform.gbmDevice = NULL;
platform.gbmSurface = NULL;
platform.prevBO = NULL;
- platform.prevFB = 0;
+#else
+ platform.prevDumbHandle = 0;
+#endif
// Initialize graphic device: display/window and graphic context
//----------------------------------------------------------------------------
@@ -965,11 +1171,12 @@ int InitPlatform(void)
if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: Default graphic device DRM opened successfully");
#else
TRACELOG(LOG_WARNING, "DISPLAY: No graphic card set, trying platform-gpu-card");
- platform.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4)
+ platform.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4)
if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: platform-gpu-card opened successfully");
if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL))
{
+ if (platform.fd != -1) close(platform.fd);
TRACELOG(LOG_WARNING, "DISPLAY: Failed to open platform-gpu-card, trying card1");
platform.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded
if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card1 opened successfully");
@@ -977,10 +1184,19 @@ int InitPlatform(void)
if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL))
{
+ if (platform.fd != -1) close(platform.fd);
TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card1, trying card0");
platform.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3)
if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card0 opened successfully");
}
+
+ if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL))
+ {
+ if (platform.fd != -1) close(platform.fd);
+ TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card0, trying card2");
+ platform.fd = open("/dev/dri/card2", O_RDWR);
+ if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card2 opened successfully");
+ }
#endif
if (platform.fd == -1)
@@ -993,29 +1209,55 @@ int InitPlatform(void)
if (!res)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources");
+ close(platform.fd);
return -1;
}
TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors);
+ // Connector detection
for (size_t i = 0; i < res->count_connectors; i++)
{
TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i);
drmModeConnector *con = drmModeGetConnector(platform.fd, res->connectors[i]);
- TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes);
+ if (!con)
+ {
+ TRACELOG(LOG_WARNING, "DISPLAY: Failed to get connector %i", i);
+ continue;
+ }
+
+ TRACELOG(LOG_TRACE, "DISPLAY: Connector %i modes detected: %i", i, con->count_modes);
+ 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");
// In certain cases the status of the conneciton is reported as UKNOWN, but it is still connected
// This might be a hardware or software limitation like on Raspberry Pi Zero with composite output
- if (((con->connection == DRM_MODE_CONNECTED) || (con->connection == DRM_MODE_UNKNOWNCONNECTION)) && (con->encoder_id))
+ // WARNING: Accept CONNECTED, UNKNOWN and even those without encoder_id connectors for software mode
+ if (((con->connection == DRM_MODE_CONNECTED) || (con->connection == DRM_MODE_UNKNOWNCONNECTION)) && (con->count_modes > 0))
{
- TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected");
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ // For hardware rendering, we need an encoder_id
+ if (con->encoder_id)
+ {
+ TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i connected with encoder", i);
+ platform.connector = con;
+ break;
+ }
+ else TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i connected but no encoder", i);
+#else
+ // For software rendering, we can accept even without encoder_id
+ TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i suitable for software rendering", i);
platform.connector = con;
break;
+#endif
}
- else
+
+ if (!platform.connector)
{
- TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)");
+ TRACELOG(LOG_TRACE, "DISPLAY: DRM connector %i NOT suitable (deleting)", i);
drmModeFreeConnector(con);
}
}
@@ -1024,14 +1266,18 @@ int InitPlatform(void)
{
TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found");
drmModeFreeResources(res);
+ close(platform.fd);
return -1;
}
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
drmModeEncoder *enc = drmModeGetEncoder(platform.fd, platform.connector->encoder_id);
if (!enc)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder");
+ drmModeFreeConnector(platform.connector);
drmModeFreeResources(res);
+ close(platform.fd);
return -1;
}
@@ -1040,7 +1286,9 @@ int InitPlatform(void)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc");
drmModeFreeEncoder(enc);
+ drmModeFreeConnector(platform.connector);
drmModeFreeResources(res);
+ close(platform.fd);
return -1;
}
@@ -1055,7 +1303,9 @@ int InitPlatform(void)
{
TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found");
drmModeFreeEncoder(enc);
+ drmModeFreeConnector(platform.connector);
drmModeFreeResources(res);
+ close(platform.fd);
return -1;
}
@@ -1064,7 +1314,7 @@ int InitPlatform(void)
}
const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT;
- const int fps = (CORE.Time.target > 0)? (1.0/CORE.Time.target) : 60;
+ const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60;
// Try to find an exact matching mode
platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
@@ -1083,7 +1333,9 @@ int InitPlatform(void)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode");
drmModeFreeEncoder(enc);
+ drmModeFreeConnector(platform.connector);
drmModeFreeResources(res);
+ close(platform.fd);
return -1;
}
@@ -1092,19 +1344,45 @@ int InitPlatform(void)
TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", platform.connector->modes[platform.modeIndex].name,
platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay,
- (platform.connector->modes[platform.modeIndex].flags & DRM_MODE_FLAG_INTERLACE)? 'i' : 'p',
+ (platform.connector->modes[platform.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p',
platform.connector->modes[platform.modeIndex].vrefresh);
+ drmModeFreeEncoder(enc);
+ enc = NULL;
+#else
+ // For software rendering, the first available mode can be used
+ if (platform.connector->count_modes > 0)
+ {
+ 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",
+ platform.connector->modes[0].name,
+ 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);
+ }
+ else
+ {
+ TRACELOG(LOG_WARNING, "DISPLAY: No modes available for connector");
+ drmModeFreeConnector(platform.connector);
+ drmModeFreeResources(res);
+ close(platform.fd);
+ return -1;
+ }
+#endif
+
// Use the width and height of the surface for render
CORE.Window.render.width = CORE.Window.screen.width;
CORE.Window.render.height = CORE.Window.screen.height;
- drmModeFreeEncoder(enc);
- enc = NULL;
-
drmModeFreeResources(res);
res = NULL;
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ // Hardware rendering initialization with EGL
platform.gbmDevice = gbm_create_device(platform.fd);
if (!platform.gbmDevice)
{
@@ -1273,6 +1551,32 @@ int InitPlatform(void)
return -1;
}
+ // Load OpenGL extensions
+ // NOTE: GL procedures address loader is required to load extensions
+ rlLoadExtensions(eglGetProcAddress);
+#else
+ // At this point we need to manage render size vs screen size
+ // NOTE: This function use and modify global module variables:
+ // -> CORE.Window.screen.width/CORE.Window.screen.height
+ // -> CORE.Window.render.width/CORE.Window.render.height
+ // -> CORE.Window.screenScale
+ SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
+
+ // Setup window ready state for software rendering
+ CORE.Window.ready = true;
+
+ 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 (Software Rendering)");
+ TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
+ TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
+ 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);
+#endif
+
if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow();
// If graphic device is no properly initialized, we end program
@@ -1285,12 +1589,8 @@ int InitPlatform(void)
CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // true
CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // false
- // Load OpenGL extensions
- // NOTE: GL procedures address loader is required to load extensions
- rlLoadExtensions(eglGetProcAddress);
//----------------------------------------------------------------------------
-
- // Initialize timming system
+ // Initialize timing system
//----------------------------------------------------------------------------
// NOTE: timming system must be initialized before the input events system
InitTimer();
@@ -1336,6 +1636,7 @@ void ClosePlatform(void)
platform.prevFB = 0;
}
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
if (platform.prevBO)
{
gbm_surface_release_buffer(platform.gbmSurface, platform.prevBO);
@@ -1353,6 +1654,7 @@ void ClosePlatform(void)
gbm_device_destroy(platform.gbmDevice);
platform.gbmDevice = NULL;
}
+#endif
if (platform.crtc)
{
@@ -1374,6 +1676,7 @@ void ClosePlatform(void)
platform.fd = -1;
}
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
// Close surface, context and display
if (platform.device != EGL_NO_DISPLAY)
{
@@ -1392,6 +1695,7 @@ void ClosePlatform(void)
eglTerminate(platform.device);
platform.device = EGL_NO_DISPLAY;
}
+#endif
CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called
diff --git a/src/raymath.h b/src/raymath.h
index d3c8b9697..65a20de59 100644
--- a/src/raymath.h
+++ b/src/raymath.h
@@ -2555,7 +2555,7 @@ RMAPI int QuaternionEquals(Quaternion p, Quaternion q)
// Decompose a transformation matrix into its rotational, translational and scaling components and remove shear
RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale)
{
- float eps = 1e-9;
+ float eps = (float)1e-9;
// Extract Translation
translation->x = mat.m12;
diff --git a/src/rlgl.h b/src/rlgl.h
index 7db05251d..ecd3795a9 100644
--- a/src/rlgl.h
+++ b/src/rlgl.h
@@ -149,7 +149,8 @@
#endif
// Security check in case no GRAPHICS_API_OPENGL_* defined
-#if !defined(GRAPHICS_API_OPENGL_11) && \
+#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE) && \
+ !defined(GRAPHICS_API_OPENGL_11) && \
!defined(GRAPHICS_API_OPENGL_21) && \
!defined(GRAPHICS_API_OPENGL_33) && \
!defined(GRAPHICS_API_OPENGL_43) && \
@@ -159,7 +160,7 @@
#endif
// Security check in case multiple GRAPHICS_API_OPENGL_* defined
-#if defined(GRAPHICS_API_OPENGL_11)
+#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
#if defined(GRAPHICS_API_OPENGL_21)
#undef GRAPHICS_API_OPENGL_21
#endif
@@ -174,6 +175,11 @@
#endif
#endif
+// Software implementation uses OpenGL 1.1 functionality
+#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ #define GRAPHICS_API_OPENGL_11
+#endif
+
// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality
// WARNING: Specific parts are checked with #if defines
#if defined(GRAPHICS_API_OPENGL_21)
@@ -427,7 +433,8 @@ typedef struct rlRenderBatch {
// OpenGL version
typedef enum {
- RL_OPENGL_11 = 1, // OpenGL 1.1
+ RL_OPENGL_11_SOFTWARE = 0, // Software rendering
+ RL_OPENGL_11, // OpenGL 1.1
RL_OPENGL_21, // OpenGL 2.1 (GLSL 120)
RL_OPENGL_33, // OpenGL 3.3 (GLSL 330)
RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330)
@@ -644,10 +651,8 @@ RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer
RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element)
RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index
RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index
-#if defined(GRAPHICS_API_OPENGL_11)
RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer
RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer
-#endif
// Textures state
RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot
@@ -686,6 +691,8 @@ RLAPI void rlDisableScissorTest(void); // Disable scissor test
RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test
RLAPI void rlEnablePointMode(void); // Enable point mode
RLAPI void rlDisablePointMode(void); // Disable point mode
+RLAPI void rlSetPointSize(float size); // Set the point drawing size
+RLAPI float rlGetPointSize(void); // Get the point drawing size
RLAPI void rlEnableWireMode(void); // Enable wire mode
RLAPI void rlDisableWireMode(void); // Disable wire mode
RLAPI void rlSetLineWidth(float width); // Set the line drawing width
@@ -768,6 +775,10 @@ RLAPI unsigned int rlLoadFramebuffer(void); // Loa
RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer
RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete
RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU
+#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+RLAPI void rlCopyFramebuffer(int x, int y, int width, int height, int format, void *pixels); // Copy framebuffer pixel data to internal buffer
+RLAPI void rlResizeFramebuffer(int width, int height); // Resize internal framebuffer
+#endif
// Shaders management
RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings
@@ -834,24 +845,32 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad
#endif
#if defined(GRAPHICS_API_OPENGL_11)
- #if defined(__APPLE__)
- #include // OpenGL 1.1 library for OSX
- #include // OpenGL extensions library
+ #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ #define RLSW_IMPLEMENTATION
+ #define SW_MALLOC(sz) RL_MALLOC(sz)
+ #define SW_REALLOC(ptr, newSz) RL_REALLOC(ptr, newSz)
+ #define SW_FREE(ptr) RL_FREE(ptr)
+ #include "external/rlsw.h" // OpenGL 1.1 software implementation
#else
- // APIENTRY for OpenGL function pointer declarations is required
- #if !defined(APIENTRY)
- #if defined(_WIN32)
- #define APIENTRY __stdcall
- #else
- #define APIENTRY
+ #if defined(__APPLE__)
+ #include // OpenGL 1.1 library for OSX
+ #include // OpenGL extensions library
+ #else
+ // APIENTRY for OpenGL function pointer declarations is required
+ #if !defined(APIENTRY)
+ #if defined(_WIN32)
+ #define APIENTRY __stdcall
+ #else
+ #define APIENTRY
+ #endif
+ #endif
+ // WINGDIAPI definition. Some Windows OpenGL headers need it
+ #if !defined(WINGDIAPI) && defined(_WIN32)
+ #define WINGDIAPI __declspec(dllimport)
#endif
- #endif
- // WINGDIAPI definition. Some Windows OpenGL headers need it
- #if !defined(WINGDIAPI) && defined(_WIN32)
- #define WINGDIAPI __declspec(dllimport)
- #endif
- #include // OpenGL 1.1 library
+ #include // OpenGL 1.1 library
+ #endif
#endif
#endif
@@ -2016,6 +2035,25 @@ float rlGetLineWidth(void)
return width;
}
+// Set the point drawing size
+void rlSetPointSize(float size)
+{
+#if defined(GRAPHICS_API_OPENGL_11)
+ glPointSize(size);
+#endif
+}
+
+// Get the point drawing size
+float rlGetPointSize(void)
+{
+ float size = 1;
+#if defined(GRAPHICS_API_OPENGL_11)
+ glGetFloatv(GL_POINT_SIZE, &size);
+#endif
+ return size;
+
+}
+
// Enable line aliasing
void rlEnableSmoothLines(void)
{
@@ -2318,6 +2356,15 @@ void rlglInit(int width, int height)
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);
+ }
+#endif
+
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
// Store screen size into global variables
RLGL.State.framebufferWidth = width;
@@ -2339,11 +2386,15 @@ void rlglClose(void)
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
rlUnloadRenderBatch(RLGL.defaultBatch);
- rlUnloadShaderDefault(); // Unload default shader
+ rlUnloadShaderDefault(); // Unload default shader
glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture
TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId);
#endif
+
+#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ swClose(); // Unload sofware renderer resources
+#endif
}
// Load OpenGL extensions
@@ -2653,7 +2704,10 @@ void *rlGetProcAddress(const char *procName)
int rlGetVersion(void)
{
int glVersion = 0;
-#if defined(GRAPHICS_API_OPENGL_11)
+
+#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+ glVersion = RL_OPENGL_11_SOFTWARE;
+#elif defined(GRAPHICS_API_OPENGL_11)
glVersion = RL_OPENGL_11;
#endif
#if defined(GRAPHICS_API_OPENGL_21)
@@ -3694,6 +3748,22 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format)
return pixels;
}
+#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
+// Copy framebuffer pixel data to internal buffer
+void rlCopyFramebuffer(int x, int y, int width, int height, int format, void* pixels)
+{
+ unsigned int glInternalFormat, glFormat, glType;
+ rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); // Get OpenGL texture format
+ swCopyFramebuffer(x, y, width, height, glFormat, glType, pixels);
+}
+
+// Resize internal framebuffer
+void rlResizeFramebuffer(int width, int height)
+{
+ swResizeFramebuffer(width, height);
+}
+#endif
+
// Read screen pixel data (color buffer)
unsigned char *rlReadScreenPixels(int width, int height)
{
@@ -4003,10 +4073,10 @@ void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffe
#endif
}
-#if defined(GRAPHICS_API_OPENGL_11)
// Enable vertex state pointer
void rlEnableStatePointer(int vertexAttribType, void *buffer)
{
+#if defined(GRAPHICS_API_OPENGL_11)
if (buffer != NULL) glEnableClientState(vertexAttribType);
switch (vertexAttribType)
{
@@ -4017,14 +4087,16 @@ void rlEnableStatePointer(int vertexAttribType, void *buffer)
//case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors
default: break;
}
+#endif
}
// Disable vertex state pointer
void rlDisableStatePointer(int vertexAttribType)
{
+#if defined(GRAPHICS_API_OPENGL_11)
glDisableClientState(vertexAttribType);
-}
#endif
+}
// Load vertex array object (VAO)
unsigned int rlLoadVertexArray(void)
@@ -5292,4 +5364,4 @@ static Matrix rlMatrixInvert(Matrix mat)
}
#endif
-#endif // RLGL_IMPLEMENTATION
+#endif // RLGL_IMPLEMENTATION
\ No newline at end of file
diff --git a/src/rmodels.c b/src/rmodels.c
index 128d5303b..9ded580c3 100644
--- a/src/rmodels.c
+++ b/src/rmodels.c
@@ -1293,10 +1293,19 @@ void UploadMesh(Mesh *mesh, bool dynamic)
rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION);
// Enable vertex attributes: texcoords (shader-location = 1)
- mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic);
- rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, RL_FLOAT, 0, 0, 0);
- rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD);
+ if (mesh->texcoords != NULL)
+ {
+ mesh->vboId[RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic);
+ rlSetVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, RL_FLOAT, 0, 0, 0);
+ rlEnableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD);
+ }
+ else
+ {
+ float value[2] = { 0.0f, 0.0f };
+ rlSetVertexAttributeDefault(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, value, SHADER_ATTRIB_VEC2, 2);
+ rlDisableVertexAttribute(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD);
+ }
// WARNING: When setting default vertex attribute values, the values for each generic vertex attribute
// is part of current state, and it is maintained even if a different program object is used
@@ -1420,7 +1429,7 @@ void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int
// Draw a 3d mesh with material and transform
void DrawMesh(Mesh mesh, Material material, Matrix transform)
{
-#if defined(GRAPHICS_API_OPENGL_11)
+#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
#define GL_VERTEX_ARRAY 0x8074
#define GL_NORMAL_ARRAY 0x8075
#define GL_COLOR_ARRAY 0x8076
@@ -1431,12 +1440,12 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform)
if (mesh.animVertices) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animVertices);
else rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices);
- rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords);
+ if (mesh.texcoords) rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords);
if (mesh.animNormals) rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.animNormals);
- else rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals);
+ else if (mesh.normals) rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals);
- rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors);
+ if (mesh.colors) rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors);
rlPushMatrix();
rlMultMatrixf(MatrixToFloat(transform));
@@ -3836,6 +3845,7 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float
// Draw a model points
// WARNING: OpenGL ES 2.0 does not support point mode drawing
+// TODO: gate these properly for non es 2.0 versions only
void DrawModelPoints(Model model, Vector3 position, float scale, Color tint)
{
rlEnablePointMode();
@@ -4261,9 +4271,10 @@ static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform
continue;
}
transforms[i].rotation = QuaternionMultiply(transforms[bones[i].parent].rotation, transforms[i].rotation);
+ transforms[i].scale = Vector3Multiply(transforms[i].scale, transforms[bones[i].parent].scale);
+ transforms[i].translation = Vector3Multiply(transforms[i].translation, transforms[bones[i].parent].scale);
transforms[i].translation = Vector3RotateByQuaternion(transforms[i].translation, transforms[bones[i].parent].rotation);
transforms[i].translation = Vector3Add(transforms[i].translation, transforms[bones[i].parent].translation);
- transforms[i].scale = Vector3Multiply(transforms[i].scale, transforms[bones[i].parent].scale);
}
}
}
@@ -4419,7 +4430,11 @@ static Model LoadOBJ(const char *fileName)
model.meshes[i].vertices = (float *)MemAlloc(sizeof(float)*vertexCount*3);
model.meshes[i].normals = (float *)MemAlloc(sizeof(float)*vertexCount*3);
model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2);
+ #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
model.meshes[i].colors = (unsigned char *)MemAlloc(sizeof(unsigned char)*vertexCount*4);
+ #else
+ model.meshes[i].colors = NULL;
+ #endif
}
MemFree(localMeshVertexCounts);
@@ -4473,8 +4488,18 @@ static Model LoadOBJ(const char *fileName)
for (int i = 0; i < 3; i++) model.meshes[meshIndex].vertices[localMeshVertexCount*3 + i] = objAttributes.vertices[vertIndex*3 + i];
- for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i];
- if (objAttributes.normals != NULL && normalIndex != TINYOBJ_INVALID_INDEX && normalIndex >= 0)
+ if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0))
+ {
+ for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i];
+ model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1];
+ }
+ else
+ {
+ model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 0] = 0.0f;
+ model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 0.0f;
+ }
+
+ if ((objAttributes.normals != NULL) && (normalIndex != TINYOBJ_INVALID_INDEX) && (normalIndex >= 0))
{
for (int i = 0; i < 3; i++) model.meshes[meshIndex].normals[localMeshVertexCount*3 + i] = objAttributes.normals[normalIndex*3 + i];
}
@@ -4484,11 +4509,9 @@ static Model LoadOBJ(const char *fileName)
model.meshes[meshIndex].normals[localMeshVertexCount*3 + 1] = 1.0f;
model.meshes[meshIndex].normals[localMeshVertexCount*3 + 2] = 0.0f;
}
-
- model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1];
-
+ #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
for (int i = 0; i < 4; i++) model.meshes[meshIndex].colors[localMeshVertexCount*4 + i] = 255;
-
+ #endif
faceVertIndex++;
localMeshVertexCount++;
}
@@ -6238,6 +6261,18 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
*animCount = (int)data->animations_count;
animations = (ModelAnimation *)RL_CALLOC(data->animations_count, sizeof(ModelAnimation));
+ Transform worldTransform = { 0 };
+ cgltf_float cgltf_worldTransform[16] = { 0 };
+ cgltf_node *node = skin.joints[0];
+ cgltf_node_transform_world(node->parent, cgltf_worldTransform);
+ Matrix worldMatrix = {
+ cgltf_worldTransform[0], cgltf_worldTransform[4], cgltf_worldTransform[8], cgltf_worldTransform[12],
+ cgltf_worldTransform[1], cgltf_worldTransform[5], cgltf_worldTransform[9], cgltf_worldTransform[13],
+ cgltf_worldTransform[2], cgltf_worldTransform[6], cgltf_worldTransform[10], cgltf_worldTransform[14],
+ cgltf_worldTransform[3], cgltf_worldTransform[7], cgltf_worldTransform[11], cgltf_worldTransform[15]
+ };
+ MatrixDecompose(worldMatrix, &worldTransform.translation, &worldTransform.rotation, &worldTransform.scale);
+
for (unsigned int i = 0; i < data->animations_count; i++)
{
animations[i].bones = LoadBoneInfoGLTF(skin, &animations[i].boneCount);
@@ -6356,6 +6391,13 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
};
}
+ Transform* root = &animations[i].framePoses[j][0];
+ root->rotation = QuaternionMultiply(worldTransform.rotation, root->rotation);
+ root->scale = Vector3Multiply(root->scale, worldTransform.scale);
+ root->translation = Vector3Multiply(root->translation, worldTransform.scale);
+ root->translation = Vector3RotateByQuaternion(root->translation, worldTransform.rotation);
+ root->translation = Vector3Add(root->translation, worldTransform.translation);
+
BuildPoseFromParentJoints(animations[i].bones, animations[i].boneCount, animations[i].framePoses[j]);
}
diff --git a/src/rtext.c b/src/rtext.c
index edfe33c8b..ce9f381c4 100644
--- a/src/rtext.c
+++ b/src/rtext.c
@@ -1452,7 +1452,7 @@ Rectangle GetGlyphAtlasRec(Font font, int codepoint)
char **LoadTextLines(const char *text, int *count)
{
int lineCount = 1;
- int textSize = strlen(text);
+ int textSize = (int)strlen(text);
// Text pass to get required line count
for (int i = 0; i < textSize; i++)
@@ -1679,7 +1679,7 @@ char *GetTextBetween(const char *text, const char *begin, const char *end)
if (beginIndex > -1)
{
- int beginLen = strlen(begin);
+ int beginLen = (int)strlen(begin);
int endIndex = TextFindIndex(text + beginIndex + beginLen, end);
if (endIndex > -1)
@@ -1758,15 +1758,15 @@ char *TextReplaceBetween(const char *text, const char *begin, const char *end, c
if (beginIndex > -1)
{
- int beginLen = strlen(begin);
+ int beginLen = (int)strlen(begin);
int endIndex = TextFindIndex(text + beginIndex + beginLen, end);
if (endIndex > -1)
{
endIndex += (beginIndex + beginLen);
- int textLen = strlen(text);
- int replaceLen = (replacement == NULL)? 0 : strlen(replacement);
+ int textLen = (int)strlen(text);
+ int replaceLen = (replacement == NULL)? 0 : (int)strlen(replacement);
int toreplaceLen = endIndex - beginIndex - beginLen;
result = (char *)RL_CALLOC(textLen + replaceLen - toreplaceLen + 1, sizeof(char));
diff --git a/src/rtextures.c b/src/rtextures.c
index 6366e4645..a85467d8c 100644
--- a/src/rtextures.c
+++ b/src/rtextures.c
@@ -811,8 +811,8 @@ Image GenImageColor(int width, int height, Color color)
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -863,8 +863,8 @@ Image GenImageGradientLinear(int width, int height, int direction, Color start,
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -900,8 +900,8 @@ Image GenImageGradientRadial(int width, int height, float density, Color inner,
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -949,8 +949,8 @@ Image GenImageGradientSquare(int width, int height, float density, Color inner,
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -974,8 +974,8 @@ Image GenImageChecked(int width, int height, int checksX, int checksY, Color col
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -997,8 +997,8 @@ Image GenImageWhiteNoise(int width, int height, float factor)
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -1048,8 +1048,8 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
@@ -1113,8 +1113,8 @@ Image GenImageCellular(int width, int height, int tileSize)
.data = pixels,
.width = width,
.height = height,
- .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
- .mipmaps = 1
+ .mipmaps = 1,
+ .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
};
return image;
diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md
index bb37b2db7..7c9de0b57 100644
--- a/tools/rexm/examples_report.md
+++ b/tools/rexm/examples_report.md
@@ -57,6 +57,7 @@ Example elements validated:
| core_high_dpi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| core_render_texture | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| core_undo_redo | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
+| core_input_actions | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| shapes_basic_shapes | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| shapes_bouncing_ball | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| shapes_colors_palette | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c
index 6fdf74645..f6c297769 100644
--- a/tools/rexm/rexm.c
+++ b/tools/rexm/rexm.c
@@ -2047,6 +2047,7 @@ static void SortExampleByName(rlExampleInfo *items, int count)
// WARNING: Supported resource file extensions is hardcoded by used file types
// but new examples could require other file extensions to be added,
// maybe it should look for '.xxx")' patterns instead
+// TODO: WARNING: Some resources could require linked resources: .fnt --> .png, .mtl --> .png, .gltf --> .png, ...
static char **ScanExampleResources(const char *filePath, int *resPathCount)
{
#define REXM_MAX_RESOURCE_PATH_LEN 256
@@ -2330,7 +2331,7 @@ static const char *GenerateUUIDv4(void)
// Update source code header and comments metadata
static void UpdateSourceMetadata(const char *exSrcPath, const rlExampleInfo *info)
{
- if (FileExists(exSrcPath) && IsFileExtension(exSrcPath, ".c"))
+ if (FileExists(exSrcPath) && IsFileExtension(exSrcPath, ".c") && (!TextIsEqual(info->category, "others")))
{
// WARNING: Cache a copy of exSrcPath to avoid modifications by TextFormat()
char exSourcePath[512] = { 0 };