• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Looping surface

Gzebra

Member
Hi, my game have a array of 512 by 512 which makes a surface that's 512*32 px in both dimensions. (max possible size for a surface) When my view is on the edge of the surface, I want to have it loop around, as if you where on a sphere.

So, to iterate. I have a 16.384 x 16.384 surface made from 32 x 32 sprites and a 512 x 512 value array. I draw a part of this surface that equals the size of the window. When I get to the edge of the surface, I would like the surface to wrap around itself, as if it were a sphere.

The only solution I can think of is instead of drawing a part of the surface that equals the size of the window, I could draw it in chunks of 32s, and determine individually which chunk to draw where and when.

That would mean I'd have to do something like this each draw event:
Code:
for(var i=0; i<room_width/32; i++){
for(var p=0; p<room_height/32; p++){
draw_surface_part(surface,left,top,32,32,i*32,p*32)
}}
Is this the best approach?
 
Last edited:

TheouAegis

Member
You could do that, or you could draw the whole surface multiple times depending on where it is. like, draw at the specified coordinates, and if those corners are too far to the right, draw it also one full surface width to the left. If it's too far to the top, draw again one full surface height below.
 

Gzebra

Member
You could do that, or you could draw the whole surface multiple times depending on where it is. like, draw at the specified coordinates, and if those corners are too far to the right, draw it also one full surface width to the left. If it's too far to the top, draw again one full surface height below.
I can't do that. I can only draw parts of the whole surface, or the whole surface once. GM can't make a surface that's bigger than 16.384 x 16.384. So whilst I can tell GM to draw the surface multiply times, I can't actually make a surface that is big enough to contain the surface multiply times.

EDIT: Maybe I don't have to increase the size of the surface. I'll try your method out..
 
Last edited:

Gzebra

Member
You could do that, or you could draw the whole surface multiple times depending on where it is. like, draw at the specified coordinates, and if those corners are too far to the right, draw it also one full surface width to the left. If it's too far to the top, draw again one full surface height below.
Step:
Code:
if(keyboard_check(ord("D")) || keyboard_check(ord("A"))){
    x+=keyboard_check(ord("D"))-keyboard_check(ord("A"))
    if(x<-room_width/32){x=512-(room_width/32)}
    if(x>512-(room_width/32)){x=-(room_width/32)} 
}
if(keyboard_check(ord("S")) || keyboard_check(ord("W"))){
    y+=keyboard_check(ord("S"))-keyboard_check(ord("W"))
    if(y<-room_height/32){y=512-(room_height/32)}
    if(y>512-(room_height/32)){y=-(room_height/32)} 
}
camera_set_view_pos(view_camera[0],x*32,y*32)
Draw:
Code:
var camX=camera_get_view_x(view_camera[0])
            var camY=camera_get_view_y(view_camera[0])
            sWidth=surface_get_width(surface)
            sHeight=surface_get_height(surface)
         
            if(camX<0){
                if(camY<0){ 
                    draw_surface_part(surface,sWidth+camX,sHeight+camY,abs(camX),abs(camY),0,0)
                    draw_surface_part(surface,0,sHeight+camY,room_width+camX,abs(camY),abs(camX),0)
                    draw_surface_part(surface,sWidth+camX,0,abs(camX),room_height+camY,0,abs(camY))
                    draw_surface_part(surface,0,0,room_width+camX,room_height+camY,abs(camX),abs(camY))
                }
                if(camY>=0){
                    draw_surface_part(surface,0,camY,room_width+camX,room_height,abs(camX),0)
                    draw_surface_part(surface,sWidth+camX,camY,abs(camX),room_height+camY,0,0)
                }
            }
            if(camX>=0){
                if(camY<0){
                    draw_surface_part(surface,camX,0,room_width,room_height+camY,0,abs(camY))
                    draw_surface_part(surface,camX,sHeight+camY,room_width+camX,abs(camY),0,0)
                }
                if(camY>=0){
                    draw_surface_part(surface,camX,camY,room_width,room_height,0,0)
                }
            }
Seems to work, I think there is a slight stutter when I change the camera coordinates though. Please let me know if I can improve this approach.
 
Last edited:

Gzebra

Member
So my motherboard stopped working shortly after posting last post.. I've been unable to do further development. I'm currently waiting on a new motherboard. If you're able to provide constructive criticism regarding my approach, please feel free to detail them below. Thank you very much :)
 

vdweller

Member
Hmm what if you used a vertex buffer/primitive (just 2 triangles) and you simply animated the uv coordinates? I can elaborate if you need me to, but it will still require to read a few documentation pages about them.

Also, this is more of a question, but aside from what game maker can handle, isn't there a texture size limitation from graphics cards as well? For example, a laptop may not be able to create a 16K x 16K surface. If that is indeed the case, I know that it's a ball buster when this happens on forums, but maaaaybe if you could take this break from work to say a few things on what you're trying to do in general (is this a tileset?), perhaps a few people could suggest better approaches (and cheaper, since your surface exceeds 1 gig of RAM as it is now).

Good luck on your new mobo.
 

Gzebra

Member
Hmm what if you used a vertex buffer/primitive (just 2 triangles) and you simply animated the uv coordinates? I can elaborate if you need me to, but it will still require to read a few documentation pages about them.

Also, this is more of a question, but aside from what game maker can handle, isn't there a texture size limitation from graphics cards as well? For example, a laptop may not be able to create a 16K x 16K surface. If that is indeed the case, I know that it's a ball buster when this happens on forums, but maaaaybe if you could take this break from work to say a few things on what you're trying to do in general (is this a tileset?), perhaps a few people could suggest better approaches (and cheaper, since your surface exceeds 1 gig of RAM as it is now).

Good luck on your new mobo.

Well, I am already using a vertex buffer, not sure what you mean by animate the uv coordinates though. Please feel free to elaborate.

I'll try and elaborate further with code..

Code:
vertex_format_begin()
vertex_format_add_position()
vertex_format_add_normal()
vertex_format_add_color()
vertex_format_add_texcoord()
vertexFormat = vertex_format_end()   
           
vertexBuffer = vertex_create_buffer()
vertex_begin(vertexBuffer, vertexFormat)
var vx,vy,tx,ty,txx,tyy
for(var w=0; w<=size; w++){
   for(var h=0; h<=size; h++){
               
       vx=w*32
       vy=h*32
       if(value[w,h]==cDeepSea)   {tx=0           ty=0 }
       if(value[w,h]==cShallowSea)   {tx=32/512       ty=0 }
       if(value[w,h]==cGrass)       {tx=64/512       ty=0 }
       if(value[w,h]==cIce)       {tx=96/512       ty=0 }
       if(value[w,h]==cSand)       {tx=128/512       ty=0 }
       if(value[w,h]==cStone)       {tx=160/512       ty=0 }
       if(value[w,h]==cIceWater)   {tx=192/512       ty=0 }
       if(value[w,h]==cThundra)   {tx=224/512       ty=0 }
       if(value[w,h]==cForrest)   {tx=256/512       ty=0 }
       if(value[w,h]==cJungle)       {tx=288/512       ty=0 }
       if(value[w,h]==cPine)       {tx=320/512       ty=0 }
       if(value[w,h]==cSwamp)       {tx=352/512       ty=0 }
       if(value[w,h]==cSavanah)   {tx=384/512       ty=0 }
       txx=tx+(32/512)
       tyy=ty+(32/512)
       
       vertex_position(vertexBuffer , vx, vy)           vertex_normal(vertexBuffer , 0, 0, -1)       vertex_color(vertexBuffer, $FFFFFF, 1)   vertex_texcoord(vertexBuffer, tx, ty)
       vertex_position(vertexBuffer , vx+32, vy)       vertex_normal(vertexBuffer , 0, 0, -1)       vertex_color(vertexBuffer, $FFFFFF, 1)   vertex_texcoord(vertexBuffer, txx, ty)
       vertex_position(vertexBuffer , vx, vy+32)       vertex_normal(vertexBuffer , 0, 0, -1)       vertex_color(vertexBuffer, $FFFFFF, 1)   vertex_texcoord(vertexBuffer, tx, tyy)
   
       vertex_position(vertexBuffer , vx, vy+32)       vertex_normal(vertexBuffer , 0, 0, -1)       vertex_color(vertexBuffer, $FFFFFF, 1)   vertex_texcoord(vertexBuffer, tx, tyy)
       vertex_position(vertexBuffer , vx+32, vy)       vertex_normal(vertexBuffer , 0, 0, -1)       vertex_color(vertexBuffer, $FFFFFF, 1)   vertex_texcoord(vertexBuffer, txx, ty)
       vertex_position(vertexBuffer , vx+32, vy+32)   vertex_normal(vertexBuffer , 0, 0, -1)       vertex_color(vertexBuffer, $FFFFFF, 1)   vertex_texcoord(vertexBuffer, txx,tyy)   
   }
}

vertex_end(vertexBuffer)
vertex_freeze(vertexBuffer)
 

vdweller

Member
Oh, I see you are using a vertex buffer tileset, so my suggestion to animate the uv coordinates probably won't cut it.

The good news is that you don't have to use a surface at all, which is indeed great because as I said not all systems may be happy with a 16Kx16K surface.

By using a very simple vertex shader, you can draw your buffer contents at any point on the screen. For example, you can draw you tileset at (0,0) and then you can redraw it at (16384,0). By using modulo in your view values you can essentially create the looping effect you want. If you want to go for such an approach, let me know.

There are also lots of ways to optimize such an approach, for example you can break down this big vertex buffer into smaller ones and only render those who are within the view, you can also make your buffer creation code faster, but such things are better left to be done last :)
 

SCR

Member
Do you know about the texture_set_repeat() fundtion? I think this would work for what you are trying to do...

///Draw Event wrap-around surface

//cx = (camera x coordinate for top-left of screen)
//cy = (camera y coordinate for top-left of screen)
//surf = (your surface)

var tex, sw, sh, sx1, sy1, sx2, sy2;

sw = surface_get_width(surf);
sh = surface_get_height(surf);
sx0 = cx/sw;
sx1 = (cx+640)/sw;
sy0 = cy/sh;
sy1 = (cy+480)/sh;

texture_set_repeat(true);

tex = surface_get_texture(surf);
draw_primitive_begin_texture(pr_trianglestrip, tex);
draw_vertex_texture(0, 0, sx0, sy0);
draw_vertex_texture(640, 0, sx1, sy0);
draw_vertex_texture(0, 480, sx0, sy1);
draw_vertex_texture(640, 480, sx1, sy1);
draw_primitive_end();

//adjust the values 640 and 480 to the size of your view.
 

Gzebra

Member
Oh, I see you are using a vertex buffer tileset, so my suggestion to animate the uv coordinates probably won't cut it.

The good news is that you don't have to use a surface at all, which is indeed great because as I said not all systems may be happy with a 16Kx16K surface.

By using a very simple vertex shader, you can draw your buffer contents at any point on the screen. For example, you can draw you tileset at (0,0) and then you can redraw it at (16384,0). By using modulo in your view values you can essentially create the looping effect you want. If you want to go for such an approach, let me know.

There are also lots of ways to optimize such an approach, for example you can break down this big vertex buffer into smaller ones and only render those who are within the view, you can also make your buffer creation code faster, but such things are better left to be done last :)
Not having a 16Kx16K surface would definitely be preferable. I don't know much about shaders, but I've fiddled around with them a bit.

I'm not really attached to any particular approach, but I am looking for the most elegant and efficient solution to my problem. Which is to draw the values of a 512x512 array as 32x32 images, at any point to the screen.
Having a shader determine the location of the vertexes is essentially telling the GPU where to draw stuff, which seems like the most appropriate approach to me.
 
D

Drepple

Guest
Surfaces are quite unstable. If you switch to/from full screen or change the window size everything on the surface will be deleted, and sometimes it even does this at random. Therefore it is unwise to only draw on the surface once unless you save the image right after or convert it to a sprite. The latter is actually a viable option for what you're trying to accomplish, you could draw the sprite multiple times in a grid and it will loop, but I think you'd be much better off just drawing on the surface every step, so that if it gets removed because it's unstable, it will be redrawn right away. Using this method you might as well make the surface only as large as your screen and then draw a grid of images on it, since you redraw the surface every step anyways, you could for example change where on the surface you draw your images every frame and achieve the same effect as moving the surface itself, except it will be much faster and more reliable than using a huge surface.

On top of that, if you're not gonna be using shaders or blend modes you might as well refrain from using surfaces all together. If you want to save the image you could also use one of the screenshot functions. And I can't think of another reason why you would want to use a surface. (Apart from being unreliable, using large/a lot of surfaces is also slow, so you should always draw on the application surface, unless you really need a surface for say, blend modes)
 

vdweller

Member
Not having a 16Kx16K surface would definitely be preferable. I don't know much about shaders, but I've fiddled around with them a bit.

I'm not really attached to any particular approach, but I am looking for the most elegant and efficient solution to my problem. Which is to draw the values of a 512x512 array as 32x32 images, at any point to the screen.
Having a shader determine the location of the vertexes is essentially telling the GPU where to draw stuff, which seems like the most appropriate approach to me.
Alright bro here is the trick for drawing a vertex buffer at any point on the screen.

Create a shader (I named it shader_vertmove), then go to the vertex shader tab and modify it like this (changes from vanilla noted with ASCII arrows <====):
Code:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform float movex; // <====
uniform float movey; // <====

void main()
{
    vec4 object_space_pos = vec4( in_Position.x+movex, in_Position.y+movey, in_Position.z, 1.0); // <====
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
 
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
}
Now when you want to draw the buffer contents, do this:
Code:
shader_set(shader_vertmove);
var mx=shader_get_uniform(shader_vertmove,"movex");
var my=shader_get_uniform(shader_vertmove,"movey");
shader_set_uniform_f(mx, *amount of x displacement* );
shader_set_uniform_f(my, *amount of y displacement* );
vertex_submit( *blah* );
shader_reset();
EDIT: Surfaces are not all that scary and can be very useful.The trick is to always check if it exists before drawing it and, if not, reconstruct it with its appropriate dimensions and contents. However you may run into other unforeseen issues like unexpected alpha blending. It all depends on your knowledge of the medium!
 
Last edited:

Gzebra

Member
Alright bro here is the trick for drawing a vertex buffer at any point on the screen.

Create a shader (I named it shader_vertmove), then go to the vertex shader tab and modify it like this (changes from vanilla noted with ASCII arrows <====):
Code:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform float movex; // <====
uniform float movey; // <====

void main()
{
    vec4 object_space_pos = vec4( in_Position.x+movex, in_Position.y+movey, in_Position.z, 1.0); // <====
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
 
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
}
Now when you want to draw the buffer contents, do this:
Code:
shader_set(shader_vertmove);
var mx=shader_get_uniform(shader_vertmove,"movex");
var my=shader_get_uniform(shader_vertmove,"movey");
shader_set_uniform_f(mx, *amount of x displacement* );
shader_set_uniform_f(my, *amount of y displacement* );
vertex_submit( *blah* );
shader_reset();
EDIT: Surfaces are not all that scary and can be very useful.The trick is to always check if it exists before drawing it and, if not, reconstruct it with its appropriate dimensions and contents. However you may run into other unforeseen issues like unexpected alpha blending. It all depends on your knowledge of the medium!

Thanks, however, I don't know which values my uniforms should be in order to have it wrap around.

I always check if the surface exists, if it does, draw it, else, create it. Anyhow, I'm only using application_surface now, and I use the camera to move around. I hadn't thought of doing it this way, but since cameras are now projection matrices, it works just fine, but I still haven't figured out how to have it wrap around.

I somehow have to tell the shader, that if the cams x and y values are below zero or above view_width + array_length * sprite_size it have to displace some of the vertexes a certain amount based upon the camera position.

Hmm, I could submit the buffer 4 times, with different displacements, then simply change the x and y of the camera.

What I wanna do is something like this:

Where the white border is the view border, the green the world, and the blue,red, and purple squares are the areas that needs to be displaced.
 
Last edited:

vdweller

Member
Even if you repeat the buffer contents, you only solve the tileset problem. I mean, what about the instances moving on these tiles?

I haven't put much though in it, but what about creating multiple views?
One view to move around the camera,
And a view to capture the other side of the border.

You can use view_surface_id[...] to set each view to be drawn to a different surface. Then, when you reach a "border" condition, you complement the image by drawing the view surface of that border. Just a thought, might be worth looking at.

upload_2019-5-5_20-2-51.png

Also, modulo ( mod() ) can help you constrain your view coordinates to what you want.

Just some thoughts.
 
Last edited:

Gzebra

Member
Even if you repeat the buffer contents, you only solve the tileset problem. I mean, what about the instances moving on these tiles?

I haven't put much though in it, but what about creating multiple views?
One view to move around the camera,
And a view to capture the other side of the border.

You can use view_surface_id[...] to set each view to be drawn to a different surface. Then, when you reach a "border" condition, you complement the image by drawing the view surface of that border. Just a thought, might be worth looking at.

View attachment 24648

Also, modulo ( mod() ) can help you constrain your view coordinates to what you want.

Just some thoughts.
I think it can be done using views, but if each view have to have its own surface, then I'll have to have a total of 5 surfaces that's the window size, so most likely 1920x1080, which is kind of a lot.

I have no idea how to use mod() for what you suggest.
 

vdweller

Member
Even in this extreme corner situation like this

upload_2019-5-5_20-52-55.png

You can cover everything with 4 surfaces, which is not a lot.

You will have to look mod() up, it returns the remainder of an integer division. It is useful to wrap a number around another. For example, in degrees, if you mod any number with 360, you always get a number within the range 0-359.

380 mod 360 = 20
12584 mod 360 = 344
12 mod 360 = 12

I suggested it in case it helps to "wrap" the view coordinates around the size you want.
 

Gzebra

Member
Even in this extreme corner situation like this

View attachment 24654

You can cover everything with 4 surfaces, which is not a lot.

You will have to look mod() up, it returns the remainder of an integer division. It is useful to wrap a number around another. For example, in degrees, if you mod any number with 360, you always get a number within the range 0-359.

380 mod 360 = 20
12584 mod 360 = 344
12 mod 360 = 12

I suggested it in case it helps to "wrap" the view coordinates around the size you want.
Ahh, okay, I see, thanks for the explanation :)

I said 5 surfaces 'cause I need 4 surfaces for each segment, plus application_surface.

I'll see if I can figure out a solution using views
 

Gzebra

Member
view_set_surface_id doesn't seem to be doing anything, it's still drawing the view to the application surface. Also, having more than one view visible at a time just makes things weird. I've been trying for hours now, the behavior of views makes absolutely no sense.
 

Gzebra

Member
Alright, so I've managed to get it working, but it's not exactly optimized.

Cam step event
Code:
var xDist=abs(min(0,x))*32
var yDist=abs(min(0,y))*32
var ww=window_get_width()
var wh=window_get_height()

if(keyboard_check(ord("D")) || keyboard_check(ord("A"))){
    x+=keyboard_check(ord("D"))-keyboard_check(ord("A"))
    if(x<-center){x=center}
    if(x>center){x=-center} 
}
if(keyboard_check(ord("S")) || keyboard_check(ord("W"))){
    y+=keyboard_check(ord("S"))-keyboard_check(ord("W"))
    if(y<-center){y=center}
    if(y>center){y=-center} 
}

view_xport[2]=0
view_yport[2]=yDist
view_wport[2]=xDist
view_hport[2]=max(0,wh-yDist)
camera_set_view_size(view_camera[2],min(ww,xDist), max(0,wh-yDist));
camera_set_view_pos(view_camera[2],sSize-(xDist),max(0,y*spe))

view_xport[3]=0
view_yport[3]=0
view_wport[3]=xDist
view_hport[3]=yDist
camera_set_view_size(view_camera[3],min(ww,xDist),min(wh,yDist));
camera_set_view_pos(view_camera[3],sSize-(xDist),sSize-(yDist))
 
view_xport[1]=xDist
view_yport[1]=0
view_wport[1]=ww-xDist
view_hport[1]=yDist
camera_set_view_size(view_camera[1], max(0,ww-xDist),min(wh,yDist));
camera_set_view_pos(view_camera[1],max(0,x*spe),sSize-yDist)

view_xport[0]=xDist
view_yport[0]=yDist
view_wport[0]=ww-xDist
view_hport[0]=wh-yDist
camera_set_view_size(view_camera[0],max(0,ww-xDist),max(0,wh-yDist));
camera_set_view_pos(view_camera[0],max(0,x*spe),max(0,y*spe))
I've tried to optimize it, but I just get weird graphical glitches, also, there are graphical artifacts or something when I set my x and y to center/-center.
 
Top