Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rshapes] DrawRectangleLines() missing pixel in lower right corner #4756

Closed
4 tasks done
Roucou opened this issue Feb 9, 2025 · 20 comments
Closed
4 tasks done

[rshapes] DrawRectangleLines() missing pixel in lower right corner #4756

Roucou opened this issue Feb 9, 2025 · 20 comments

Comments

@Roucou
Copy link

Roucou commented Feb 9, 2025

  • I tested it on latest raylib version from master branch
  • I checked there is no similar issue already reported
  • I checked the documentation on the wiki
  • My code has no errors or misuse of raylib

Issue description

If you draw a rectangle outline using DrawRectangleLines(0, 0, w, h, WHITE) the lower right corner is missing.

Environment

N/A

Issue Screenshot

/

Code Example

#include "raylib.h"
int main(void) {
    InitWindow(150, 150, "DrawRectangleLines bug");
    while (!WindowShouldClose()) {
        BeginDrawing();
            ClearBackground(BLACK);
            DrawRectangleLines(0, 0, 100, 100, WHITE); // FIXME: MISSING LOWER RIGHT CORNER
        EndDrawing();
    }
   CloseWindow();        // Close window and OpenGL context
   return 0;
}
@raysan5
Copy link
Owner

raysan5 commented Feb 9, 2025

@Roucou I'm afraid Environment and Screenshot data are required to track this issue

@Roucou
Copy link
Author

Roucou commented Feb 9, 2025

Running on Linux desktop:

INFO: Initializing raylib 5.6-dev
INFO: Platform backend: DESKTOP (GLFW)
INFO: Supported raylib modules:
INFO: > rcore:..... loaded (mandatory)
INFO: > rlgl:...... loaded (mandatory)
INFO: > rshapes:... loaded (optional)
INFO: > rtextures:. loaded (optional)
INFO: > rtext:..... loaded (optional)
INFO: > rmodels:... loaded (optional)
INFO: > raudio:.... loaded (optional)
INFO: DISPLAY: Device initialized successfully
INFO: > Display size: 1920 x 1200
INFO: > Screen size: 1000 x 800
INFO: > Render size: 1000 x 800
INFO: > Viewport offsets: 0, 0
INFO: GLAD: OpenGL extensions loaded successfully
INFO: GL: Supported extensions count: 232
INFO: GL: OpenGL device information:
INFO: > Vendor: Intel
INFO: > Renderer: Mesa Intel(R) Graphics (ADL GT2)
INFO: > Version: 4.6 (Core Profile) Mesa 24.2.8-1ubuntu1~24.04.1
INFO: > GLSL: 4.60
INFO: GL: VAO extension detected, VAO functions loaded successfully
INFO: GL: NPOT textures extension detected, full NPOT textures supported
INFO: GL: DXT compressed textures supported
INFO: GL: ETC2/EAC compressed textures supported
INFO: PLATFORM: DESKTOP (GLFW - X11): Initialized successfully

Screenshot:

Image

@raysan5
Copy link
Owner

raysan5 commented Feb 10, 2025

@Roucou The line with the missing pixel in the screenshot does not seem to be a 1px border line drawn using DrawRectangleLines(), are you scaling it? Are you using some other function?

@Roucou
Copy link
Author

Roucou commented Feb 10, 2025

Here's a screenshot of the exact above code example. The DrawRectangleLines(0, 0, 100, 100, WHITE) call draws a 1-pixel wide border, starting at 0, 0, and exactly 100 pixels wide x 100 pixels high. Except for the lower right pixel that's missing.

Image

@sleeptightAnsiC
Copy link
Contributor

Cursed...

Image

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 10, 2025

I don't see this problem on my end. This might be an issue with underlying OpenGL implementation. Here's how it looks like on my side (Arch Linux x86_64 with Xorg and proprietary Nvidia drivers):

Screenshots

Image
Image

Notice that in my case I'm using proprietary Nvidia drivers while OP is using Mesa Intel drivers which might be the reason why it looks different:

Logs
INFO: Initializing raylib 5.6-dev
INFO: Platform backend: DESKTOP (GLFW)
INFO: Supported raylib modules:
INFO:     > rcore:..... loaded (mandatory)
INFO:     > rlgl:...... loaded (mandatory)
INFO:     > rshapes:... loaded (optional)
INFO:     > rtextures:. loaded (optional)
INFO:     > rtext:..... loaded (optional)
INFO:     > rmodels:... loaded (optional)
INFO:     > raudio:.... loaded (optional)
INFO: DISPLAY: Device initialized successfully
INFO:     > Display size: 2560 x 1440
INFO:     > Screen size:  150 x 150
INFO:     > Render size:  150 x 150
INFO:     > Viewport offsets: 0, 0
INFO: GLAD: OpenGL extensions loaded successfully
INFO: GL: Supported extensions count: 390
INFO: GL: OpenGL device information:
INFO:     > Vendor:   NVIDIA Corporation
INFO:     > Renderer: NVIDIA GeForce GTX 1070 Ti/PCIe/SSE2
INFO:     > Version:  3.3.0 NVIDIA 570.86.16
INFO:     > GLSL:     3.30 NVIDIA via Cg compiler
INFO: GL: VAO extension detected, VAO functions loaded successfully
INFO: GL: NPOT textures extension detected, full NPOT textures supported
INFO: GL: DXT compressed textures supported
INFO: GL: ETC2/EAC compressed textures supported
INFO: PLATFORM: DESKTOP (GLFW - X11): Initialized successfully
INFO: TEXTURE: [ID 1] Texture loaded successfully (1x1 | R8G8B8A8 | 1 mipmaps)
INFO: TEXTURE: [ID 1] Default texture loaded successfully
INFO: SHADER: [ID 1] Vertex shader compiled successfully
INFO: SHADER: [ID 2] Fragment shader compiled successfully
INFO: SHADER: [ID 3] Program shader loaded successfully
INFO: SHADER: [ID 3] Default shader loaded successfully
INFO: RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)
INFO: RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)
INFO: RLGL: Default OpenGL state initialized successfully
INFO: TEXTURE: [ID 2] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
INFO: SYSTEM: Working Directory: /home/korn/rlspr

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 10, 2025

I believe this is one of those super annoyances with OpenGL...

It seems that DrawRectangleLines depends on unspecified/undefined behavior which does not guarantee that lines would join in all cases.

[EDIT] Current implementation of DrawRectangleLines tries to mitigate the problem by using glVertex2f with 0.5f offsets, but this still seems not enough. Also related to #3884 and #4670

CC @raysan5

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 10, 2025

This might a problem with more shapes than RectangleLines.

Out of curiosity, does this issue also occurs with DrawTriangleLines or DrawCircleLines on your side? @Roucou

@orcmid
Copy link
Contributor

orcmid commented Feb 10, 2025

I believe this is one of those super annoyances with OpenGL...

I have been wondering if the FP rounding settings on float to int have an impact on these cases,, especially across (target) platforms and build systems.

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 10, 2025

I have been wondering if the FP rounding settings on float to int have an impact on these cases

I played a bit with DrawRectangleLines() inside of debugger and don't really see where it could loose precision or cause rounding error. It does int-to-float cast (not float-to-int) and values from DrawRectangleLines(0, 0, 100, 100, WHITE) are too small to cause this issue (I think).

Maybe it happens somewhere deeper in rlVertex3f()

Image

@Roucou
Copy link
Author

Roucou commented Feb 10, 2025

Am doing some business travel the following days, will do some tests over the weekend. I did play around with switching to float instead of int earlier on, but that resulted in the same missing pixel. Also tried drawing the four lines individually using DrawLine(...) but that gave even weirder results.

@sleeptightAnsiC is probably right, it might very well be basic OpenGL incompatibility issues. So, for my game I'll probably have to switch to textures as a workaround. But many thanks for the investigation!

@raysan5 raysan5 changed the title [rshapes] DrawRectangleLines missing pixel in lower right corner [rshapes] DrawRectangleLines() missing pixel in lower right corner Feb 11, 2025
@raysan5
Copy link
Owner

raysan5 commented Feb 11, 2025

@Roucou @sleeptightAnsiC I'm afraid this is a recurrent issue and unfortunately it's platform dependant, it depends on the OS, the GPU and the drivers. If solved for a specific case it can break for other cases. Already worked on it many many times...

As a solution, you can use DrawRectangleLinesEx() tha uses QUADs instead of LINES and should work as expected.

Related issues: #4386, #3973, #3884, #3931...

@raysan5 raysan5 closed this as completed Feb 11, 2025
@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 11, 2025

@Roucou despite that issue is closed, make sure to answer my questions when you'll have some opportunity, please. I may take a look at fixing/forwarding this anyway.

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 13, 2025

Well, well, well...

I was able to reproduce this on some very old hardware with iGPU: Intel Mobile GM965/GL960 Integrated Graphics Controller and just like OP reported, one pixel was missing in bottom-right corner when camera 2d zoom was at 1.0 (with different zoom, missing pixel could appear in top-left).

I tried to fix it by manipulating the offset used in DrawRectangleLines() but it never really worked as expected. It would either make every corner empty or make every corner filled, depending on zoom. I guess, it made it more consistent to look at, but still, the issue remains.

If someone wishes to mess with this, here are...

patch with this function refactored

diff --git a/src/rshapes.c b/src/rshapes.c
index 704d74f..ed628e9 100644
--- a/src/rshapes.c
+++ b/src/rshapes.c
@@ -807,23 +807,30 @@ void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Col
 // but it solves another issue: https://github.com/raysan5/raylib/issues/3884
 void DrawRectangleLines(int posX, int posY, int width, int height, Color color)
 {
-    Matrix mat = rlGetMatrixTransform();
-    float xOffset = 0.5f/mat.m0;
-    float yOffset = 0.5f/mat.m5;
+    const float x = (float)posX;
+    const float y = (float)posY;
+    const float w = (float)width;
+    const float h = (float)height;
+    const float o = 0.51;  // offset
 
     rlBegin(RL_LINES);
         rlColor4ub(color.r, color.g, color.b, color.a);
-        rlVertex2f((float)posX + xOffset, (float)posY + yOffset);
-        rlVertex2f((float)posX + (float)width - xOffset, (float)posY + yOffset);
 
-        rlVertex2f((float)posX + (float)width - xOffset, (float)posY + yOffset);
-        rlVertex2f((float)posX + (float)width - xOffset, (float)posY + (float)height - yOffset);
+        // top: left => right
+        rlVertex2f(x + o,      y + o);
+        rlVertex2f(x + w - o,  y + o);
 
-        rlVertex2f((float)posX + (float)width - xOffset, (float)posY + (float)height - yOffset);
-        rlVertex2f((float)posX + xOffset, (float)posY + (float)height - yOffset);
+        // right: top => bottom
+        rlVertex2f(x + w - o,  y + o);
+        rlVertex2f(x + w - o,  y + h - o);
 
-        rlVertex2f((float)posX + xOffset, (float)posY + (float)height - yOffset);
-        rlVertex2f((float)posX + xOffset, (float)posY + yOffset);
+        // bottom: right => left
+        rlVertex2f(x + w - o,  y + h - o);
+        rlVertex2f(x + o,      y + h - o);
+
+        // left: bottom => top
+        rlVertex2f(x + o,      y + h - o);
+        rlVertex2f(x + o,      y + o);
     rlEnd();
 
 /*

test program

#include "raylib.h"

int main(void) {
	SetConfigFlags(FLAG_VSYNC_HINT);
	InitWindow(800, 800, "DrawRectangleLines bug");
	Camera2D camera = {.zoom = 1};
	bool growing = true;
	while (!WindowShouldClose())
	{
		const float spike = GetFrameTime() * 0.5f;
		// NOTE: uncomment line below to use animation
		// camera.zoom = growing ? (camera.zoom + spike) : (camera.zoom - spike);
		if (IsKeyPressed(KEY_SPACE))
			camera.zoom = growing ? (camera.zoom + 1) : (camera.zoom - 1);
		growing = (camera.zoom <= 1) ? true : (camera.zoom >= 7) ? false : growing;

		BeginDrawing();
			BeginMode2D(camera);
				ClearBackground(BLACK);
				DrawRectangleLines(50, 50, 50, 50, WHITE);
			EndMode2D();
		EndDrawing();
	}
	CloseWindow();
	return 0;
}

runtime logs

INFO: Initializing raylib 5.6-dev
INFO: Platform backend: DESKTOP (GLFW)
INFO: Supported raylib modules:
INFO:     > rcore:..... loaded (mandatory)
INFO:     > rlgl:...... loaded (mandatory)
INFO:     > rshapes:... loaded (optional)
INFO:     > rtextures:. loaded (optional)
INFO:     > rtext:..... loaded (optional)
INFO:     > rmodels:... loaded (optional)
INFO:     > raudio:.... loaded (optional)
INFO: DISPLAY: Trying to enable VSYNC
INFO: DISPLAY: Device initialized successfully
INFO:     > Display size: 1440 x 900
INFO:     > Screen size:  800 x 800
INFO:     > Render size:  800 x 800
INFO:     > Viewport offsets: 0, 0
INFO: GL: OpenGL device information:
INFO:     > Vendor:   Intel
INFO:     > Renderer: Mesa Intel(R) 965GM (CL)
INFO:     > Version:  2.1 Mesa 24.3.4-arch1.1
INFO:     > GLSL:     1.20
INFO: PLATFORM: DESKTOP (GLFW - X11): Initialized successfully
INFO: TEXTURE: [ID 1] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
INFO: SYSTEM: Working Directory: /home/korn/raylib/src
INFO: TEXTURE: [ID 1] Unloaded texture data from VRAM (GPU)
INFO: Window closed successfully

output of: `eglinfo -B`

GBM platform:
EGL API version: 1.5
EGL vendor string: Mesa Project
EGL version string: 1.5
EGL client APIs: OpenGL OpenGL_ES 
OpenGL compatibility profile vendor: Intel
OpenGL compatibility profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL compatibility profile version: 2.1 Mesa 24.3.4-arch1.1
OpenGL compatibility profile shading language version: 1.20
OpenGL ES profile vendor: Intel
OpenGL ES profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL ES profile version: OpenGL ES 2.0 Mesa 24.3.4-arch1.1
OpenGL ES profile shading language version: OpenGL ES GLSL ES 1.0.16

Wayland platform:
eglinfo: eglInitialize failed

X11 platform:
EGL API version: 1.5
EGL vendor string: Mesa Project
EGL version string: 1.5
EGL client APIs: OpenGL OpenGL_ES 
OpenGL compatibility profile vendor: Intel
OpenGL compatibility profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL compatibility profile version: 2.1 Mesa 24.3.4-arch1.1
OpenGL compatibility profile shading language version: 1.20
OpenGL ES profile vendor: Intel
OpenGL ES profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL ES profile version: OpenGL ES 2.0 Mesa 24.3.4-arch1.1
OpenGL ES profile shading language version: OpenGL ES GLSL ES 1.0.16

Surfaceless platform:
EGL API version: 1.5
EGL vendor string: Mesa Project
EGL version string: 1.5
EGL client APIs: OpenGL OpenGL_ES 
OpenGL compatibility profile vendor: Intel
OpenGL compatibility profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL compatibility profile version: 2.1 Mesa 24.3.4-arch1.1
OpenGL compatibility profile shading language version: 1.20
OpenGL ES profile vendor: Intel
OpenGL ES profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL ES profile version: OpenGL ES 2.0 Mesa 24.3.4-arch1.1
OpenGL ES profile shading language version: OpenGL ES GLSL ES 1.0.16

Device platform:
Device #0:

Platform Device platform:
EGL API version: 1.5
EGL vendor string: Mesa Project
EGL version string: 1.5
EGL client APIs: OpenGL OpenGL_ES 
OpenGL compatibility profile vendor: Intel
OpenGL compatibility profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL compatibility profile version: 2.1 Mesa 24.3.4-arch1.1
OpenGL compatibility profile shading language version: 1.20
OpenGL ES profile vendor: Intel
OpenGL ES profile renderer: Mesa Intel(R) 965GM (CL)
OpenGL ES profile version: OpenGL ES 2.0 Mesa 24.3.4-arch1.1
OpenGL ES profile shading language version: OpenGL ES GLSL ES 1.0.16

Device #1:

Platform Device platform:
EGL API version: 1.5
EGL vendor string: Mesa Project
EGL version string: 1.5
EGL client APIs: OpenGL OpenGL_ES 
OpenGL core profile vendor: Mesa
OpenGL core profile renderer: llvmpipe (LLVM 19.1.7, 128 bits)
OpenGL core profile version: 4.5 (Core Profile) Mesa 24.3.4-arch1.1
OpenGL core profile shading language version: 4.50
OpenGL compatibility profile vendor: Mesa
OpenGL compatibility profile renderer: llvmpipe (LLVM 19.1.7, 128 bits)
OpenGL compatibility profile version: 4.5 (Compatibility Profile) Mesa 24.3.4-arch1.1
OpenGL compatibility profile shading language version: 4.50
OpenGL ES profile vendor: Mesa
OpenGL ES profile renderer: llvmpipe (LLVM 19.1.7, 128 bits)
OpenGL ES profile version: OpenGL ES 3.2 Mesa 24.3.4-arch1.1
OpenGL ES profile shading language version: OpenGL ES GLSL ES 3.20

Linux DEAL260624 6.13.2-arch1-1 #1 SMP PREEMPT_DYNAMIC Sat, 08 Feb 2025 18:54:55 +0000 x86_64 GNU/Linux

@orcmid
Copy link
Contributor

orcmid commented Feb 13, 2025

With

const float o = 0.51;  // offset

0.51 does not have an exact float (and double) value on most C Language implementations. It would be interesting to see how it is converted back to decimal in a string with several digits (8 or more) to the right of the decimal point on different platforms and with different compilers and compilation options.

There are other ways to expose what the exact deviation is. It may not matter except when multiplications are involved and when rounding, if any, occurs in casting values to int.

PS: If at some point such a float is clipped into a short as a kind of union, it gets even weirder.

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Feb 13, 2025

0.51 does not have an exact float (and double) value on most C Language implementations.

The idea here was to just have something different than exact 0.5f to test how it would work. I tried 0.49, 0.51, 0.499, 0.501, and other bigger/smaller values. Float precision isn't really a problem here (I think).

This is really just a problem with OpenGL itself not really being meant for such usage. Hitting perfect pixels with GL_LINES is not fully deterministic, see: https://stackoverflow.com/questions/4532342/opengl-gl-lines-endpoints-not-joining.

@orcmid
Copy link
Contributor

orcmid commented Feb 13, 2025

@sleeptightAnsiC

0.51 does not have an exact float (and double) value on most C Language implementations.

... Hitting perfect pixels with GL_LINES is not fully deterministic, see: https://stackoverflow.com/questions/4532342/opengl-gl-lines-endpoints-not-joining.

I think "deterministic" is the wrong term. They key thing is to know where in a certain "diamond" you must be hitting to light a particular pixel. Then there is the matter of stepping through pixels (as when drawing a line) to light the starting pixel, the ending pixel, and all between. That can definitely depend on how the arithmetic is done and with what precision of operations. It will be a great lesson to try the same thing manually though.

Second-guessing OpenGL implementations is certainly challenging. You could draw a line yourself pixel-by-pixel to confirm what the challenges are, but that will still leave you stymied to figure out how to predict what OpenGL would do in drawing the same line as an internal operation.

PS: To see what struggle is involved, you can investigate what PostScript and TeX go through to obtain precision and predictable graphics at very high DPI.

@Roucou
Copy link
Author

Roucou commented Feb 15, 2025

DrawTraingleLines seems to have missing pixels at every vertex:

BeginDrawing();
    ClearBackground(BLACK);
    DrawTriangleLines((Vector2){1, 1}, (Vector2){101, 1}, (Vector2){1, 101}, WHITE);
EndDrawing();

Image

@Roucou
Copy link
Author

Roucou commented Feb 15, 2025

DrawCircleLines is broken too:

BeginDrawing();
    ClearBackground(BLACK);
    DrawCircleLines(50, 50, 24, WHITE);
EndDrawing();

Image

@Roucou
Copy link
Author

Roucou commented Feb 15, 2025

DrawRectangleLinesEx works, yeah!

BeginDrawing();
    ClearBackground(BLACK);
    DrawRectangleLinesEx((Rectangle){0, 0, 100, 100 }, 1, WHITE);
EndDrawing();

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants