The Java Cafe

The Java Cafe is a community of 3,665 amazing users

Java and Cloud content, by the community for the community

Create new account Log in
loading...

"Bad Code" Code Review

summerspittman profile image Hoyt Summers Pittman ・5 min read

I’ve been working on a blog post about implementing perlin noise. It is going to be a gentle explanation of how to generate multidimensional, gradient noise images. I plan for that post to have links, citations, explanations, etc. while also showcasing good code. However, on the way to good code we often write a lot of bad code, and that is what this blog post is about.

image10

Above is my first implementation written in Java. The image shows a software rendered noise generated image that looks a bit like a lava lamp and when in motion, it is a calm, soothing visualization running at…

image1

Ouch… Ok let's get those numbers up.

Perlin noise averages random values at each pixel to create a gradient,assigns that gradient to a color, and draws the pixel. It is also embarrassingly parallelizable1.

My renderer works by drawing pixels left to right, top to bottom. I implemented this common dual loop pattern as two IntStreams. Each stream contributes an x, and a y coordinate, then those are passed to my Perlin object along with a third coordinate z. The perlin function returns a number between 0 and 1 which is mapped to a specific color.

image9

In Java, streams can be run parallel which causes them to be scheduled on different threads and thus (if Java behaves) run on multiple cores.I made the outer loop parallel2 by replacing

    IntStream.range(0,width).forEach(
Enter fullscreen mode Exit fullscreen mode

with

IntStream.range(0,width).parallel().forEach(
Enter fullscreen mode Exit fullscreen mode

Now our screen should render with the full force of my Intel i7 8700k's 12 virtual cores and at the very least the app should be rendering at 30 fps. Now to hit the run button to find out….

image3

… Well frack. Hopefully I'm at least not burning my computer to the ground…

image6

Double frack. How did I end up burning through 70% of my CPU but only doubled my framerate? In this example the CPU draws images to main memory and then transfers that image to the GPU to be displayed.3 Much of our CPU time is consumed with transferring data around and not displaying or generating that data. By running my code on the GPU directly then this transfer step is removed, I free up the CPU, and I get more performance.

And now a snag : GPUs run shader programs written in a language called GLSL. This means porting my Perlin class to GLSL and adding code to my app to load it onto the GPU. This exercise also turned into porting the application to Android and rewriting the UI Kotlin.4

image4

Oh hey that's pretty nice! What about performance?

image5

Sweet, 60 fps. All it took was cargo culting c code into Java code, marrying that with GLSL code, and then shoving it into an Android activity.5

Also, how's the CPU performance on our Android device?6

image8

Hey! 6% of CPU resources and 60 FPS, this seems like a big win.

So where's the bad code we were promised? Well, here's a sample of our shader.


"float fade(in float t) {\n" +
"    return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);      // 6t^5 - 15t^4 + 10t^3\n" +
"}\n" +
"\n" +
"\n" +
"int inc(in int num) {\n" +
"    num++;\n" +
"    return num;\n" +
"}\n" +
"\n" +
"float lerp(in float a,in float b,in float x) {\n" +
"    return a + x * (b - a);\n" +
"}\n" +...


---
Enter fullscreen mode Exit fullscreen mode

I borrowed my Android implementation from the Android Developers OpenGL ES docs. Their example code supplies a basic framework for loading shaders and displaying geometry. Their examples used a Kotlin file with the GLSL program stored as string constraints, and I followed their pattern.78 If this weren't an experiment I would have these strings be externalized into a file, but this IS an experiment though so let's just leave it in a fume hood and move on.

In addition to being a GLSL demonstration, this is also an Android application. In Android, an application has to obey the Activity lifecycle9. This lifecycle gives the Android operating system the ability to start, stop, background, hibernate, and kill any application in the system. It is your responsibility to make sure your app handles these events correctly.

Notice how I said "YOUR app", my app handles these with good ole fashioned neglect.


class MainActivity : AppCompatActivity() {

   private lateinit var gLView: GLSurfaceView
   public override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)

       // Create a GLSurfaceView instance and set it
       // as the ContentView for this Activity.
       gLView = PerlinSurfaceView(this)
       setContentView(gLView)
   }

}


--------
Enter fullscreen mode Exit fullscreen mode

That's the whole main action. Don't expect it to behave, it was raised in a barn by a drunken lout.10 The code gets worse from here, feel free to take a look on my github and laugh or weep as appropriate.

So what did I learn from this experiment and the mountain of radioactive code it produced? A lot actually! This project sets up the GPU on an Android device, loads GLSL code into the shaders, and RUNS IT! I also have a better idea of how I should managed source files, have gotten a sharpening of my Kotlin and C skills, and remembered that OpenGL is an insane monster that justifies the prices game developers pay for Unity.

That's it for today guys, keep coding and have fun.

Notes


  1. Yes that is a real term https://en.wikipedia.org/wiki/Embarrassingly_parallel 

  2. Why don't I also add parallel to the inner loop as well? It turns out the extra synchronization overhead will make everything run slower not faster. Remember, measure before you optimize and compare to that! 

  3. I don't actually know the nuances of how Java gets buffered image data to the GPU on Linux, but it gets us to our next point. 

  4. Why Android? Because Android's OpenGL examples have code I can copy/paste. 

  5. Don't worry, you're going to get to see this tire fire.  

  6. Yes, you can just shell into an Android phone and run top. 

  7. This is called "cargo cult programming". It is what happens when you blindly copy code from the internet into your application without caring if it is good or not. 

  8. Remember how I said the shader was C code that I "ported" to Java and then to GLSL? Well now it is Kotlin code too. That taste in your mouth is either bile or blood. 

  9. Image credit Google "A simplified illustration of the activity lifecycle."  

  10. That's me. I'm that lout. 

Discussion (0)

pic
Editor guide