Skip to content

Commit 2720868

Browse files
authored
Fix stencil testing remaining enabled after it is disabled (#2987)
* Fix stencil enable-state caching and preserve parameter updates * Remove redundant stencil disable test
1 parent a99b007 commit 2720868

3 files changed

Lines changed: 255 additions & 3 deletions

File tree

‎jme3-core/src/main/java/com/jme3/renderer/RenderContext.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,22 @@ private static <T> WeakReference<T>[] newWeakReferenceArray(int size) {
331331
* Stencil test function for back-facing polygons.
332332
*/
333333
public RenderState.TestFunction backStencilFunction;
334+
/**
335+
* Stencil reference value for front-facing polygons.
336+
*/
337+
public int frontStencilReference;
338+
/**
339+
* Stencil reference value for back-facing polygons.
340+
*/
341+
public int backStencilReference;
342+
/**
343+
* Stencil comparison mask for front-facing polygons.
344+
*/
345+
public int frontStencilMask;
346+
/**
347+
* Stencil comparison mask for back-facing polygons.
348+
*/
349+
public int backStencilMask;
334350

335351
/**
336352
* Vertex attribs currently bound and enabled. If a slot is null, then
@@ -417,6 +433,10 @@ private void init() {
417433
backStencilDepthPassOperation = RenderState.StencilOperation.Keep;
418434
frontStencilFunction = RenderState.TestFunction.Always;
419435
backStencilFunction = RenderState.TestFunction.Always;
436+
frontStencilReference = 0;
437+
backStencilReference = 0;
438+
frontStencilMask = -1;
439+
backStencilMask = -1;
420440

421441
depthFunc = RenderState.TestFunction.Less;
422442
alphaFunc = RenderState.TestFunction.Greater;

‎jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,16 +1157,25 @@ public void applyRenderState(RenderState state) {
11571157
|| context.backStencilDepthFailOperation != state.getBackStencilDepthFailOperation()
11581158
|| context.backStencilDepthPassOperation != state.getBackStencilDepthPassOperation()
11591159
|| context.frontStencilFunction != state.getFrontStencilFunction()
1160-
|| context.backStencilFunction != state.getBackStencilFunction()) {
1161-
1162-
context.frontStencilStencilFailOperation = state.getFrontStencilStencilFailOperation(); //terrible looking, I know
1160+
|| context.backStencilFunction != state.getBackStencilFunction()
1161+
|| context.frontStencilReference != state.getFrontStencilReference()
1162+
|| context.backStencilReference != state.getBackStencilReference()
1163+
|| context.frontStencilMask != state.getFrontStencilMask()
1164+
|| context.backStencilMask != state.getBackStencilMask()) {
1165+
1166+
context.stencilTest = state.isStencilTest();
1167+
context.frontStencilStencilFailOperation = state.getFrontStencilStencilFailOperation();
11631168
context.frontStencilDepthFailOperation = state.getFrontStencilDepthFailOperation();
11641169
context.frontStencilDepthPassOperation = state.getFrontStencilDepthPassOperation();
11651170
context.backStencilStencilFailOperation = state.getBackStencilStencilFailOperation();
11661171
context.backStencilDepthFailOperation = state.getBackStencilDepthFailOperation();
11671172
context.backStencilDepthPassOperation = state.getBackStencilDepthPassOperation();
11681173
context.frontStencilFunction = state.getFrontStencilFunction();
11691174
context.backStencilFunction = state.getBackStencilFunction();
1175+
context.frontStencilReference = state.getFrontStencilReference();
1176+
context.backStencilReference = state.getBackStencilReference();
1177+
context.frontStencilMask = state.getFrontStencilMask();
1178+
context.backStencilMask = state.getBackStencilMask();
11701179

11711180
if (state.isStencilTest()) {
11721181
gl.glEnable(GL.GL_STENCIL_TEST);
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* Copyright (c) 2009-2026 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package com.jme3.renderer.opengl;
33+
34+
import static org.mockito.Mockito.clearInvocations;
35+
import static org.mockito.Mockito.mock;
36+
import static org.mockito.Mockito.verify;
37+
import static org.mockito.Mockito.verifyNoInteractions;
38+
39+
import com.jme3.material.RenderState;
40+
import com.jme3.material.RenderState.StencilOperation;
41+
import com.jme3.material.RenderState.TestFunction;
42+
import org.junit.jupiter.api.BeforeEach;
43+
import org.junit.jupiter.api.Test;
44+
45+
/**
46+
* Verifies that cached stencil state matches the calls made to OpenGL.
47+
*/
48+
public class GlRendererStencilStateTest {
49+
50+
private GL gl;
51+
private GLRenderer renderer;
52+
53+
@BeforeEach
54+
public void initializeRenderer() {
55+
gl = mock(GL.class);
56+
renderer = new GLRenderer(gl, mock(GLExt.class), mock(GLFbo.class));
57+
}
58+
59+
@Test
60+
public void applyingUnchangedEnabledStateDoesNotRepeatGlCalls() {
61+
RenderState state = stencil(true, TestFunction.Equal);
62+
renderer.applyRenderState(state);
63+
clearInvocations(gl);
64+
65+
renderer.applyRenderState(state);
66+
67+
verifyNoInteractions(gl);
68+
}
69+
70+
@Test
71+
public void stencilCanBeDisabledAndReenabledWithTheSameParameters() {
72+
RenderState enabled = stencil(true, TestFunction.Never);
73+
RenderState disabled = stencil(false, TestFunction.Never);
74+
renderer.applyRenderState(enabled);
75+
clearInvocations(gl);
76+
renderer.applyRenderState(disabled);
77+
verify(gl).glDisable(GL.GL_STENCIL_TEST);
78+
clearInvocations(gl);
79+
80+
renderer.applyRenderState(enabled);
81+
82+
verify(gl).glEnable(GL.GL_STENCIL_TEST);
83+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_NEVER, 0, Integer.MAX_VALUE);
84+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_NEVER, 0, Integer.MAX_VALUE);
85+
}
86+
87+
@Test
88+
public void changingOnlyTheFrontReferenceUpdatesTheFunction() {
89+
RenderState state = stencil(true, TestFunction.Equal);
90+
renderer.applyRenderState(state);
91+
clearInvocations(gl);
92+
93+
state.setFrontStencilReference(2);
94+
renderer.applyRenderState(state);
95+
96+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_EQUAL, 2, Integer.MAX_VALUE);
97+
}
98+
99+
@Test
100+
public void changingOnlyTheBackReferenceUpdatesTheFunction() {
101+
RenderState state = stencil(true, TestFunction.Equal);
102+
renderer.applyRenderState(state);
103+
clearInvocations(gl);
104+
105+
state.setBackStencilReference(3);
106+
renderer.applyRenderState(state);
107+
108+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_EQUAL, 3, Integer.MAX_VALUE);
109+
}
110+
111+
@Test
112+
public void changingOnlyTheFrontMaskUpdatesTheFunction() {
113+
RenderState state = stencil(true, TestFunction.Equal);
114+
renderer.applyRenderState(state);
115+
clearInvocations(gl);
116+
117+
state.setFrontStencilMask(0x0f);
118+
renderer.applyRenderState(state);
119+
120+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_EQUAL, 0, 0x0f);
121+
}
122+
123+
@Test
124+
public void changingOnlyTheBackMaskUpdatesTheFunction() {
125+
RenderState state = stencil(true, TestFunction.Equal);
126+
renderer.applyRenderState(state);
127+
clearInvocations(gl);
128+
129+
state.setBackStencilMask(0xf0);
130+
renderer.applyRenderState(state);
131+
132+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_EQUAL, 0, 0xf0);
133+
}
134+
135+
@Test
136+
public void changingOnlyTheOperationsUpdatesBothFaces() {
137+
RenderState state = stencil(true, TestFunction.Equal);
138+
renderer.applyRenderState(state);
139+
clearInvocations(gl);
140+
141+
state.setStencil(true,
142+
StencilOperation.Replace, StencilOperation.Keep, StencilOperation.Increment,
143+
StencilOperation.Zero, StencilOperation.Decrement, StencilOperation.Invert,
144+
TestFunction.Equal, TestFunction.Equal);
145+
renderer.applyRenderState(state);
146+
147+
verify(gl).glStencilOpSeparate(GL.GL_FRONT, GL.GL_REPLACE, GL.GL_KEEP, GL.GL_INCR);
148+
verify(gl).glStencilOpSeparate(GL.GL_BACK, GL.GL_ZERO, GL.GL_DECR, GL.GL_INVERT);
149+
}
150+
151+
@Test
152+
public void changingOnlyTheFunctionsUpdatesBothFaces() {
153+
RenderState state = stencil(true, TestFunction.Equal);
154+
renderer.applyRenderState(state);
155+
clearInvocations(gl);
156+
157+
state.setStencil(true,
158+
StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep,
159+
StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep,
160+
TestFunction.Never, TestFunction.Always);
161+
renderer.applyRenderState(state);
162+
163+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_NEVER, 0, Integer.MAX_VALUE);
164+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_ALWAYS, 0, Integer.MAX_VALUE);
165+
}
166+
167+
@Test
168+
public void parametersChangedWhileDisabledAreAppliedWhenEnabled() {
169+
RenderState state = stencil(false, TestFunction.Equal);
170+
state.setFrontStencilReference(2);
171+
state.setBackStencilReference(3);
172+
state.setFrontStencilMask(0x0f);
173+
state.setBackStencilMask(0xf0);
174+
renderer.applyRenderState(state);
175+
clearInvocations(gl);
176+
177+
state.setStencil(true,
178+
StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep,
179+
StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep,
180+
TestFunction.Equal, TestFunction.Equal);
181+
renderer.applyRenderState(state);
182+
183+
verify(gl).glEnable(GL.GL_STENCIL_TEST);
184+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_EQUAL, 2, 0x0f);
185+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_EQUAL, 3, 0xf0);
186+
}
187+
188+
@Test
189+
public void invalidatingStateReappliesEnabledStencilParameters() {
190+
RenderState state = stencil(true, TestFunction.Equal);
191+
state.setFrontStencilReference(2);
192+
state.setBackStencilMask(0xf0);
193+
renderer.applyRenderState(state);
194+
renderer.invalidateState();
195+
clearInvocations(gl);
196+
197+
renderer.applyRenderState(state);
198+
199+
verify(gl).glEnable(GL.GL_STENCIL_TEST);
200+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_EQUAL, 2, Integer.MAX_VALUE);
201+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_EQUAL, 0, 0xf0);
202+
}
203+
204+
@Test
205+
public void enablingStencilWithDefaultParametersAppliesBothFaces() {
206+
renderer.applyRenderState(stencil(true, TestFunction.Always));
207+
208+
verify(gl).glEnable(GL.GL_STENCIL_TEST);
209+
verify(gl).glStencilFuncSeparate(GL.GL_FRONT, GL.GL_ALWAYS, 0, Integer.MAX_VALUE);
210+
verify(gl).glStencilFuncSeparate(GL.GL_BACK, GL.GL_ALWAYS, 0, Integer.MAX_VALUE);
211+
verify(gl).glStencilOpSeparate(GL.GL_FRONT, GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
212+
verify(gl).glStencilOpSeparate(GL.GL_BACK, GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
213+
}
214+
215+
private static RenderState stencil(boolean enabled, TestFunction function) {
216+
RenderState state = new RenderState();
217+
state.setStencil(enabled,
218+
StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep,
219+
StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep,
220+
function, function);
221+
return state;
222+
}
223+
}

0 commit comments

Comments
 (0)