Windows draw_line offset 1 pixel ?

DaveInDev

Member
Hi,

I just noticed that draw_line(x1,y1,x2,y2) is starting to draw one pixel after x1,y1...
Do you have the same result on your GMS2 or is it my graph card ?

Here is an example to put in the draw event of an empty object :

GML:
dy = 0;
draw_set_color(c_grey);
draw_rectangle(x,y+dy,x+200,y+dy+15,false);

dy += 20;
draw_set_color(c_grey);
draw_rectangle(x,y+dy,x+200,y+dy+15,false);
draw_set_color(c_white);
draw_line(x,y+dy,x+200,y+dy);

dy += 20;
draw_set_color(c_grey);
draw_rectangle(x,y+dy,x+200,y+dy+15,false);
draw_set_color(c_white);
draw_line(x,y+dy,x+200,y+dy);
draw_set_color(c_red);
draw_point(x,y+dy);
look at the upper left corners of this zoomed portion of the display...
the rectangle and the point are ok, but the line is forgetting the first pixel...

PS: note that I just use the default room, viewport, etc... so no resizing of the display. All is 1:1.

1610184801953.png
 

Attachments

Last edited:

FoxyOfJungle

Kazan Games
Do you have the same result on your GMS2 or is it my graph card ?
Yes:



And apparently it also affects functions like draw_rectangle and among others...
I really don't know why they didn't fix it, but this problem has been around for a long time, and it bothers me a lot because it limits you from doing certain things...
We could send them a ticket to fix this function maybe...
 

DaveInDev

Member
Yes I'll send a ticket.

(I already send one for something of the same kind : I copy the ticket for you :
"Description: I noticed a strange problem when using draw_text_ext_transformed_color or draw_text_transformed_colour. If you use them with their defaults values, they are printing on screen at 1 pixel lower than all other draw_text functions... It can be quite annoying when trying to place precisely texts on the screen."

I added a YYZ example showing it. They reply that this bug is already in their todo list.... It appears today in first position ;)
 

DaveInDev

Member
I had a strange answer from Yoyo :
Thank you for reporting this, but it's not a bug or something we need to address, as drawing primitives is handled by your GPU and isn't something we can control. I do get the same results as you show, but others may get the line drawn exactly as expected.

You should review whether drawing primitives like this is correct for your project, or if you should just have a sprite for your button instead.
Are all these simple primitives like draw_line not supposed to render exactly the same on any hardware ? Then every game would have a different rendering depending on the hardware ? There must be some kind of standard about that no ? DirectX is a standard, no ? I don't know what to think... 🤨
 

DaveInDev

Member
so now, every time I use a "beginning" draw_line that is not connected to a previous one, I add a "draw_point"
 

TheouAegis

Member
The fact of the matter is draw_rectangle() also removes the first pixel. It only adds it if you set "outline" to 0. So then the question is does setting "outline" to 1 tell it to draw 4 lines, or does setting "outline" to 0 tell it to draw n points? Also, draw_line() and draw_rectangle(.....,1) both skip the first pixel vertically as well.

I think if you want a chain of lines, you should probably use draw_primitive_begin(pr_linestrip).
I wasted a few hours before finally testing this, and I still don't know what to think. So draw_primitive_begin(pr_linestrip) has the same issue as draw_line(), except in reverse -- it skips the last pixel instead of the first. Reversing the vertexes changes nothing. Reversing the arguments changes nothing. Why does one method draw shifted left and the other draw shifted right?

Using draw_rectangle(......,0) would appear to be a valid alternative to draw_line(), except I remember in my old Castlevania engine on GM8 trying to use draw_rectangle(......,0) and having 1 pixel shaved off, so maybe it's not as valid either. For straight lines, Mike used to just suggest stretching a 1x1 sprite as the most stable method.

As for why I "wasted hours", I was trying to use vertex buffers (incorrectly, I think, so I scratched that) and messing with GM's graphics settings. Came up empty handed. The only idea I had taht I didn't try was working in 3D.
 
Last edited:

DaveInDev

Member
The fact of the matter is draw_rectangle() also removes the first pixel.
Really ? On my PC, the filled rectangle is correct (look at the first shape on the zoomed screencopy in the first message of this thread). Only the draw_line has a missing point at the start.
So "interpretations" of these primitives really depends on the graphic card... So weird that they did not come to a standard.
 

gnysek

Member
This depends on GFX card manufacturer in fact. It's an issue which was there since first versions of GameMaker (yet without Studio in name).
 

TheouAegis

Member
Really ? On my PC, the filled rectangle is correct (look at the first shape on the zoomed screencopy in the first message of this thread). Only the draw_line has a missing point at the start.
So "interpretations" of these primitives really depends on the graphic card... So weird that they did not come to a standard.
I had a typo in the post. draw_rectangle(......,0) will utilize all the pixels. draw_rectangle(......,1) will ignore the top-left pixels, at least on graphics cards that do the same with draw_line().
 

HalRiyami

Member
From my experience draw_line draws at the end of a pixel while draw_primitive draws at the beginning of a pixel which is why you get these differences.

If this issue is truly out of yoyo's hands, then here are 3 different work arounds you can use to account for this discrepancy:

1. Make a draw_line function with the subtraction built in:
GML:
function draw_line_fixed(_x1, _y1, _x2, _y2) {
    draw_line(_x1 -1, _y1 -1, _x2 -1, _y2 -1);
}
2. Make a function that uses primitives:
GML:
function draw_line_fixed(_x1, _y1, _x2, _y2) {
    draw_primitive_begin(pr_linestrip);
    draw_vertex(_x1, _y1);
    draw_vertex(_x2, _y2);
    draw_primitive_end();
}
3. Make a function that uses a sprite:
GML:
function draw_line_fixed(_x1, _y1, _x2, _y2) {
    var _a    = point_direction(_x1, _y1, _x2, _y2);
    var _d    = point_distance(_x1, _y1, _x2, _y2);
    // Where 64 is the sprite's width and height, respectively.
    draw_sprite_ext(sprPlainColor, 0, _x1, _y1, _d/64, 1/64, _a, c_white, 1);
}
All three methods can be extended to include width and color arguments, and an alpha argument for the last two.
In my testing, the sprite method is faster than the primitives method so if I'm looking for performance, I go with sprite, but if I want non-dependency I go with primitives. draw_line is faster than the two but is inconsistent even when accounting for the pixel shift (try it with a zoomed in room).
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
1. Make a draw_line function with the subtraction built in:
As gnysek pointed out above, this depends on GPU - I at one point made a little application to test various inconsistencies in DirectX drawing and primitive coordinates varied by +-1 px between various GPUs. Whether a rectangle should cover the last column/row of pixels (making the size x2-x1+1, y2-y1+1) would also vary. Other differences included initial content within surfaces (on some GPUs, not clearing the surface would have you see whatever that was previously on that spot in VRAM) and behaviours when asking the GPU to draw a non-power-of-two surface with tiling (some GPUs would draw it correctly, some would leave padding until pow2).

2. Make a function that uses primitives:
The only case where primitives make a difference is for drawing a connected set of lines

3. Make a function that uses a sprite:
This works. For low-resolution, pixel-perfect games, implementing Bresenham's line algorithm (with a pixel sprite, of course) can be a good solution.
 

DaveInDev

Member
1. Make a draw_line function with the subtraction built in:
GML:
function draw_line_fixed(_x1, _y1, _x2, _y2) {
    draw_line(_x1 -1, _y1 -1, _x2 -1, _y2 -1);
}
I'm afraid this solution cannot work :
Yoyo answer to my report pointed out that this draw_line +1 pixel behaviour depends on your GPU. My GPU draw 1 pixel later, but some GPU are precise and draw from x1,y1 .
More than that, if you draw a line to the left, your solution will lose 2 pixels ! ;)
For me, the only consistent simple fix is :
GML:
function draw_line_fixed(_x1, _y1, _x2, _y2) {
    draw_point(_x1,_y1);
    draw_line(_x1, _y1, _x2, _y2);
}
If the draw_line is correct, I will just waste time drawing a pixel, not a big deal.
 

DaveInDev

Member
I had a typo in the post. draw_rectangle(......,0) will utilize all the pixels. draw_rectangle(......,1) will ignore the top-left pixels, at least on graphics cards that do the same with draw_line().
ha ha, guess what ?......... Not on my GPU (nvidia GTX1060) !! 🤯
draw_line is wrong but draw_rect is ok, outlined or filled.

1610584107901.png

anyone want to run this test and post a different zoomed result ?...
GML:
dx = 0;
dy = 0;
draw_set_color(c_grey);
draw_rectangle(x+dx,y+dy,x+dx+15,y+dy+15,false);

dy += 20;
draw_set_color(c_grey);
draw_rectangle(x+dx,y+dy,x+dx+15,y+dy+15,false);
draw_set_color(c_lime);
draw_rectangle(x+dx,y+dy,x+dx+15,y+dy+15,true);

dx += 20
dy = 0;
draw_set_color(c_grey);
draw_rectangle(x+dx,y+dy,x+dx+15,y+dy+15,false);
draw_set_color(c_white);
draw_line(x+dx,y+dy,x+dx+15,y+dy);

dy += 20;
draw_set_color(c_grey);
draw_rectangle(x+dx,y+dy,x+dx+15,y+dy+15,false);
draw_set_color(c_white);
draw_line(x+dx,y+dy,x+dx+15,y+dy);
draw_set_color(c_red);
draw_point(x+dx,y+dy);
 

HalRiyami

Member
@YellowAfterlife Oh I didn't know primitives exhibited the same issue on different GPU's. I do know that they make a difference compared to line functions (in my case, they draw one pixel above and to the left.)

@DaveInDev I tried your code and got slightly different results than you. I think your outline rectangle might be wrong since it is larger than the filled rectangle.
Screenshot 2021-01-13 205106.png
Orange is 20 pixels, blue is 15 pixels (sprite background)
 

DaveInDev

Member
@DaveInDev I tried your code and got slightly different results than you. I think your outline rectangle might be wrong since it is larger than the filled rectangle.
Yes, even the rectnagle outline has different behaviour...
I just found in the GMS2 doc :
"Please note that the rectangle being drawn may need different values (+/-1 on the x, y, or width or height) to be drawn with the desired dimensions due to differences across the various supported platforms. "

I was using these primitives to draw some simple UI (buttons, frames, etc...), but I will forget these functions that are not reliable. Will go on sprites, even if they ask for more work if you want to resize them nicely (a simple stretch does not give nice results on a button).
 
Well, shouldn't it be possible to make a set of wrapper functions, that add/subtract a pixel depending on the GPU behaviour?
First an init call would perform render tests to a surface in order to decide one or more offset values or flags which then are used in all further primitive draw calls. The main issue would be correctly figuring out what all the possible behaviours are, in order to know what should be accounted for.

On my gpu, at least, a pattern I find is that depending on whether the bounding rectangle of what's being draw has a width/height of 1, or 2 or more, it adds a 1 px offset on that axis. Also, a bit strangely, when I try to draw a sloping line with a 1 px difference in height level for the two endpoints, it just becomes a horizontal line along the bottom of the bounding rectangle. If, however, I make the height difference 2 pixels, it does make that sloping line I wanted in the previous case (first half of the line at one level, the second half of of the line at the other).
 

DaveInDev

Member
The main issue would be correctly figuring out what all the possible behaviours are, in order to know what should be accounted for
Like you, I thought about an automatic test but fins the same conclusion. Seing how many different behaviours we have on all our different cards, how many checks are we supposed to make ? How many versions of draw_line or draws_rectangles exist ? We nned to make a big survey on the forum ;)
 
Top