Enabling and specifying scissoring via RenderState

I added the ability to enable and specify scissoring via RenderState. So for example you would use:

someMaterial.getAdditionalRenderState().setScissorTest(true);
someMaterial.getAdditionalRenderState().setScissorRect(x, y, width, height);

which would be handled by the following code in a Renderer’s applyRenderState() method:

[java]

  •    if (state.isScissorTest()) {
    
  •        if (!context.clipRectEnabled) {
    
  •            glEnable(GL_SCISSOR_TEST);
    
  •            glScissor(state.getScissorX(), state.getScissorY(), state.getScissorW(), state.getScissorH());
    
  •        } else {
    
  •            int scisX = state.getScissorX();
    
  •            int scisY = state.getScissorY();
    
  •            int scisW = state.getScissorW();
    
  •            int scisH = state.getScissorH();
    
  •            ScissorRectangle i = ScissorRectangle.intersect(clipX, clipY, clipW, clipH, scisX, scisY, scisW, scisH);
    
  •            if (i == null) {
    
  •                glScissor(0, 0, 0, 0);
    
  •            } else {
    
  •                glScissor(i.getX(), i.getY(), i.getW(), i.getH());
    
  •            }
    
  •        }
    
  •    } else {
    
  •        if (context.clipRectEnabled) {
    
  •            glScissor(clipX, clipY, clipW, clipH);
    
  •        } else {
    
  •            glDisable(GL_SCISSOR_TEST);
    
  •        }
    
  •    }
    

[/java]

If the scissor test is turned on and GL_SCISSOR_TEST isn’t already enabled, it turns it on and applies the rectangle. If GL_SCISSOR_TEST is already enabled, it sets the rectangle to be the intersection of the desired area and the existing one specified by setClipRect() and clipx/clipy/clipw/cliph. If the scissor test is turned off but GL_SCISSOR_TEST is enabled, it makes sure the area is clipx/clipy/clipw/cliph as specified by setClipRect(). If both are off, it makes sure GL_SCISSOR_TEST is disabled.

ScissorRectangle is just a simple rectangle class that allows a quick intersection test and a way to get the result. This bit does the heavy lifting:

[java]

  • /**

  • * Intersects two rectangles represented by the values that define them.
    
  • * If there is no intersection between them, the result is null.
    
  • * Otherwise it is a new <code>ScissorRectangle</code> representing
    
  • * the intersection.
    
  • *
    
  • * @param x0 the x coordinate of the lower left of the first rectangle
    
  • * @param y0 the y coordinate of the lower left of the first rectangle
    
  • * @param w0 the width of the first rectangle
    
  • * @param h0 the height of the first rectangle
    
  • * @param x1 the x coordinate of the lower left of the second rectangle
    
  • * @param y1 the y coordinate of the lower left of the second rectangle
    
  • * @param w1 the width of the second rectangle
    
  • * @param h1 the height of the second rectangle
    
  • * @return null if there is no intersection otherwise the resulting
    
  • * <code>ScissorRectangle</code>
    
  • */
    
  • public static ScissorRectangle intersect(int x0, int y0, int w0, int h0,

  •                                         int x1, int y1, int w1, int h1)
    
  • {

  •    int left = Math.max(x0, x1);
    
  •    int bottom = Math.max(y0, y1);
    
  •    int right = Math.min(x0 + w0, x1 + w1);
    
  •    int top = Math.min(y0 + h0, y1 + h1);
    
  •    int width = right - left;
    
  •    int height = top - bottom;
    
  •    // If width or height are zero or less, there was no intersection.
    
  •    if ((width &lt;= 0) || (height &lt;= 0)) {
    
  •        return null;
    
  •    } else {
    
  •        return new ScissorRectangle(left, bottom, width, height);
    
  •    }
    
  • }
    [/java]

Is this of any use/merit?

Thanks @Retzinsky, I’m obviously gonna ask for a Pull Request on github, as it’s getting pretty popular these days :wink:

@pspeed,@Momoko_Fan what do you think. Wouldn’t that give a way to have an easy clipRect api for GUI?

@nehon said: Thanks @Retzinsky, I'm obviously gonna ask for a Pull Request on github, as it's getting pretty popular these days ;)

@pspeed,@Momoko_Fan what do you think. Wouldn’t that give a way to have an easy clipRect api for GUI?

I can’t speak to the implementation but being able to have scroll areas without using a viewport or special materials would be really nice.

I submitted a pull request. I’m pretty new to all this, so hopefully I didn’t make a hash of it. If anything is wrong please let me know.

I like this but I have 4 concerns:

  1. It calls glScissor() each time even if the rect didn’t change at all (when the RenderState.scissorTest=false but scissor is enabled on the Renderer itself).
  2. It allocates an object (ScissorRectangle) for every object every frame … It adds up, especially on mobile devices which have bad garbage collectors.
  3. If context.clipRectEnabled = false then its not enabled … no need to keep calling glDisable(GL_SCISSOR_TEST).
  4. Inconsistent terminology. On the RenderState its called ScissorRect, on the Renderer its called ClipRect, but they are the same thing.

I think adding a renderStateClipRectEnabled field to RenderContext will help reduce state changes, also comparing the state’s x,y,w,h values to the ones from the intersection will help avoid unneccessary glScissor() calls if they didn’t change.

I haven’t looked at the code recently, but I suspect you can look at how alpha test and related settings (test func, falloff) were handled for an example of not needlessly setting state. I think they were finally all unwound properly.

@Momoko_Fan said: I like this but I have 4 concerns: 1) It calls glScissor() each time even if the rect didn't change at all (when the RenderState.scissorTest=false but scissor is enabled on the Renderer itself).

You’re right, that’s my bad. I’ll fix that.

2) It allocates an object (ScissorRectangle) for every object every frame .. It adds up, especially on mobile devices which have bad garbage collectors.

Only when both are enabled and the intersection is needed but I absolutely get what you’re saying. I’ll put a ScissorRectangle field in the renderer to collect the value when needed. Is there a better solution than that?

3) If context.clipRectEnabled = false then its not enabled .. no need to keep calling glDisable(GL_SCISSOR_TEST).

Again, my bad. Will fix it.

4) Inconsistent terminology. On the RenderState its called ScissorRect, on the Renderer its called ClipRect, but they are the same thing.

I’ll change that and bring it all in line.

I think adding a renderStateClipRectEnabled field to RenderContext will help reduce state changes, also comparing the state's x,y,w,h values to the ones from the intersection will help avoid unneccessary glScissor() calls if they didn't change.

Agreed.

Looks like I have some fiddling to do :slight_smile: Thanks for the constructive criticism.

I took another run at this today and it’s quite a minefield of overlapping states!

As above, the naming used is unified now. Everything is a “clip” something. Also, there’s no longer an object allocation. Those were the two easy things to get right… The rest was quite perplexing. I ended up making wider changes than I expected. I added renderStateClipRectEnabled to RenderContext but I also did away with clipX/clipY/clipW/clipH in the renderer and replaced it with a ClipRectangle object for ease of use. There are now several such ClipRectangles.

currentClipRect- should always match exactly whatever glScissor is set to.
rendererClipRect - contains the dimensions of the clip rectangle desired by the renderer (as set via Renderer.setClipRect())
renderStateClipRect - contains the dimensions of the clip rectangle desired by RenderState (as set via Material.setClipRect())
intersectionClipRect - a place to hold the result of intersecting the above two ClipRectangles

The code in applyRenderState() is as follows. I’ve annotated it here.

[java]
// if RenderState desires GL_SCISSOR_TEST to be enabled.
if (state.isClipTest()) {
// Update the desired renderStateClipRect dimensions.
renderStateClipRect.set(state.getClipX(), state.getClipY(), state.getClipW(), state.getClipH());
// If GL_SCISSOR_TEST is already enabled via setClipRect().
if (context.clipRectEnabled) {
// If RenderState clipping was not flagged as enabled, flag it so.
if (!context.renderStateClipRectEnabled) {
context.renderStateClipRectEnabled = true;
}
// Both this Renderer and RenderState want to set the clip rectangle. The result
// should be the intersection of these two ClipRectangles. Calculate the intersection
// and whether it is valid.
if (ClipRectangle.intersect(rendererClipRect, renderStateClipRect, intersectionClipRect)) {
// The intersection is valid, if the actual current clip area does not have this value,
// update it.
if (!currentClipRect.equals(intersectionClipRect)) {
int iClipX = intersectionClipRect.getX();
int iClipY = intersectionClipRect.getY();
int iClipW = intersectionClipRect.getW();
int iClipH = intersectionClipRect.getH();
currentClipRect.set(iClipX, iClipY, iClipW, iClipH);
glScissor(iClipX, iClipY, iClipW, iClipH);
}
} else {
// There was no intersection. Set the current clip area to zero if it isn’t already.
if (currentClipRect.getX() != 0 || currentClipRect.getY() != 0 ||
currentClipRect.getW() != 0 || currentClipRect.getH() != 0) {
currentClipRect.set(0, 0, 0, 0);
glScissor(0, 0, 0, 0);
}
}
// If GL_SCISSOR_TEST was not already enabled via setClipRect().
} else {
// and it was also not enabled via RenderState, enable GL_SCISSOR_TEST and flag
// that Renderstate clipping is enabled.
if (!context.renderStateClipRectEnabled) {
context.renderStateClipRectEnabled = true;
glEnable(GL_SCISSOR_TEST);
}
// If the current clip area does not match that desired by RenderState, update it.
if (!currentClipRect.equals(renderStateClipRect)) {
int rsClipX = renderStateClipRect.getX();
int rsClipY = renderStateClipRect.getY();
int rsClipW = renderStateClipRect.getW();
int rsClipH = renderStateClipRect.getH();
currentClipRect.set(rsClipX, rsClipY, rsClipW, rsClipH);
glScissor(rsClipX, rsClipY, rsClipW, rsClipH);
}
}
// If RenderState does not want any clipping.
} else {
// If GL_SCISSOR_TEST is enabled via setClipRect().
if (context.clipRectEnabled) {
// and also via RenderState.
if (context.renderStateClipRectEnabled) {
// Flag that RenderState clipping has been disabled.
context.renderStateClipRectEnabled = false;
}
// If the current clipping area does not match that specified via setClipRect(),
// update it.
if (!currentClipRect.equals(rendererClipRect)) {
int rClipX = rendererClipRect.getX();
int rClipY = rendererClipRect.getY();
int rClipW = rendererClipRect.getW();
int rClipH = rendererClipRect.getH();
currentClipRect.set(rClipX, rClipY, rClipW, rClipH);
glScissor(rClipX, rClipY, rClipW, rClipH);
}
// If GL_SCISSOR_TEST is disabled via clearClipRect().
} else {
// If RenderState clipping is enabled. Disable it and disable GL_SCISSOR_TEST,
// nobody wants it.
if (context.renderStateClipRectEnabled) {
context.renderStateClipRectEnabled = false;
glDisable(GL_SCISSOR_TEST);
}
}
}
[/java]

I can submit another pull request if required, but these changes are only included in LwjglRenderer currently since this definitely needs another pair of eyes to make sure I’m not doing anything stupid like earlier.

1 Like

I updated this. Hopefully my changes fix the issues you had @Momoko_Fan. I also updated the message on my pull request to make clear my intentions with regard to the modifications made.

Was this merged into the engine? If yes, in which version can we find it? :smile:

This was not merged into the engine. I closed it because I’d fundamentally changed how it worked. I took some time out today to clean it up though and submit a new pull request. I’ve been using the new version for a while in my custom GUI project without any issues. It now works at the spatial level rather than via renderstate. Use spatial.enableClipping(x, y, width, height) and spatial.disableClipping() to turn it on and off.

1 Like

@Momoko_Fan: Any way this gets merged into the mainline?
Or at least what are general plans regarding clipping?

Hi guys,

Any update on clipping without doing it in shaders?
I’m writting my own GUI lib and i need clipping for scroll areas but i can’t force users to implement the clipping thing in all of their materials.

The problem with the approach in the patch that we’ve received is that it does it right on the Geometry. This is problematic because a) it’s a 2D only effect, and b) a Geometry can be rendered in multiple viewports and clipping is viewport specific. It ends up causing more problems than it solves, really.

I’ve been trying to think of a way to make viewports more accessible for this sort of feature because it’s something I need for Lemur also. The basic approach would be to have the scroll panels use a viewport that contains their children. As is this would work fine without any changes to JME. It’s just that it’s kind of hard to setup and the math/management to make the viewport follow the scroll panel’s pixel size, move around, etc… is not as trivial as it could be.

It could be that for Lemur, I end up writing a utility class that manages a viewport (and it’s camera) on behalf of a scroll pane or any other element that needs to do clipping.

Hum, right. It’s problematic for 3D in game gui…
Your solution is pretty Interesting, so the effect still be in 2D but relative to the panel viewport…only affecting its children.

Do you already have started something or?

Anyway, thanks for the hint :smiley:

I went round and round trying to make the patch work somehow and just couldn’t. I’ve been thinking about other solutions for a few weeks now. I plan to experiment on a ViewportControl.

In the general case, the trickiest part is giving it everything it needs. Lemur already has a GuiGlobals to hold singleton stuff about the 2D UI… so it should be pretty straight forward.

…then it’s just math.

Hmm, not sure that I like the GuiGlobals approach at all. I mean, it is global state! :smile: It is like using LibGDX instead of JME3! :wink:

Yes, but in a UI you generally only have one user, one interface. So a singleton makes a lot of things more convenient. (GuiGlobals is a singleton that can be replaced by the application if they like.)

It makes it really convenient for being able to create GUI elements like in every other real life GUI API… without having to pass Application or AssetManager to everything.

…And i’m really sucks in math x).

I will give it a try :p. thanks.

PS: and yes gui singleton could be really handy so i agree with paul :slight_smile: