internal/shader: add a builtin function frontfacing

Closes #3264
This commit is contained in:
Hajime Hoshi
2025-06-22 23:12:10 +09:00
parent 5f18a80a0f
commit a96153805e
14 changed files with 155 additions and 66 deletions

View File

@@ -102,7 +102,7 @@ func compileShader(program *shaderir.Program) (vsh, psh *_ID3DBlob, ferr error)
return vsh, psh, nil
}
vs, ps, _ := hlsl.Compile(program)
vs, ps, _, _ := hlsl.Compile(program)
var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3)
var wg errgroup.Group

View File

@@ -421,6 +421,16 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
return nil, nil, nil, false
}
finalType = shaderir.Type{Main: shaderir.Vec4}
case shaderir.FrontFacing:
if len(args) != 0 {
cs.addError(e.Pos(), fmt.Sprintf("number of %s's arguments must be 0 but %d", callee.BuiltinFunc, len(args)))
return nil, nil, nil, false
}
if fname != cs.fragmentEntry {
cs.addError(e.Pos(), fmt.Sprintf("frontfacing is available only in %s", cs.fragmentEntry))
return nil, nil, nil, false
}
finalType = shaderir.Type{Main: shaderir.Bool}
case shaderir.DiscardF:
if len(args) != 0 {
cs.addError(e.Pos(), fmt.Sprintf("number of %s's arguments must be 0 but %d", callee.BuiltinFunc, len(args)))
@@ -434,7 +444,6 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
Type: shaderir.Discard,
})
return nil, nil, stmts, true
case shaderir.Clamp, shaderir.Mix, shaderir.Smoothstep, shaderir.Faceforward, shaderir.Refract:
// 3 arguments
if len(args) != 3 {

View File

@@ -180,8 +180,8 @@ func TestCompile(t *testing.T) {
}
if tc.HLSL != nil {
vs, _, prelude := hlsl.Compile(s)
if got, want := hlslNormalize(vs, prelude), hlslNormalize(string(tc.HLSL), prelude); got != want {
vs, _, vertexPrelude, _ := hlsl.Compile(s)
if got, want := hlslNormalize(vs, vertexPrelude), hlslNormalize(string(tc.HLSL), vertexPrelude); got != want {
compare(t, "HLSL", got, want)
}
}

View File

@@ -1,6 +1,6 @@
void F0(thread array<float2, 3>& l0);
void F0(bool front_facing, thread array<float2, 3>& l0);
void F0(thread array<float2, 3>& l0) {
void F0(bool front_facing, thread array<float2, 3>& l0) {
array<float2, 2> l1 = {};
array<float2, 3> l2 = {};
{

View File

@@ -1,6 +1,6 @@
bool F0(float l0, float l1);
bool F0(bool front_facing, float l0, float l1);
bool F0(float l0, float l1) {
bool F0(bool front_facing, float l0, float l1) {
float l2 = float(0);
float l3 = float(0);
l2 = atan((l1) / (l0));

View File

@@ -1,6 +1,6 @@
void F0(thread float& l0, thread array<float, 4>& l1, thread float4& l2);
void F0(bool front_facing, thread float& l0, thread array<float, 4>& l1, thread float4& l2);
void F0(thread float& l0, thread array<float, 4>& l1, thread float4& l2) {
void F0(bool front_facing, thread float& l0, thread array<float, 4>& l1, thread float4& l2) {
l0 = float(0);
l1 = {};
l2 = float4(0);

View File

@@ -29,6 +29,7 @@ vertex Varyings Vertex(
fragment float4 Fragment(
Varyings varyings [[stage_in]],
constant Uniforms& uniforms [[buffer(0)]]) {
constant Uniforms& uniforms [[buffer(0)]],
bool front_facing [[front_facing]]) {
return float4((varyings.Position).x, (varyings.M0).y, (varyings.M1).z, 1.0);
}

View File

@@ -520,6 +520,9 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
}
return result
}
if callee.BuiltinFunc == shaderir.FrontFacing {
return "gl_FrontFacing"
}
}
// Using parentheses at the callee is illegal.
return fmt.Sprintf("%s(%s)", expr(&callee), strings.Join(args, ", "))

View File

@@ -80,14 +80,14 @@ float4x4 float4x4FromScalar(float x) {
return float4x4(x, 0, 0, 0, 0, x, 0, 0, 0, 0, x, 0, 0, 0, 0, x);
}`
func Compile(p *shaderir.Program) (vertexShader, pixelShader, prelude string) {
func Compile(p *shaderir.Program) (vertexShader, pixelShader, vertexPrelude, pixelPrelude string) {
offsets := UniformVariableOffsetsInDwords(p)
c := &compileContext{
unit: p.Unit,
}
var lines []string
appendPrelude := func(lines []string, vertex bool) []string {
lines = append(lines, strings.Split(utilFuncs, "\n")...)
lines = append(lines, "", "struct Varyings {")
@@ -109,9 +109,21 @@ func Compile(p *shaderir.Program) (vertexShader, pixelShader, prelude string) {
}
}
}
if !vertex {
lines = append(lines, "\tbool FrontFacing : SV_IsFrontFace;")
}
lines = append(lines, "};")
prelude = strings.Join(lines, "\n")
return lines
}
var vslines, pslines []string
vslines = appendPrelude(vslines, true)
pslines = appendPrelude(pslines, false)
vertexPrelude = strings.Join(vslines, "\n")
pixelPrelude = strings.Join(pslines, "\n")
appendGlobalVariables := func(lines []string) []string {
lines = append(lines, "", "{{.Structs}}")
if len(p.Uniforms) > 0 {
@@ -142,11 +154,10 @@ func Compile(p *shaderir.Program) (vertexShader, pixelShader, prelude string) {
lines = append(lines, "SamplerState samp : register(s0);")
}
}
vslines := make([]string, len(lines))
copy(vslines, lines)
pslines := make([]string, len(lines))
copy(pslines, lines)
return lines
}
vslines = appendGlobalVariables(vslines)
pslines = appendGlobalVariables(pslines)
var vsfuncs []*shaderir.Func
if p.VertexFunc.Block != nil {
@@ -522,6 +533,9 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
}
return result
}
if callee.Type == shaderir.BuiltinFuncExpr && callee.BuiltinFunc == shaderir.FrontFacing {
return fmt.Sprintf("%s.FrontFacing", vsOut)
}
return fmt.Sprintf("%s(%s)", expr(&e.Exprs[0]), strings.Join(args, ", "))
case shaderir.FieldSelector:
return fmt.Sprintf("(%s).%s", expr(&e.Exprs[0]), expr(&e.Exprs[1]))

View File

@@ -819,9 +819,9 @@ void F0(in float l0, in float l1, out float l2) {
}`,
Metal: msl.Prelude(shaderir.Pixels) + `
void F0(float l0, float l1, thread float& l2);
void F0(bool front_facing, float l0, float l1, thread float& l2);
void F0(float l0, float l1, thread float& l2) {
void F0(bool front_facing, float l0, float l1, thread float& l2) {
for (int l3 = 0; l3 < 100; l3++) {
int l4 = 0;
l2 = l4;
@@ -916,9 +916,9 @@ void F0(in float l0, in float l1, out float l2) {
}`,
Metal: msl.Prelude(shaderir.Pixels) + `
void F0(float l0, float l1, thread float& l2);
void F0(bool front_facing, float l0, float l1, thread float& l2);
void F0(float l0, float l1, thread float& l2) {
void F0(bool front_facing, float l0, float l1, thread float& l2) {
for (int l3 = 0; l3 < 100; l3++) {
int l4 = 0;
l2 = l4;

View File

@@ -156,6 +156,8 @@ func Compile(p *shaderir.Program) (shader string) {
lines[len(lines)-1] += ","
lines = append(lines, fmt.Sprintf("\ttexture2d<float> T%[1]d [[texture(%[1]d)]]", i))
}
lines[len(lines)-1] += ","
lines = append(lines, "\tbool front_facing [[front_facing]]")
lines[len(lines)-1] += ") {"
lines = append(lines, c.block(p, p.FragmentFunc.Block, p.FragmentFunc.Block, 0)...)
lines = append(lines, "}")
@@ -244,6 +246,7 @@ func (c *compileContext) function(p *shaderir.Program, f *shaderir.Func, prototy
for i := 0; i < p.TextureCount; i++ {
args = append(args, fmt.Sprintf("texture2d<float> T%d", i))
}
args = append(args, "bool front_facing")
var idx int
for _, t := range f.InParams {
@@ -404,6 +407,7 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
for i := 0; i < p.TextureCount; i++ {
args = append(args, fmt.Sprintf("T%d", i))
}
args = append(args, "front_facing")
}
for _, exp := range e.Exprs[1:] {
args = append(args, expr(&exp))
@@ -425,6 +429,9 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
}
return result
}
if callee.Type == shaderir.BuiltinFuncExpr && callee.BuiltinFunc == shaderir.FrontFacing {
return "(front_facing)"
}
return fmt.Sprintf("%s(%s)", expr(&callee), strings.Join(args, ", "))
case shaderir.FieldSelector:
return fmt.Sprintf("(%s).%s", expr(&e.Exprs[0]), expr(&e.Exprs[1]))

View File

@@ -294,6 +294,7 @@ const (
Fwidth BuiltinFunc = "fwidth"
DiscardF BuiltinFunc = "discard"
TexelAt BuiltinFunc = "__texelAt"
FrontFacing BuiltinFunc = "frontfacing"
)
func ParseBuiltinFunc(str string) (BuiltinFunc, bool) {
@@ -351,7 +352,8 @@ func ParseBuiltinFunc(str string) (BuiltinFunc, bool) {
Dfdy,
Fwidth,
DiscardF,
TexelAt:
TexelAt,
FrontFacing:
return BuiltinFunc(str), true
}
return "", false

View File

@@ -497,7 +497,7 @@ func compile(shader *Shader, targets []string) error {
Fragment: fs,
}
case "hlsl":
vs, ps, _ := hlsl.Compile(ir)
vs, ps, _, _ := hlsl.Compile(ir)
shader.HLSL = &HLSL{
Vertex: vs,
Pixel: ps,

View File

@@ -3010,3 +3010,56 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
}
}
}
func TestShaderFrontFacing(t *testing.T) {
const w, h = 16, 16
dst := ebiten.NewImage(w, h)
s, err := ebiten.NewShader([]byte(`//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
if frontfacing() {
return vec4(0.5, 0, 0, 1)
}
return vec4(0, 0.5, 0, 1)
}
`))
if err != nil {
t.Fatal(err)
}
vs := []ebiten.Vertex{
{
DstX: 0,
DstY: 0,
},
{
DstX: w,
DstY: 0,
},
{
DstX: 0,
DstY: h,
},
{
DstX: w,
DstY: h,
},
}
op := &ebiten.DrawTrianglesShaderOptions{}
op.Blend = ebiten.BlendLighter
dst.DrawTrianglesShader32(vs, []uint32{0, 1, 2, 1, 2, 3}, s, op)
dst.DrawTrianglesShader32(vs, []uint32{2, 1, 0, 3, 2, 1}, s, op)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := color.RGBA{R: 0x80, G: 0x80, B: 0x00, A: 0xff}
if !sameColors(got, want, 2) {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}