openframeworks / openFrameworks

openFrameworks is a community-developed cross platform toolkit for creative coding in C++.
http://openframeworks.cc
Other
9.97k stars 2.55k forks source link

Feature Proposal: ofDrawBackgroundGrid #5217

Open bakercp opened 8 years ago

bakercp commented 8 years ago

Hi all, just wanted to get some feedback on an idea and possible implementation. It is sometimes useful to have a gridded / checkerboard background (e.g. like Photoshop). Since we have ofBackgroundGradient, it seems like it would be easy enough to also have a simple grid background.

Here's a very quick and dirty mesh-based solution (that is likely using way too many mesh points and the wrong mesh mode ... but anyway).

void ofDrawBackgroundGrid(float size, const ofColor& onColor, const ofColor& offColor)
{
    float w = ofGetViewportWidth(), h = ofGetViewportHeight();
    gridMesh.clear();
    gridMesh.setMode(OF_PRIMITIVE_TRIANGLES);
#ifndef TARGET_EMSCRIPTEN
#ifdef TARGET_OPENGLES
    if(ofIsGLProgrammableRenderer()) gridMesh.setUsage(GL_STREAM_DRAW);
#else
    gridMesh.setUsage(GL_STREAM_DRAW);
#endif
#endif

    std::vector<glm::vec3> verts;

    for (std::size_t y = 0; y < 3; ++y)
    {
        for (std::size_t x = 0; x < 3; ++x)
        {
            verts.push_back({x * size, y * size, 0.f});
        }
    }

    std::vector<ofColor> colors = { onColor, offColor };

    float twoSize = size * 2;

    for (std::size_t y = 0; y < h; y += twoSize)
    {
        for (std::size_t x = 0; x < w; x += twoSize)
        {
            glm::vec3 offset(x, y, 0.f);

            for (std::size_t i = 0; i < 2; ++i)
            {
                gridMesh.addVertex(verts[i + 0] + offset);
                gridMesh.addColor(colors[i]);
                gridMesh.addVertex(verts[i + 3] + offset);
                gridMesh.addColor(colors[i]);
                gridMesh.addVertex(verts[i + 4] + offset);
                gridMesh.addColor(colors[i]);
                gridMesh.addVertex(verts[i + 0] + offset);
                gridMesh.addColor(colors[i]);
                gridMesh.addVertex(verts[i + 4] + offset);
                gridMesh.addColor(colors[i]);
                gridMesh.addVertex(verts[i + 1] + offset);
                gridMesh.addColor(colors[i]);

                std::size_t j = colors.size() - i - 1;

                gridMesh.addVertex(verts[i + 3] + offset);
                gridMesh.addColor(colors[j]);
                gridMesh.addVertex(verts[i + 6] + offset);
                gridMesh.addColor(colors[j]);
                gridMesh.addVertex(verts[i + 7] + offset);
                gridMesh.addColor(colors[j]);
                gridMesh.addVertex(verts[i + 3] + offset);
                gridMesh.addColor(colors[j]);
                gridMesh.addVertex(verts[i + 7] + offset);
                gridMesh.addColor(colors[j]);
                gridMesh.addVertex(verts[i + 4] + offset);
                gridMesh.addColor(colors[j]);
            }
        }
    }

    GLboolean depthMaskEnabled;
    glGetBooleanv(GL_DEPTH_WRITEMASK,&depthMaskEnabled);
    glDepthMask(GL_FALSE);
    gridMesh.draw();
    if(depthMaskEnabled){
        glDepthMask(GL_TRUE);
    }
}

Seems to me that this would probably be good to just implement using a shader, but was curious to get other thoughts on that -- including for compatibility. I'm assuming background gradient could also be done w/ a shader, so I wasn't sure why it was implemented as a mesh (and reconstructed each time, not just on window resize).

Anyway your feedback is appreciated.

Changed method to ofDrawBackgroundGrid

bakercp commented 8 years ago

untitled-1___33_3___layer_1__rgb_8_

arturoc commented 8 years ago

ofBackgroundGradient is already kind of problematic, it's the only function in ofGraphics that directly does gl calls making it harder to maintain but also changes the global state and does a glGet call that can slow things down.

There's even some edge cases in which it doens't work properly and people expect it to work the same as ofBackground

i think this kind of calls would work better as ofDrawGradient and ofDrawTransparencyGrid or some similar name or even ofGetGradient or ofMesh::gradient or something like that.

That way you can just draw them and be sure to disable depth testing or whatever needs to be disabled in every specific case.

bakercp commented 8 years ago

Good thoughts. I agree with the idea of moving the gradient (and potentially the grids, etc) to ofMesh or similar. It makes more sense and makes the functions more useful generally (I was just needing to draw the same grid in a smaller area). I'll think on it some more and see if others have feedback as well.

arturoc commented 8 years ago

probably in ofPath is what makes more sense but not sure would need to think about it a bit more too

bakercp commented 8 years ago

Yeah, it's not a clear call for either ofPath or ofMesh -- ofPath has all of the nice commands for moving a position around in arcs, curves, etc, but doesn't do much in the way of fills. Likewise, ofMesh is good as a powerful low-level data structure and I wouldn't want to pollute it too much with grids or shading. Anyway ... more thought needed. It's easy enough draw grids on one's own at the moment, so it doesn't seem urgent.

tgfrerer commented 8 years ago

A grid primitive could be useful - and could fit with the other primitives (like plane/icosphere/etc).

I agree, keeping the ofMesh implementation- (and graphics-API) agnostic is important, ofMesh is easier to read and more versatile if it stays a CPU-side data-container class. It is mostly used as a staging ground for GPU uploads. There's a similar pattern in ofPixel, and ofPath.

I feel a bit less enthusiastic about adding new global graphics methods - they are essentially a promise made on behalf of any (future) renderers in advance, and can be a pain to maintain.

For ofDrawBackgroundGradient alone, there are a bunch of open issues: #3201 , #5043, and an ancient one dating back 4 years: #955.

The "meta-issue" here is maybe not that proposals are missing to solve the problem - but that each attempt at a solution might have unforseen consequences, because the context of execution of any global graphics method cannot be controlled: Global draw state gets damaged inadvertently, elements of current global draw state are ignored, API calls with may be illegal in the current context are issued. Its really hard to test such involved, but global methods.

I'd suggest we keep global graphics methods to a bare minumum, and:


In terms of rendering, GL_STREAM_DRAW is in this case probably not the most fitting memory usage hint. It works best when vertices change with every draw - which here doesn't need to be the case. With this API hint the OpenGL driver may prefer to place the vertex memory in CPU+GPU visible memory, so it can avoid transferring it to GPU-only memory. CPU visible memory (="Host-visible") memory is usually slower and there's less of it, unless on an integrated graphics card (like an intel gpu), where memory is often unified.

That said, since OpenGL drivers take usage hints only as recommendations, there's a good chance that the driver treats the memory as static after a few frames.

It could be an idea to store the mesh in a vbo - and draw it with its own (constant) viewprojection matrix (that is in screen space) - and only re-creating the mesh on window resizes. You could also draw the mesh at the very end and use a depth test where the mesh is drawn/rasterised only where there is no depth value in the depth buffer, but that might be overengineering it =)

arturoc commented 8 years ago

thinking a bit more about it i'm more inclined to put this and gradient background into ofPath.

think of ofPath as anything you would do in illustrator, inkscape or any 2d vector graphics and ofMesh as anything you would do in cinema, blender or any other 3d software :)

in the long term i think we should explore some option for 2d drawing that has an immediate mode kind of api but that instead of drawing right away would accumulate the primitives in 1 mesh and draw them at once. similar to ofPath but instead of having 1 vbo per path have 1 vbo for everything drawn during one frame.

tgfrerer commented 8 years ago

Not sure whether it's possible to make a clear cut distinction between ofMesh and ofPath along the 2d/3d line - by consequence, ofMesh::plane might have to move to ofPath, too =)

How about seeing ofMesh as responsible for anything that is concrete, and tessellated (and is built of triangles/primitives) - while ofPath defines abstract perimeters, that's lines and curves and shapes, which need to be tessellated or traced to become GPU drawable primitives?

It could be interesting to have generators for primitives in abstract (path) or tessellated (mesh) contexts.

Yeah, +1 for a batching 2D graphics context. Doesn't ofPath, with its close following of the SVG standard, do this to a certain degree?

arturoc commented 8 years ago

Not sure whether it's possible to make a clear cut distinction between ofMesh and ofPath along the 2d/3d line - by consequence, ofMesh::plane might have to move to ofPath, too =)

it's not so much 2d/3d as the type of graphics, you can still create a plane in blender but not a sphere in illustrator (or if you can is just a projected vector graphics) and although you can create beziers in a 3d software is not the kind of primitive you work with there. a gradient definitely belongs in something like illustrator/inkscape.

the main difference probably is that ofPath stores primitives that are vectorial in the sense that you could zoom in infinitely without loosing detail on curves... which is pretty much how software like illustrator do things as opposed to 3d edition software which work with vertices, normals... similar to how ofMesh works