Pure GML Perlin Noise Function

M

mothteeth

Guest
Hi.

I am keen to discuss *pure GML* implementations of Perlin Noise, Simplex Noise, and similar variants. I was just making a feature request over in the Tech Support forum, for a native Perlin Noise function in GML but it sounds like it's not really being considered. I posted this noise function over there, but I thought I'd post it over here in case it is useful to someone. It works fine, but it not as fast as I would like. If anyone has any other 2D noise code that might run faster, I'd be keen to share. :)

Here we go, semi quoting the other post...

This is the GML Fast Noise function I am using. I don't believe it is true perlin noise, but rather "value noise", which looks similar. This function was translated from a Java language source file I found on the internet. It was convenient to translate because it is a single function call with no external requirements such as arrays of data etc (which some noise functions require.)

If anyone has any suggestions for speeding this up in GML, please let me know :)

UPDATE: Improved compatibility with different versions of GameMaker based on feedback below...

Code:
//FastNoise(x, y, nbOctave)
///FastNoise(x, y, nbOctave)
// Source of original Java version:
// http://www.java-gaming.org/index.php?topic=23771.0
/*
* This file is part of 3DzzD http://dzzd.net/.
*
* Released under LGPL
*
* 3DzzD is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 3DzzD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with 3DzzD.  If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2005 - 20010 Bruno Augier
*/
/**
 * Fast perlin noise generation
 *
 * Generate a perlin noise with 8 octave and a persistence of 0.5
 *
 * NB:
 * - output range between 0 and 255
 * - maximum octave = 7
 *
 * you can change type of noise between grad & value noise by commenting/uncommenting block
 * you can change type of interpolation between bicubic/bilinear by commenting/uncommenting block
 */ 
var nx = argument0;
var ny = argument1;
var nbOctave = argument2;
var result=0;   
var frequence256=256;
var sx=floor((nx)*frequence256);
var sy=floor((ny)*frequence256);
var octave=nbOctave; 
while(octave!=0)
{
   var bX=sx & $FF;
   var bY=sy & $FF;
   var sxp=sx>>8;
   var syp=sy>>8;
     
   //Compute noise for each corner of current cell
   var Y1376312589_00=syp*1376312589;
   var Y1376312589_01=Y1376312589_00+1376312589;
   var XY1376312589_00=sxp+Y1376312589_00;
   var XY1376312589_10=XY1376312589_00+1;
   var XY1376312589_01=sxp+Y1376312589_01;
   var XY1376312589_11=XY1376312589_01+1;
   var XYBASE_00=(XY1376312589_00<<13)^XY1376312589_00;
   var XYBASE_10=(XY1376312589_10<<13)^XY1376312589_10;
   var XYBASE_01=(XY1376312589_01<<13)^XY1376312589_01;
   var XYBASE_11=(XY1376312589_11<<13)^XY1376312589_11;
   var alt1=(XYBASE_00 * (XYBASE_00 * XYBASE_00 * 15731 + 789221) + 1376312589) ;
   var alt2=(XYBASE_10 * (XYBASE_10 * XYBASE_10 * 15731 + 789221) + 1376312589) ;
   var alt3=(XYBASE_01 * (XYBASE_01 * XYBASE_01 * 15731 + 789221) + 1376312589) ;
   var alt4=(XYBASE_11 * (XYBASE_11 * XYBASE_11 * 15731 + 789221) + 1376312589) ;
     
   /*
   *NOTE : on  for true grandiant noise uncomment following block
   * for true gradiant we need to perform scalar product here, gradiant vector are created/deducted using
   * the above pseudo random values (alt1...alt4) : by cutting thoses values in twice values to get for each a fixed x,y vector
   * gradX1= alt1&0xFF
   * gradY1= (alt1&0xFF00)>>8
   *
   * the last part of the PRN (alt1&0xFF0000)>>8 is used as an offset to correct one of the gradiant problem wich is zero on cell edge
   *
   * source vector (sXN;sYN) for scalar product are computed using (bX,bY)
   *
   * each four values  must be replaced by the result of the following
   * altN=(gradXN;gradYN) scalar (sXN;sYN)
   *
   * all the rest of the code (interpolation+accumulation) is identical for value & gradiant noise
   */
       
       
   /*START BLOCK FOR TRUE GRADIANT NOISE*/
     
   var grad1X=(alt1 & $FF)-128;
   var grad1Y=((alt1 >> 8) & $FF)-128;
   var grad2X=(alt2 & $FF)-128;
   var grad2Y=((alt2 >> 8) & $FF)-128;
   var grad3X=(alt3 & $FF)-128;
   var grad3Y=((alt3 >> 8) & $FF)-128;
   var grad4X=(alt4 & $FF)-128;
   var grad4Y=((alt4 >> 8) & $FF)-128;
       
       
   var sX1=bX>>1;
   var sY1=bY>>1;
   var sX2=128-sX1;
   var sY2=sY1;
   var sX3=sX1;
   var sY3=128-sY1;
   var sX4=128-sX1;
   var sY4=128-sY1;
   alt1=(grad1X*sX1+grad1Y*sY1)+16384+((alt1 & $FF0000)>>9); //to avoid seams to be 0 we use an offset
   alt2=(grad2X*sX2+grad2Y*sY2)+16384+((alt2 & $FF0000)>>9);
   alt3=(grad3X*sX3+grad3Y*sY3)+16384+((alt3 & $FF0000)>>9);
   alt4=(grad4X*sX4+grad4Y*sY4)+16384+((alt4 & $FF0000)>>9);
       
   /*END BLOCK FOR TRUE GRADIANT NOISE */
     
     
   /*START BLOCK FOR VALUE NOISE*/
   /*/
   alt1 &= $FFFF;
   alt2 &= $FFFF;
   alt3 &= $FFFF;
   alt4 &= $FFFF;
   //*/
   /*END BLOCK FOR VALUE NOISE*/
     
     
   /*START BLOCK FOR LINEAR INTERPOLATION*/
   //BiLinear interpolation
   /*/
   var f24=(bX*bY)>>8;
   var f23=bX-f24;
   var f14=bY-f24;
   var f13=256-f14-f23-f24;
   var val=(alt1*f13+alt2*f23+alt3*f14+alt4*f24);
   //*/
   /*END BLOCK FOR LINEAR INTERPOLATION*/
     
     
     
   //BiCubic interpolation ( in the form alt(bX) = alt[n] - (3*bX^2 - 2*bX^3) * (alt[n] - alt[n+1]) )
   /*START BLOCK FOR BICUBIC INTERPOLATION*/
   //*/
   var bX2=(bX*bX)>>8;
   var bX3=(bX2*bX)>>8;
   var _3bX2=3*bX2;
   var _2bX3=2*bX3;
   var alt12= alt1 - (((_3bX2 - _2bX3) * (alt1-alt2)) >> 8);
   var alt34= alt3 - (((_3bX2 - _2bX3) * (alt3-alt4)) >> 8);
     
     
   var bY2=(bY*bY)>>8;
   var bY3=(bY2*bY)>>8;
   var _3bY2=3*bY2;
   var _2bY3=2*bY3;
   var val= alt12 - (((_3bY2 - _2bY3) * (alt12-alt34)) >> 8);
     
   val*=256;
   //*/
   /*END BLOCK FOR BICUBIC INTERPOLATION*/
     
     
   //Accumulate in result
   result+=(val<<octave);
     
   octave--;
   sx = sx << 1;
   sy = sy << 1;
     
}
result = result >> (16+nbOctave+1);
return result;
 
Last edited by a moderator:
I

icuurd12b42

Guest
I used noise for my InfinitLander terrain!!!

This is a good read
http://www.gmlscripts.com/forums/viewtopic.php?id=1960
and this
http://www.gmlscripts.com/forums/viewtopic.php?id=1962

Not sure why those aren't part of the gmlscripts.com's listed functions yet. very helpful

I would push for noise functions in gml's engine... but since we can now compile in yyc the advantage of this is not necessarily obvious.

The obvious use for noise is terrain... but it can be so useful for other things!

Noise is an essential part of making games for a naturalistic looking set of random number progression which currently everyone unaware of noise function are simply using one of the random() functions

I use it in TMC LUX for the lights scones and the campfire... As such I would really push for having the feature in gml. it can make things really easy and it belongs in the language as mush as random() belongs in a game making language.

Another use of it is for randomized movement for cheap and convincing movement for things like birds and flies and particle or other thing similar.

assuming noise(x,y,z) returns 0 to 1...
t = 0;
base_seed = random(100);
....
x += (noise(based_seed+t,based_seed,0)-.5) * 2;
y += (noise(based_seed,based_seed+t,0)-.5) * 2;
t+=.01;
or
x = (noise(based_seed+t,based_seed,0) *2-1) * room_width;
y = (noise(based_seed,based_seed+t,0) *2-1) * room_height;
t+=.01;

Or having an AI weighing in on a decision... like say to decide it's "mood" to attack or retreat
var attack = sign((noise(based_seed+t,0,0)-.5) * (point_distance(x,y,player.x,player.y) <100);
if(attack)
{
move_toward_point(x,y,4);
}
else
{
move_toward_point(x,y,-4);
}
t+=.01;
 
Last edited by a moderator:
M

mothteeth

Guest
Hi @icuurd12b42, thanks for the reply.

Thanks for the links. Somehow google had not lead me to that GML Improved Perlin script. I'll try it out and tell you how it compares.

I'm currently using Perlin-like noise to generate 2d Caves. I find Perlin noise useful for so many things, which is why I thought it might be a nice addition to the engine. A built in function would be the fastest, and I think a lot of developers would use it and appreciate it.

In your experience, does YYC end up running at something closer to native speeds, compared with the VM build? This evening I ironed out some YYC issues I had and built my game, and found that the noise function was not really running all that much faster. It takes about a second to generate a 400 x 400 block of noise. I still need to do some more detailed profiling of the overhead of calling scripts and that sort of thing. I expected a bigger speed up to be honest, as I had seen one the last time I built with it.

I just switched to using this pure GML simplex noise function from the Marketplace:

https://marketplace.yoyogames.com/assets/1617/simplex-noise

The author, Binksified, has released the code on the forum, but I think it's well worth supporting his efforts for a mere $1 purchase. I contacted the author and he sent me a small application for converting the extension to Game Maker 2, which was very handy. This code runs a bit faster than the one I posted above, which is nice. Every bit counts. The code has a GPL license though, which might be an issue for a commercial game. For that reason I might have to keep looking.

Right now I am generating a large map (can go bigger than the largest Terraria map :) doing chunks of 16 x 16 tiles per frame. It takes a while for the world to be built but the player can play as soon as the local area is generated. In future I'll create a more optimal system to measure the framerate and only use available clock cycles. That pretty much overcomes the problem of it being slow, but obviously more speed would be good!

By the way, TMC LUX looks like a pretty great extension. :)
 
I

icuurd12b42

Guest
Well the code you posted was not fonctionnal yet... here
Code:
///FastNoise(x, y, nbOctave)
// Source of original Java version:
// http://www.java-gaming.org/index.php?topic=23771.0
/*
* This file is part of 3DzzD http://dzzd.net/.
*
* Released under LGPL
*
* 3DzzD is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 3DzzD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with 3DzzD.  If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2005 - 20010 Bruno Augier
*/
/**
 * Fast perlin noise generation
 *
 * Generate a perlin noise with 8 octave and a persistence of 0.5
 *
 * NB:
 * - output range between 0 and 255
 * - maximum octave = 7
 *
 * you can change type of noise between grad & value noise by commenting/uncommenting block
 * you can change type of interpolation between bicubic/bilinear by commenting/uncommenting block
 */  
var nx = argument0;
var ny = argument1;
var nbOctave = argument2;
var result=0;    
var frequence256=256;
var sx=floor((nx)*frequence256);
var sy=floor((ny)*frequence256);
var octave=nbOctave;  
while(octave!=0)
{
    var bX=sx & $FF;
    var bY=sy & $FF;
    var sxp=sx>>8;
    var syp=sy>>8;
       
    //Compute noise for each corner of current cell
    var Y1376312589_00=syp*1376312589;
    var Y1376312589_01=Y1376312589_00+1376312589;
    var XY1376312589_00=sxp+Y1376312589_00;
    var XY1376312589_10=XY1376312589_00+1;
    var XY1376312589_01=sxp+Y1376312589_01;
    var XY1376312589_11=XY1376312589_01+1;
    var XYBASE_00=(XY1376312589_00<<13)^XY1376312589_00;
    var XYBASE_10=(XY1376312589_10<<13)^XY1376312589_10;
    var XYBASE_01=(XY1376312589_01<<13)^XY1376312589_01;
    var XYBASE_11=(XY1376312589_11<<13)^XY1376312589_11;
    var alt1=(XYBASE_00 * (XYBASE_00 * XYBASE_00 * 15731 + 789221) + 1376312589) ;
    var alt2=(XYBASE_10 * (XYBASE_10 * XYBASE_10 * 15731 + 789221) + 1376312589) ;
    var alt3=(XYBASE_01 * (XYBASE_01 * XYBASE_01 * 15731 + 789221) + 1376312589) ;
    var alt4=(XYBASE_11 * (XYBASE_11 * XYBASE_11 * 15731 + 789221) + 1376312589) ;
       
    /*
    *NOTE : on  for true grandiant noise uncomment following block
    * for true gradiant we need to perform scalar product here, gradiant vector are created/deducted using
    * the above pseudo random values (alt1...alt4) : by cutting thoses values in twice values to get for each a fixed x,y vector
    * gradX1= alt1&0xFF
    * gradY1= (alt1&0xFF00)>>8
    *
    * the last part of the PRN (alt1&0xFF0000)>>8 is used as an offset to correct one of the gradiant problem wich is zero on cell edge
    *
    * source vector (sXN;sYN) for scalar product are computed using (bX,bY)
    *
    * each four values  must be replaced by the result of the following
    * altN=(gradXN;gradYN) scalar (sXN;sYN)
    *
    * all the rest of the code (interpolation+accumulation) is identical for value & gradiant noise
    */
         
         
    /*START BLOCK FOR TRUE GRADIANT NOISE*/
       
    var grad1X=(alt1 & $FF)-128;
    var grad1Y=((alt1 >> 8) & $FF)-128;
    var grad2X=(alt2 & $FF)-128;
    var grad2Y=((alt2 >> 8) & $FF)-128;
    var grad3X=(alt3 & $FF)-128;
    var grad3Y=((alt3 >> 8) & $FF)-128;
    var grad4X=(alt4 & $FF)-128;
    var grad4Y=((alt4 >> 8) & $FF)-128;
         
         
    var sX1=bX>>1;
    var sY1=bY>>1;
    var sX2=128-sX1;
    var sY2=sY1;
    var sX3=sX1;
    var sY3=128-sY1;
    var sX4=128-sX1;
    var sY4=128-sY1;
    alt1=(grad1X*sX1+grad1Y*sY1)+16384+((alt1 & $FF0000)>>9); //to avoid seams to be 0 we use an offset
    alt2=(grad2X*sX2+grad2Y*sY2)+16384+((alt2 & $FF0000)>>9);
    alt3=(grad3X*sX3+grad3Y*sY3)+16384+((alt3 & $FF0000)>>9);
    alt4=(grad4X*sX4+grad4Y*sY4)+16384+((alt4 & $FF0000)>>9);
         
    /*END BLOCK FOR TRUE GRADIANT NOISE */
       
       
    /*START BLOCK FOR VALUE NOISE*/
    /*/ 
    alt1 &= $FFFF;
    alt2 &= $FFFF;
    alt3 &= $FFFF;
    alt4 &= $FFFF;
    //*/
    /*END BLOCK FOR VALUE NOISE*/
       
       
    /*START BLOCK FOR LINEAR INTERPOLATION*/
    //BiLinear interpolation
    /*/
    var f24=(bX*bY)>>8;
    var f23=bX-f24;
    var f14=bY-f24;
    var f13=256-f14-f23-f24;
    var val=(alt1*f13+alt2*f23+alt3*f14+alt4*f24);
    //*/
    /*END BLOCK FOR LINEAR INTERPOLATION*/
       
       
       
    //BiCubic interpolation ( in the form alt(bX) = alt[n] - (3*bX^2 - 2*bX^3) * (alt[n] - alt[n+1]) )
    /*START BLOCK FOR BICUBIC INTERPOLATION*/
    //*/
    var bX2=(bX*bX)>>8;
    var bX3=(bX2*bX)>>8;
    var _3bX2=3*bX2;
    var _2bX3=2*bX3;
    var alt12= alt1 - (((_3bX2 - _2bX3) * (alt1-alt2)) >> 8);
    var alt34= alt3 - (((_3bX2 - _2bX3) * (alt3-alt4)) >> 8);
       
       
    var bY2=(bY*bY)>>8;
    var bY3=(bY2*bY)>>8;
    var _3bY2=3*bY2;
    var _2bY3=2*bY3;
    var val= alt12 - (((_3bY2 - _2bY3) * (alt12-alt34)) >> 8);
       
    val*=256;
    //*/
    /*END BLOCK FOR BICUBIC INTERPOLATION*/
       
       
    //Accumulate in result
    result+=(val<<octave);
       
    octave--;
    sx = sx << 1;
    sy = sy << 1;
       
}
result = result >> (16+nbOctave+1);
return result;
Looking at it compared to another one I peeked at, this one uses old school bit shifting and nothing complex for the smoothing... so in theory it should be very efficient. but perhaps not as versatile as the one you mentioned.

Pure faster code is what YYC was made for. so this (function) should be almost as efficient as any compiled program
 
M

mothteeth

Guest
@icuurd12b42 - Was there a problem with the noise function in the earlier version of GameMaker? It was working in GameMaker 2. Based on your changes I'm guessing that support for Hex numbers in the "0xF00" format is new in GM2. I also found that the function broke in YYC, which was one reason I switched to the Simplex one I linked to above. I've noticed that YYC seems to have some problems with the ternary / conditional operator. It sometimes produces some kind of type ambiguity. This can lead to some very cautious rewriting of code being imported from other languages.

Thanks for the feedback and testing. Cheers!
 
I

icuurd12b42

Guest
the yyc is definitely broken on this function. It's probably using the wrong version of a function or operator. It can probably be fixed by adding a few well placed ().
@xot also gave me some simplified noise functions for infinitLander. I'm not sure if they are on his site somewhere. the one I finally used is essentially a single row noise field... definitely worth sharing
 
I

icuurd12b42

Guest
I also tried yyc on the noise function documented here
http://www.gmlscripts.com/forums/viewtopic.php?id=1962

and it also fails as you increase z...

That function (on it's own) is much faster than the FastNoise function posted...

On that note I decided to write my own cheap noise function, derived from the systems I tried and the technique I used in infinitLander... one that hopefully will work under yyc by removing all them bitwise operators...
 

RujiK

Member
About a year ago I made a vary simple noise generation script where speed was my number one goal. I called it "Circular Sum Noise" because it simply adds random circles to a ds_grid.

Pros:
  • Fast. About 1 second for a 512x512 grid.
  • Incredibly easy to understand
  • Convincing Noise

Cons:
  • The ds_grid will either need to be resized or have special care for the edges. Out of bounds errors will happen otherwise.
  • Values vary wildly and need to be normalized before use.

The "Spacing" value is how close circles are added. The further the spacing, the faster the generation but the more obvious the faults of this algorithim. Notice the spacing and the generation time for the image below.


And here is the generation code, complete with normalizing the values. Very simple stuff.

Code:
var _grid = argument0;
var width = ds_grid_width(_grid);
var height = ds_grid_height(_grid);

//CHANGE THESE VARIABLES FOR DIFFERENT LOOKS!
var rad_min = 7; //radius
var rad_max = 15;
var spacing = global.num;



var val = 0;
var xx=rad_max;
var yy=rad_max;

while (yy < (height - rad_max)) {
    while (xx < (width - rad_max)) {
         val=irandom(255);
         ds_grid_add_disk(_grid,xx,yy,random_range(rad_min,rad_max),val);
         xx+=spacing;
         }
    yy+=spacing;
    xx=rad_max;
    }

 
 
//GENERATION DONE! But max value is unknown. Normalize values
var grid_max = (ds_grid_get_max(_grid,rad_max*2,rad_max*2,width - rad_max*2,height - rad_max*2) + 1);
var grid_min =  ds_grid_get_min(_grid,rad_max*2,rad_max*2,width - rad_max*2,height - rad_max*2);

var normal = 255;
xx=0;
yy=0;

while yy < height { // loop through grid and set range from to 0 - normal (normal is set above)
    while xx < width {
         val = floor((ds_grid_get(_grid,xx,yy)- grid_min) * normal / (grid_max - grid_min));
         ds_grid_set(_grid,xx,yy,max(val,0));
         xx+=1;
         }
    yy+=1;
    xx=0;
    }
Although it's functionally nothing like CPU-based Perlin noise, it creates a similar results at a much faster pace.
 

chance

predictably random
Forum Staff
Moderator
... it creates a similar results...
Similar yes... but the similarities may be superficial. Looking at the examples, I sense lots of small-scale content. Probably an artifact of the "disk" hard edge. That could be a problem, depending on the application. Of course, you could always apply a smoothing filter, or use a Gaussian "kernel" instead of a flat disk. Either way, you don't have much control over the spectral content.

One remedy would be adding the noise in multiple layers -- starting with low frequency (large scale) and moving to high frequency. And you could adjust the relative amplitudes of each layer. This allows you to tailor the spatial scales and correlation length of the final result. And it would still be very fast.

Regardless, your approach is a clever quickie solution when spectral content isn't that important. +1
 

RujiK

Member
@icuurd12b42 @chance

Definately true. It's not good for generating on the fly as it doesn't add well to already existing terrain. I suppose a more accurate statement would be that my noise can create a 2d height map that looks somewhat similar to a perlin height map.

It is possible to have more control over the noise if instead of adding irandom(255) you use value_to_add = irandom(distance_to_whatever). Here's something I generated using 5 "peaks" for the distance check.


But still, even with a few peak seeds it's lacking all the bells and whistles that perlin has. It's probably closer in functionality to midpoint displacement noise.

Also @chance adding noise in multiple layers is a clever idea. It would be using a pseudo-octave system of sort.
 
I

icuurd12b42

Guest
OK, I tweaked the functions from xot's post on the gmlscripts forum, mixing some code with a few reference site and optimized the grad funct. and added the perlin_octave_noise which was almost completely implemented via the fbm, again on xot's site.

https://www.dropbox.com/s/76ntu8t2wy7o0zm/PerlinAndLinearNoise.gmz?dl=0

Please note that there are issues with the perlin stuff if you state to use the 3rd dimention...

upload_2017-4-29_21-5-46.png

upload_2017-4-29_21-6-17.png

upload_2017-4-29_21-9-59.png

upload_2017-4-29_21-10-54.png

Yeah, dynamically drawing a 150x150 region with draw_primitive, 0 fps at that level of detail... it certainly ain't for live use... I'm really surprised... watching The Coding Train's example in p5.js He's doing something like 3000x3000 point on a fraken laptop. something stinks here. I commented out the drawing... the perlin noise is 99% of the step event and the grad function is taking most of the cpu cycle... and the fps is still 0 without drawing


On a brighter note, I ALSO put up a linear_noise function for 1d noise which is highly efficient and also supports multiple instantiations so you can have multiple fields, it's sort of the system I used in infinitLander, just a little better at randomizing the data

upload_2017-4-29_21-4-43.png
 
Last edited by a moderator:

GMWolf

aka fel666
I would say that this sort of workload is best left to the GPU.
Write a compute shader, draw it to a surface and get the buffer from it, voila, super fast noise!


I did see an implementation of simplex noise in GML on the older forums, and used it, it was really quite fast. (All things considered). I cannot find it at the moment but will let you know if i do.

@RujiK the main advantage of gradient noise iver your method is that once computed, you can still go in and increase the resolution by sampling point in between.
It also allows you to generate different chunks of the map at different times, and still keep them seamless.
Both things that cannot be done with "rujik noise"
 
Last edited:
I

icuurd12b42

Guest
I would say that this sort of workload is best left to the GPU.
Write a compute shader, draw it to a surface and get the buffer from it, voila, super fast noise!
That "Fast Noise" is useless because the only way to read the content is to either use surface_getpixel or transfer the data to a buffer... both slow as hell. drawing clouds is not the point of a noise function. its use expands way beyond that
 

RujiK

Member
@icuurd12b42
Have you used buffer_get_surface() and is it equally slow? It's a relatively new command and I was under the impression that it was reasonably fast. I haven't used it myself.


@Fel666
I agree with you. That's why I said almost the exact same thing in an earlier post :p
@icuurd12b42 @chance
[My Noise] is not good for generating on the fly as it doesn't add well to already existing terrain. I suppose a more accurate statement would be that my noise can create a 2d height map that looks somewhat similar to a perlin height map.
 
I

icuurd12b42

Guest
@icuurd12b42
Have you used buffer_get_surface() and is it equally slow? It's a relatively new command and I was under the impression that it was reasonably fast. I haven't used it myself.
yes, it was slower than calling get_pixel in a xx,yy loop... who knows they might have improved its performance. Apparently it's fast in gms2 according to mike who used it in his lemming recreation, but obviously he was not calling this each step...

My issue is with the yyc version of the gml perlin working as though it's the gosh darn 80's :)
I modified this codepen example to display as much as I do in YYC. (150x150).. as far as I can tell, this code runs smoothly... it's start to fail at around 800x800. but much better than 1 fps

http://codepen.io/anon/pen/pPPgbQ
 

chance

predictably random
Forum Staff
Moderator
yes, it was slower than calling get_pixel in a xx,yy loop... who knows they might have improved its performance. Apparently it's fast in gms2
Yes, it's VERY fast in GM2, compared to the old get_pixel functions. Not something you want to do each step, necessarily. But you could.

I've been making height fields for "terrain fly-through" apps. I draw a 256 x 256 sprite (noise image) to a surface, then use buffer_get_surface to grab the pixel color data. Then read the buffer contents into a height array -- i.e. z(x,y), to create a vertex buffer and render it.

Haven't timed the process, but any lag is imperceptible.
 

GMWolf

aka fel666
yes, it was slower than calling get_pixel in a xx,yy loop...
Really? When in tried it back in 1.4 I found it to be much, much faster than get_pixel when dealing with anything larger than 16Ă—16 image.
Perhaps this is Hardware dependent?.

drawing clouds is not the point of a noise function. its use expands way beyond that
You don't have to draw clouds in a shader you know.
Generally, you will want to call the method more than once. A shader is good at that. If you need to generate something other than clouds, nothing is stopping you from writing this behavior into the shader.

If you only need to call it a handful of times, then performance really should not be an issue on CPU.
 
I

icuurd12b42

Guest
The surface to buffer may be hardware dependant, true... I mean it needs to download the texture from the gpu back to memory. and possible some hardware can't do this in any other form than 1 pixel at a time.

Well obviously the perlin noise is used (other than in context of graphical effects) to generate continuous 2d or 3d field of data in order to create from such data something that feels natural, like level generation is where it shines...

the continuous aspect allow traversing/exploring such field and possibly be used to generate a limitless world, like say minecraft. but the issue at hand is that it is slow in gml and since gml is not multi threaded, you can't have a thread working on generating terrain... so, in that case you need to generate very tiny chunks, lets say 10x10, else your gameplay would jitter when exploring something unexplored.

as for the other use I mentioned, natural movement of AI, that can be accomplished in a simpler noise system.

I'm sorry for sidetracking the discussion with the performance bitching. I'm not ignoring the GPU solution proposed, it's just that this discovery feel pretty odd that the YYC version of the same perlin code runs orders of magnitude lower than a javascript equivalent. and the location of where it crawls in the code is where YYC should actually shine! See the perlin_grad function is called about as many times as lerp, yet accounts for most the slowdown in the process, while lerp's cost is 30 times less... and what it does is so simple, it makes no sense.
 

GMWolf

aka fel666
I'm sorry for sidetracking the discussion with the performance bitching. I'm not ignoring the GPU solution proposed, it's just that this discovery feel pretty odd that the YYC version of the same perlin code runs orders of magnitude lower than a javascript equivalent. and the location of where it crawls in the code is where YYC should actually shine! See the perlin_grad function is called about as many times as lerp, yet accounts for most the slowdown in the process, while lerp's cost is 30 times less... and what it does is so simple, it makes no sense.
Ill give you my trick to fix this problem: Anything more complex than an action platformer or top down shooter I make in another language.
 

Mike

nobody important
GMC Elder
It's funny.... I was sitting watching from the sidelines waiting for you all to notice shaders would be better for this :)

The smaller the surface, the quicker you'll grab it. In a game, only generate what you need - even if that's a strip down the side.

Also, to avoid stalls of any kind, generate the surface and wait a couple of frames, then grab it. This will avoid any GPU stall as there is no chance the surface will be in use. Use a few surfaces and rotate the generation of them so you're always grabbing an unused one.

You'll b hard pressed to get anywhere near the speed a shader can do this.
 

GMWolf

aka fel666
It's funny.... I was sitting watching from the sidelines waiting for you all to notice shaders would be better for this :)

The smaller the surface, the quicker you'll grab it. In a game, only generate what you need - even if that's a strip down the side.

Also, to avoid stalls of any kind, generate the surface and wait a couple of frames, then grab it. This will avoid any GPU stall as there is no chance the surface will be in use. Use a few surfaces and rotate the generation of them so you're always grabbing an unused one.

You'll b hard pressed to get anywhere near the speed a shader can do this.
Ah, some great info here!

Its quite likely that my dissertation project next year will use a lot of shader - generated noise, and I had not even thought about doing that!

What would be nice too would be to have access to the IGPU for this. (Freeing up the dedicated GPU for graphics).
The unified ram would also make it faster for the CPU to fetch that data (I presume. I'm actually not sure about that).
Naturally it would be ridiculous for YYG to put time into something like this, but perhaps writing a GPU noise DLL could be worth it.
 
I

icuurd12b42

Guest
It's funny.... I was sitting watching from the sidelines waiting for you all to notice shaders would be better for this :)
Post 16 onward. Knowledge of perlin using the shader is (sortof) implied by the topic title as well, well that was my conclusion anyway :)
The smaller the surface, the quicker you'll grab it. In a game, only generate what you need - even if that's a strip down the side.

Also, to avoid stalls of any kind, generate the surface and wait a couple of frames, then grab it. This will avoid any GPU stall as there is no chance the surface will be in use. Use a few surfaces and rotate the generation of them so you're always grabbing an unused one.

You'll b hard pressed to get anywhere near the speed a shader can do this.
That is obviously true, but as implied after post 16, any shader based method, as efficient as a shader would be to generate the noise, may have its benefits nullified by the fetching of the result from the target surface through the surface to buffer mechanism + the buffer read functions...

Here's the big item question.
Is a gml YYC compiled based perling_noise(x,y,z) fetching a region in a loop (therefore generating the values one at a time) really slower than setting the render output to a surface (pre allocated with all the caution this implies), therefore generating a batch of values, draw with a perlin shader, reset the draw output, transfer it to a buffer, read the buffer in a loop...?

Now, imo, it's too soon to tell as the YYC version is not producing the right output so there is definitely some possibilities there are slowdowns in there based on the yyc code erroring. You can get the gmz I posted and give it a whirl.


Ah, some great info here!

Its quite likely that my dissertation project next year will use a lot of shader - generated noise, and I had not even thought about doing that!

What would be nice too would be to have access to the IGPU for this. (Freeing up the dedicated GPU for graphics).
The unified ram would also make it faster for the CPU to fetch that data (I presume. I'm actually not sure about that).
Naturally it would be ridiculous for YYG to put time into something like this, but perhaps writing a GPU noise DLL could be worth it.
That's interesting!
 

GMWolf

aka fel666
Is a gml YYC compiled based perling_noise(x,y,z) fetching a region in a loop (therefore generating the values one at a time) really slower than setting the render output to a surface (pre allocated with all the caution this implies), therefore generating a batch of values, draw with a perlin shader, reset the draw output, transfer it to a buffer, read the buffer in a loop...?
i think it depends on the size of the sample.
but not only is GML quite slow, Shaders really do excel at parallel workflows. i would say anything larger than 128 by 128 is definitely worth doing on GPU. event 64 by 64 is probably worth it.
Doing it on GPU also frees up the CPU to do other tasks, like processing the perlin data.
 
I

icuurd12b42

Guest
I Agree fel666 but this this freeing the CPU for other tasks is not applicable in gml
 
I

icuurd12b42

Guest
Is it not?
No it's not.

try
surface_set_target(...);
shader_set(perlin_shdr);
draw_rectangle(...);
shader_reset();
surface_reset_target();
buffer_get_surface(...);
Not freeing cpu for other tasks when you are sitting on yo ass waiting for the draw to complete...
 

GMWolf

aka fel666
I was under the impression that whilst the rendering thread did its stuff, the GM engine would be doing input poling and all, but this is just speculation.
We need @Mike for some insight on GM.
 
M

mothteeth

Guest
Hi everyone, thanks for all your responses. There is some interesting information here! I'd definitely be keen to see those noise scripts if you can find the links. It would be cool to identify which are fastest, and which are suitably licensed for use in a commercial game. For the average user in the community, who just wants something they can use easily and quickly for some light procgen, I think this would be widely appreciated.

In relation to rendering noise using shaders... I'm wondering if that's an option in my situation. A shader is good for creating a visualisation of the noise, but I am not generally using the noise to create graphics at all. I'm using it for procedural generation, in a variety of ways, for things like probability, density, and object displacement, so the output of the noise function never usually ends up being drawn directly onto the screen. As suggested above, maybe I could use a shader to quickly generate big blocks of noise and read them out as needed. I'll definitely look into that possibility. Thanks for the discussion and tips on this subject.

I have been generating a custom tile map about the size of a large Terraria map, so I definitely need to generate a lot of noise! My system of generating chunks in the background is working just fine now, as long as the character doesn't "catch up" to the edge of the world, but anything I could do to speed it up would be desirable.

Cheers!
 

Mike

nobody important
GMC Elder
I was under the impression that whilst the rendering thread did its stuff, the GM engine would be doing input poling and all, but this is just speculation.
We need @Mike for some insight on GM.
GM is single threaded, only Audio really runs on separate threads.
 

GMWolf

aka fel666
GM is single threaded, only Audio really runs on separate threads.
Is that right? Any plans for this to change? It would make sense for input polling and box2D for example, to be moved to its own thread.
 
R

renex

Guest
I wrote a perlin noise generator in 2009, for game maker 7.

It's utter garbage now but at the time it was fast enough to generate a new cloud texture every level load...

Code:
#define main
var s, result, n, xx, yy, fac, lev, sz, pres, randomap;

sz=min(512,max(64,argument0));
pres=min(32,max(4,argument1));
lev=240-min(100,max(0,argument2))*1.8;
texture_set_interpolation(1);
s=surface_create(sz,sz);
result=surface_create(sz,sz);
n=1;
do {
surface_set_target(s);
draw_clear(0);
xx=0;
yy=0;
repeat (pres) {repeat (pres) {a=round(random(lev));
draw_point_color(xx,yy,make_color_rgb(a,a,a));
xx+=1;
} xx=0;
yy+=1;
}
randomap=background_create_from_surface(s,0,0,pres,pres,0,0);
draw_clear(0);
draw_background_tiled(randomap,0,0);
background_delete(randomap);
surface_set_target(result);
draw_set_blend_mode(bm_add);
fac=min(sz,n*pres/2);
draw_surface_part_ext(s,0,0,fac,fac,0,0,sz/fac,sz/fac,$ffffff,1/n);
draw_set_blend_mode(0);
n*=2;
} until (n*pres>sz*2);
surface_set_target(s);
draw_clear(0);
draw_surface(result,0,0);
draw_set_blend_mode(bm_add);
draw_surface_ext(result,0,0,1,1,0,$ffffff,0.4);
surface_set_target(result);
if (argument3=-1) {
draw_clear($ffffff);
} else {draw_set_blend_mode(0);
draw_background_stretched(argument3,0,0,sz,sz);
} draw_set_blend_mode(bm_subtract);
draw_surface(s,0,0);
draw_set_blend_mode(0);
surface_reset_target();
n=background_create_from_surface(result,0,0,sz,sz,0,0);
a=background_create_color(sz,sz,$ffffff);
background_set_alpha_from_background(a,n);
background_delete(n);
surface_free(s);
surface_free(result);
return a;
It's unreadable, but I remember it working by adding surfaces together at different scales and then using a blending trick to crop the lower half of the spectrum to transparency. It was considerably faster than doing anything in pure gml, and the result was pretty decent for a simple cloud effect.
CloudTex.png
In fact, I still use this same cloud texture to this day... I've grown attached to it :)
 
Top