Skip to content

Commit 8bb2bbc

Browse files
Added Stencil Support
1 parent 71c2693 commit 8bb2bbc

3 files changed

Lines changed: 166 additions & 1 deletion

File tree

Prowl.Runtime/AssetImporting/ShaderParser.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,10 @@ private static ParsedPass ParsePass(Tokenizer<ShaderToken> tokenizer)
641641
pass.State.Winding = EnumParse<RasterizerState.WindingOrder>(tokenizer.Token.ToString(), "winding order");
642642
break;
643643

644+
case "Stencil":
645+
ParseStencilState(tokenizer, ref pass.State);
646+
break;
647+
644648
case "GLSLPROGRAM":
645649
pass.ProgramStartLine = tokenizer.CurrentLine;
646650
EnsureUndef(pass.Program, "'GLSLPROGRAM' in pass");
@@ -779,6 +783,64 @@ private static void ParseBlendState(Tokenizer<ShaderToken> tokenizer, ref Raster
779783
}
780784
}
781785

786+
/// <summary>
787+
/// Parses a Stencil block: <c>Stencil { Ref 1 Comp Equal Pass Replace Fail Keep ZFail Keep ReadMask 255 WriteMask 255 }</c>
788+
/// All keys are optional; unspecified values keep their defaults.
789+
/// </summary>
790+
private static void ParseStencilState(Tokenizer<ShaderToken> tokenizer, ref RasterizerState state)
791+
{
792+
state.StencilEnabled = true;
793+
794+
// Expect opening brace
795+
ExpectToken("stencil", tokenizer, ShaderToken.OpenCurlBrace);
796+
797+
while (tokenizer.MoveNext() && tokenizer.TokenType != ShaderToken.CloseCurlBrace)
798+
{
799+
string key = tokenizer.Token.ToString();
800+
801+
switch (key)
802+
{
803+
case "Ref":
804+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
805+
state.StencilRef = int.Parse(tokenizer.Token.ToString());
806+
break;
807+
808+
case "Comp":
809+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
810+
state.StencilFunc = EnumParse<RasterizerState.StencilFunction>(tokenizer.Token.ToString(), "stencil comparison");
811+
break;
812+
813+
case "Pass":
814+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
815+
state.StencilPassOp = EnumParse<RasterizerState.StencilOp>(tokenizer.Token.ToString(), "stencil pass op");
816+
break;
817+
818+
case "Fail":
819+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
820+
state.StencilFailOp = EnumParse<RasterizerState.StencilOp>(tokenizer.Token.ToString(), "stencil fail op");
821+
break;
822+
823+
case "ZFail":
824+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
825+
state.StencilZFailOp = EnumParse<RasterizerState.StencilOp>(tokenizer.Token.ToString(), "stencil zfail op");
826+
break;
827+
828+
case "ReadMask":
829+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
830+
state.StencilReadMask = int.Parse(tokenizer.Token.ToString());
831+
break;
832+
833+
case "WriteMask":
834+
ExpectToken("stencil", tokenizer, ShaderToken.Identifier);
835+
state.StencilWriteMask = int.Parse(tokenizer.Token.ToString());
836+
break;
837+
838+
default:
839+
throw new ParseException("stencil state", $"unknown stencil key: {key}");
840+
}
841+
}
842+
}
843+
782844
private static RasterizerState.PolyFace ParseCullMode(string cullMode)
783845
{
784846
if (cullMode.Equals("Off", StringComparison.OrdinalIgnoreCase))

Prowl.Runtime/Graphics/Graphics.cs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ public static unsafe class Graphics
4343

4444
private static RasterizerState.WindingOrder winding = RasterizerState.WindingOrder.CW;
4545

46+
private static bool stencilEnabled = false;
47+
private static RasterizerState.StencilFunction stencilFunc = RasterizerState.StencilFunction.Always;
48+
private static int stencilRef = 0;
49+
private static int stencilReadMask = 255;
50+
private static int stencilWriteMask = 255;
51+
private static RasterizerState.StencilOp stencilPassOp = RasterizerState.StencilOp.Keep;
52+
private static RasterizerState.StencilOp stencilFailOp = RasterizerState.StencilOp.Keep;
53+
private static RasterizerState.StencilOp stencilZFailOp = RasterizerState.StencilOp.Keep;
54+
4655
private static GraphicsFrameBuffer? currentFramebuffer = null;
4756
private static GraphicsFrameBuffer? currentReadFramebuffer = null;
4857
private static GraphicsFrameBuffer? currentDrawFramebuffer = null;
@@ -163,6 +172,39 @@ public static void SetState(RasterizerState state, bool force = false)
163172
GL.FrontFace(WindingToGL(state.Winding));
164173
winding = state.Winding;
165174
}
175+
176+
if (stencilEnabled != state.StencilEnabled || force)
177+
{
178+
if (state.StencilEnabled)
179+
GL.Enable(EnableCap.StencilTest);
180+
else
181+
GL.Disable(EnableCap.StencilTest);
182+
stencilEnabled = state.StencilEnabled;
183+
}
184+
185+
if (state.StencilEnabled &&
186+
(stencilFunc != state.StencilFunc || stencilRef != state.StencilRef || stencilReadMask != state.StencilReadMask || force))
187+
{
188+
GL.StencilFunc(StencilFuncToGL(state.StencilFunc), state.StencilRef, (uint)state.StencilReadMask);
189+
stencilFunc = state.StencilFunc;
190+
stencilRef = state.StencilRef;
191+
stencilReadMask = state.StencilReadMask;
192+
}
193+
194+
if (state.StencilEnabled &&
195+
(stencilFailOp != state.StencilFailOp || stencilZFailOp != state.StencilZFailOp || stencilPassOp != state.StencilPassOp || force))
196+
{
197+
GL.StencilOp(StencilOpToGL(state.StencilFailOp), StencilOpToGL(state.StencilZFailOp), StencilOpToGL(state.StencilPassOp));
198+
stencilFailOp = state.StencilFailOp;
199+
stencilZFailOp = state.StencilZFailOp;
200+
stencilPassOp = state.StencilPassOp;
201+
}
202+
203+
if (state.StencilEnabled && (stencilWriteMask != state.StencilWriteMask || force))
204+
{
205+
GL.StencilMask((uint)state.StencilWriteMask);
206+
stencilWriteMask = state.StencilWriteMask;
207+
}
166208
}
167209

168210
public static RasterizerState GetState()
@@ -176,7 +218,15 @@ public static RasterizerState GetState()
176218
BlendSrc = blendSrc,
177219
BlendDst = blendDst,
178220
Blend = blendEquation,
179-
CullFace = cullFace
221+
CullFace = cullFace,
222+
StencilEnabled = stencilEnabled,
223+
StencilFunc = stencilFunc,
224+
StencilRef = stencilRef,
225+
StencilReadMask = stencilReadMask,
226+
StencilWriteMask = stencilWriteMask,
227+
StencilPassOp = stencilPassOp,
228+
StencilFailOp = stencilFailOp,
229+
StencilZFailOp = stencilZFailOp,
180230
};
181231
}
182232

@@ -209,6 +259,16 @@ public static void ResetState()
209259

210260
GL.FrontFace(FrontFaceDirection.CW);
211261
winding = RasterizerState.WindingOrder.CW;
262+
263+
GL.Disable(EnableCap.StencilTest);
264+
stencilEnabled = false;
265+
stencilFunc = RasterizerState.StencilFunction.Always;
266+
stencilRef = 0;
267+
stencilReadMask = 255;
268+
stencilWriteMask = 255;
269+
stencilPassOp = RasterizerState.StencilOp.Keep;
270+
stencilFailOp = RasterizerState.StencilOp.Keep;
271+
stencilZFailOp = RasterizerState.StencilOp.Keep;
212272
}
213273

214274
// Helper method to combine program ID and string hash into a unique ulong key
@@ -662,6 +722,38 @@ private static FrontFaceDirection WindingToGL(RasterizerState.WindingOrder windi
662722
};
663723
}
664724

725+
private static StencilFunction StencilFuncToGL(RasterizerState.StencilFunction func)
726+
{
727+
return func switch
728+
{
729+
RasterizerState.StencilFunction.Never => StencilFunction.Never,
730+
RasterizerState.StencilFunction.Less => StencilFunction.Less,
731+
RasterizerState.StencilFunction.Equal => StencilFunction.Equal,
732+
RasterizerState.StencilFunction.Lequal => StencilFunction.Lequal,
733+
RasterizerState.StencilFunction.Greater => StencilFunction.Greater,
734+
RasterizerState.StencilFunction.Notequal => StencilFunction.Notequal,
735+
RasterizerState.StencilFunction.Gequal => StencilFunction.Gequal,
736+
RasterizerState.StencilFunction.Always => StencilFunction.Always,
737+
_ => throw new ArgumentOutOfRangeException(nameof(func), func, null),
738+
};
739+
}
740+
741+
private static StencilOp StencilOpToGL(RasterizerState.StencilOp op)
742+
{
743+
return op switch
744+
{
745+
RasterizerState.StencilOp.Keep => StencilOp.Keep,
746+
RasterizerState.StencilOp.Zero => StencilOp.Zero,
747+
RasterizerState.StencilOp.Replace => StencilOp.Replace,
748+
RasterizerState.StencilOp.Incr => StencilOp.Incr,
749+
RasterizerState.StencilOp.IncrWrap => StencilOp.IncrWrap,
750+
RasterizerState.StencilOp.Decr => StencilOp.Decr,
751+
RasterizerState.StencilOp.DecrWrap => StencilOp.DecrWrap,
752+
RasterizerState.StencilOp.Invert => StencilOp.Invert,
753+
_ => throw new ArgumentOutOfRangeException(nameof(op), op, null),
754+
};
755+
}
756+
665757
#endregion
666758

667759
#endregion

Prowl.Runtime/Graphics/Primitives.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public enum Blending { Zero, One, SrcColor, OneMinusSrcColor, DstColor, OneMinus
6868
public enum BlendMode { Add, Subtract, ReverseSubtract, Min, Max }
6969
public enum PolyFace { None, Front, Back, FrontAndBack }
7070
public enum WindingOrder { CW, CCW }
71+
public enum StencilFunction { Never, Less, Equal, Lequal, Greater, Notequal, Gequal, Always }
72+
public enum StencilOp { Keep, Zero, Replace, Incr, IncrWrap, Decr, DecrWrap, Invert }
7173

7274
public bool DepthTest = true;
7375
public bool DepthWrite = true;
@@ -82,6 +84,15 @@ public enum WindingOrder { CW, CCW }
8284

8385
public WindingOrder Winding = WindingOrder.CW;
8486

87+
public bool StencilEnabled = false;
88+
public StencilFunction StencilFunc = StencilFunction.Always;
89+
public int StencilRef = 0;
90+
public int StencilReadMask = 255;
91+
public int StencilWriteMask = 255;
92+
public StencilOp StencilPassOp = StencilOp.Keep;
93+
public StencilOp StencilFailOp = StencilOp.Keep;
94+
public StencilOp StencilZFailOp = StencilOp.Keep;
95+
8596
public RasterizerState()
8697
{
8798
// Default constructor

0 commit comments

Comments
 (0)