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) | core_high_dpi | ⭐⭐☆☆ | 5.0 | 5.5 | [Jonathan Marler](https://github.com/marler8997) | | [core_render_texture](core/core_render_texture.c) | core_render_texture | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_undo_redo](core/core_undo_redo.c) | core_undo_redo | ⭐⭐⭐☆ | 5.5 | 5.6 | [Ramon Santamaria](https://github.com/raysan5) | +| [core_input_actions](core/core_input_actions.c) | core_input_actions | ⭐⭐☆☆ | 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) | text_codepoints_loading | ⭐⭐⭐☆ | 4.2 | 4.2 | [Ramon Santamaria](https://github.com/raysan5) | | [text_inline_styling](text/text_inline_styling.c) | text_inline_styling | ⭐⭐⭐☆ | 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) | models_animation_gpu_skinning | ⭐⭐⭐☆ | 4.5 | 4.5 | [Daniel Holden](https://github.com/orangeduck) | | [models_bone_socket](models/models_bone_socket.c) | models_bone_socket | ⭐⭐⭐⭐️ | 4.5 | 4.5 | [iP](https://github.com/ipzaur) | | [models_tesseract_view](models/models_tesseract_view.c) | models_tesseract_view | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Timothy van der Valk](https://github.com/arceryz) | +| [models_basic_voxel](models/models_basic_voxel.c) | models_basic_voxel | ⭐⭐☆☆ | 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 };