Hacker News new | ask | show | jobs
by datenwolf 3698 days ago
> When writing my first OpenGL code, I was stunned by the amount of boilerplate required.

Huh, actually the amount of boilerplate required for getting a triangle to the screen with OpenGL is minimal.

- Create a context - fill a buffer with the triangle vertex data - load buffer to OpenGL - create vertex shader implementing vertex to position transformation - create vertex shader implementing pixel coloirization - issue draw commands

2 comments

And yet you've just described a decent amount of boilerplate.

The fact that future iterations of OpenGL and OpenGL ES have nuked the fixed function pipeline is sort of annoying. I'm not saying there should be more codepaths in the implementation, but if there were a default compiled fixed function vertex shader and fragment shader, it would be helpful.

What you've just described for rendering one primitive includes compiling two shaders, linking arguments, and specifying byte arrays before issuing commands, when this task used to be as simple as:

    glBegin(GL_TRIANGLES);
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(0.0f, 1.0f, 1.0f);
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-0.866f, -0.5f, 1.0f);
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(0.866f, -0.5f, 1.0f);
    glEnd();
Being able to draw a triangle in eight lines of code is much more encouraging to someone wanting to make a 3D game for mobile or the web than an estimated 50 lines, including two shaders they had to borrow from a textbook.

I would understand if immediate mode were gone, which might be better regardless. Still, things should be as simple as a glDrawArrays call without compiling shaders.

Not to mention, when working as a solo game developer, shaders feel like an orphan between artists and programmers.

> when this task used to be as simple as:

a) Immediate Mode has been out of fashion for since before the turn of the century. Even the good old NVidia GeForce2 of 1998 already had OpenGL extensions that are essentially vertex buffer objects; you can in fact use the GL_ARB_vertex_buffer_object extension on a GeForce2. If you don't believe me, I'm currently headed to my mother's and I have a old box stored in her cellar with a GeForce2 in it; I could give interested parties a SSH into it (you just have to live with a old Gentoo installation that I didn't touch for some 10 years or so).

b) There's a certain complexity cutoff where the whole shader loading boilerplate plus shader code is less, than what it takes to setup an equivalent fixed function pipeline setup. Making an educated estimation I'd say, that the break even point is, when enabling a register combiner on two textures, one a cube map, the other a normal+shininess map, setting DOT3 combining of normal with vertex secondary color (for normal mapping), directing the normal map into the cubemap texture coordinate lookup and fading between reflection and dull shader based on shininess. Sounds complex? Indeed it is, but keep in mind that this kind of thing was already possible with the GeForce3 (at least, I think it may even work on the GeForce2, but after over 14 years since writing the last time a program targeting it, I'm a bit shaky on the details).

Anyway, to set up this kind of register combining you'd need between 4 to 5 calls of glTexEnvi per texture. Another 3 glTexEnvi calls for setting up the secondary color muxing mode and another 3 calls for setting the final stage fading mode. Add to that the hours of twisting your brain to figure trying to wrap your mind around all the relevant state switches in the register combiners.

With shaders you simply write down what you want.

Yes, I'm aware of how both immediate mode and vertex arrays work in OpenGL.

The problem, and this is even getting away from what the article addresses, is that a green developer approaching the application must learn an entirely new language (shaders) before drawing a single primitive. In mattbee's example, what is the beginner supposed to think of the line "#version 410" or "in vec3 vp" which is not even C code?

I'm also not saying that all of the legacy functions from the fixed-function pipeline should be maintained, with all of their specific parameters, but that those attributes should be bound with a default shader that supports all of the same capabilities. So, using GLSL attribute accessors with each platform having the same default shader (and default parameter names). Then, wrap the IM example in arrays, call a few binds, and make a draw call. That's much simpler than writing two embedded programs inside your first program. I think it would have been very beneficial if OpenGL ES had been rolled out with a design like this.

Maybe it's because graphics programming is so feature focused that it creates a problem. For me, the issue is the obstacles between getting shapes on the screen, especially in contexts like WebGL or mobile.

The beginner needs to rise to the task.

GLSL isn't complex at all, least of all because its problem domain is basically pure math.

It should take less than an afternoon to understand how to write a shader if they have any familiarity with math.

If they don't know math, they shouldn't be doing shader programming until they learn.

Writing a fixed function shader is to learning graphics programming what writing a Makefile is to learning C++. I'm not saying it's not important, or not important in the long run, but what introductory programming book would start out showing how to use a linker instead of how to write a program? In this case things are compounded by the fact that there is no linker, which means the programmer has to shuffle around string programs at runtime.

I agree, though, that shader writing should help with learning 3D math. Saying GLSL is not complex is a bit of an overstatement, though. The language is simple, but the fact is that GLSL doesn't behave like C with certain statements, and not knowing everything will make things complicated.

You've also described a chicken-and-egg problem. Can't make a 3D app till you learn GLSL, can't use GLSL until you make a 3D app.

It also feels absurd to be writing any static language code wrapped in a string, in JavaScript, to be sent to a GPU and then compiled. The user doesn't have newlines, let alone syntax highlighting. Sure, most WebGL developers will just use Three.js but then they aren't really learning anything, anyway, and we still have these patchwork solutions.

I disagree that it's inappropriate for beginners, and comparing it to a build system is incorrect.

Shaders are a step in the pipeline. Beginners should be learning the pipeline:

1. Geometry is instantiated and draw parameters set.

2. Geometry vertices are transformed by a vertex shader.

3. Transformed vertices in primitive are used to sample across its geometry, producing pixel fragments.

4. Pixel fragments are transformed by a fragment shader.

5. Transformed fragments are written to a framebuffer.

That's the whole thing, and GLSL neatly handles 2-4. Step 1 is a kinda pain in the ass, but not terrible. Step 5 is usually simple, but no worse than 1. We shouldn't be protecting users from dealing with the (simple) facts of life in a graphics pipeline.

As for Javacsript not having multi-line strings...that's hardly the fault of WebGL, and honestly the simplicity of passing around strings means that as the language gets more interesting support (for interpolations and whatever) the API will be unaffected.

> but what introductory programming book would start out showing how to use a linker instead of how to write a program?

A good one!

Just take a glance over at StackOverflow and consider how many questions there essentially boil down to lack of understanding how the pieces in the compiler toolchain fit together:

http://lmgtfy.com/?q=stackoverflow.com%20undefined%20referen...

In workshop class a teacher will also start with explaining how to properly handle a tool before showing how to apply said tool to a problem.

It's staggering! The above is immediate-mode code which is what's gone and that's how a lot of people learned OpenGL 1-3.

Anton Gerdelans's tutorials are the best I've found and just look at how much typing there is:

Code:

https://github.com/capnramses/antons_opengl_tutorials_book/b...

Explanation:

http://antongerdelan.net/opengl/hellotriangle.html

Once you can get through that mess, the effort/reward ration seems to be a bit more incremental, but the triangle program a pretty good illustration of that first cliff, and how much it's grown.

Fundamentally, the biggest problem with the fixed-function model and the vertex-plotting calls in the "red book" is they don't scale. They just don't. :(

There's a reason OpenGL ES threw most of that baby and bathwater out---it's not to diminish the footprint of the library on storage (even in an embedded device, memory's cheap for long-term storage). It's that those function calls are each a stack setup, library operation, stack teardown.... Per function call. You're more-or-less tripling the cost of describing your vertices.

We can ignore that cost on desktop machines (well, we COULD, if people didn't want to play the latest and greatest games in CRYENGINE or what have you ;) ). The cost was not ignorable in time-constrained, power-constrained architectures.

Simpler to reason about, but flatly the wrong solution for performant graphics on a coprocessor.

My argument is not to keep the interfaces and functionality of the OLD pipeline, but to maintain a reasonable, fixed-function shader that is a default out-of-the-box. The most general purpose fragment/vertex shader for drawing a triangle and maybe texturing it is 120 lines top, which actually compiled in the driver would be a couple of hundred bytes. The real cost of not including that feature, or including it in the software library, is preventing new developers from easily experimenting with 3D programming in the browser and on portable devices, which is a fairly large loss of creativity in an emerging area.
> My argument is not to keep the interfaces and functionality of the OLD pipeline, but to maintain a reasonable, fixed-function shader that is a default out-of-the-box.

There's very little use of such a catch-all shader in a driver. This is a very narrow requirement and only newbies and super simple programs would benefit from it. Providing such a default shader fits more in a companion library like GLU, that could also cover things like image pyramid generation and generating meshes for primitive solids.

Well, we have WebGL but we don't have WebGLU.

It's shitty.

That's what I'm saying.

And the worst part about it is that the shaders have to be stored as strings. It's not as if it's possible to write shaders in JavaScript (even though it could be possible), so it's really an outrageous expectation when using WebGL from square one to have to write GLSL shaders inline in this specific language (to put a triangle on the screen). Even streaming them as text resources, there is not an easy way to write or debug a fragment shader in the browser that isn't stroke inducing. In fact, it doesn't seem that there's a reasonable way to do that AT ALL.

Your "very little use" case covers about 10,000 snippets that could be easy to share if they didn't end up looking like this, first result from Google for "Pastebin Webgl": http://pastebin.com/uiHGium5

Compare a decently developed web standard like HTML 5 video: http://pastebin.com/znbHgG3r

Wow, a 40 line demo that is easy to demonstrate.

The whole domain of 3D programming is full of such arrogant programmers that it's no wonder that everything continues to be difficult even 20 years on. It takes expert knowledge. Why even bother using a graphics card or language, when it's trivial for a programmer to write their own software rasterizer? This is obviously how someone should learn to program, if they're going to do it the RIGHT WAY.

Spare me.

Or go on beating that dead horse.
I think you missed:

- spend a few hours working out why your display window just shows black

> - spend a few hours working out why your display window just shows black

I hardly ever had this kind of problem… and I'm doing OpenGL programming for almost two decades now. Yes, when implementing a new shader (idea) I often am greeted with a blank window, too. This is where the "stringiness" of OpenGL is actually a huge benefit.

See, you don't have to recompile your program just to change things in the shader. In fact in development builds of my OpenGL programs I regularly place a filesystem monitor on each shader source file, so that the shader gets automatically reload and recompiled whenever the file changes. Combine that with a GLSL compiler error log parser (the GLSL compiler will emit helpful diagnostics) for your favourite editor and you can quickly iterate your development cycles.

Just to give you an idea of how well this works and how quickly you can develop with OpenGL if you know how to do it properly: Past February our company showed our new realtime videorate 4D-OCT system at Photonics West/BiOS expo. The first day of the expo, about 10 minutes before opening my CEO asked me, how difficult it would be to hack a variance visualization into it. Since we had a developer build of the software on the demo system I could make the changes in-situ in the running program. It took me less than 5 minutes to implement that new display mode shader… from scratch. Considering that even the smallest changes in the program C++ files would still require a at least 10s long linking stage and that after each launch of the program it takes about 2 minutes for the program to go through all the required calibration I couldn't have done it that quickly.

----

For those wondering how to effectively debug shaders: First get the vertex shader working, only then start with the fragment shader. Until the vertex shader doesn't work use the following as fragment shader:

out vec4 o_fragcolor; void main() { o_fragcolor = vec4(1.,0.,0.,1.); }

i.e. a solid red. For debugging the vertex shader you may introduce some varyings that you pass into the fragment color. Once you've got the vertex shader doing what you want it to do you can tackle the fragment shader.

And always remember: Don't link the shader text hard into your program binary. Load them from external files instead and have a way to signal a reload.

There's nothing intrinsic to graphics work there, you're just talking about content-driven development which is common in a bunch of domains.

Your not hitting the black-screen since you've been doing it self-admitted for 20 years. Kudos, good for you.

The problem is getting fresh-blood in who have a hunger to learn but don't have a deep understanding of shaders, pipelines, vertices, etc. It's really easy to make a trivial mistake in that boiler plate(I've seen someone fight something for hours until they thought to turn of culling, they just had their winding wrong).

Case in point from one of the foremost figures in the domain:

https://twitter.com/id_aa_carmack/status/370205518532329472

PIX was about the most sane step in the direction of fixing this, sadly tools seem to have slid backwards of late.

Your advice is sound, if maddening (it's maddening to have to debug a black box; I've hit bugs in shader compilers before that had me tearing my hair out. Things should not be fixable by multiplying a quantity by 1.0 in a sane world ;) ).
Ha, the things I've seen shader compilers do. One of the funniest (or so I think) was how the NVidia shader compiler messed up its intermediary assembly code:

The NVidia GLSL compiler (which is based on the Cg compiler), to this date, first compiles from GLSL into assembly as specified by the GL_ARB__program extensions plus extra instructions specified in the GL_NV__program extensions. Nothing wrong with that. But what's wrong is applying the LC_NUMERIC locale onto floating point literals emitted into the assembly code, because if your system happens to be set to a locale where the decimal separator is a comma (,) and not a period (.) the following assembly stage will interpret the commas as list separators. NVidia's beta drivers of November 2006 for their then newly released G80 GPUs had this very bug (it also happens that I did report it first, or so I think); NVidia got the report just in time to apply an in-situ workaround before the next non-beta driver release (at least so I was told back then).