Skip to content

Commit 91ae184

Browse files
committed
Feature: Extend 'perform arithmetic' to include vectors.
E.g. vec2 f = vec2(1.1, 2.2) + 3.3 * 4.4; to vec2 f = vec2(15.62, 16.72);
1 parent 6e5b2b6 commit 91ae184

File tree

6 files changed

+161
-56
lines changed

6 files changed

+161
-56
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ It is written in C# using WPF and Visual Studio 2019, and has several hundred NU
1010

1111
It is designed to work primarily with code from [Shadertoy](https://www.shadertoy.com/), but has limited support for other styles of GLSL too (E.g. [Bonzomatic](https://github.com/Gargaj/Bonzomatic))
1212

13-
After writing a Shadertoy shader, usually from my boilerplate starting code, there is usually a fixed sequence of operations I perform:
13+
After writing a Shadertoy shader, usually from my boilerplate starting code, there is a sequence of operations I perform:
1414
* Delete dead/commented-out code.
1515
* Remove unused functions.
1616
* Inline some constants (Max raymarching distance, 'hit test' accuracy, ...)
@@ -529,6 +529,7 @@ E.g.
529529
* Change ```f + 0.0``` or ```f - 0.0``` to ```f```
530530
* Remove ```f * 0.0``` (when safe).
531531
* Change ```pow(3.0, 2.0)``` to ```9.0```
532-
* Change ```float a = 1.2 / 2.3 * 4.5``` to ```a = 2.3478```
532+
* Change ```float a = 1.2 / 2.3 * 4.5;``` to ```float a = 2.3478;```
533+
* Change ```vec2 f = vec2(1.1, 2.2) + 3.3 * 4.4;``` to ```vec2 f = vec2(15.62, 16.72);```
533534

534535
---

ShaderShrinker/Shrinker.Lexer/FloatToken.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ namespace Shrinker.Lexer
1616
{
1717
public class FloatToken : Token, INumberToken
1818
{
19+
public static FloatToken From(double c, int maxDp)
20+
{
21+
var floatToken = Math.Abs(c) < 0.0001 && Math.Abs(c) > 0.0 ? new FloatToken(c.ToString($".#{new string('#', maxDp - 1)}e0")) : new FloatToken(c.ToString($"F{maxDp}"));
22+
return (FloatToken)floatToken.Simplify();
23+
}
24+
1925
public FloatToken(string s)
2026
{
2127
Content = s;

ShaderShrinker/Shrinker.Parser/Optimizations/PerformArithmeticExtension.cs

Lines changed: 106 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
132132
if (xy.Count == 2 && xy.All(o => o > 0.0))
133133
{
134134
powNode.Params.Remove();
135-
powNode.ReplaceWith(new GenericSyntaxNode(new FloatToken($"{Math.Pow(xy[0], xy[1]):F}").Simplify()));
135+
powNode.ReplaceWith(new GenericSyntaxNode(FloatToken.From(Math.Pow(xy[0], xy[1]), MaxDp)));
136136
didChange = true;
137137
}
138138
}
@@ -147,7 +147,64 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
147147
if (x.Count == 1)
148148
{
149149
absNode.Params.Remove();
150-
absNode.ReplaceWith(new GenericSyntaxNode(new FloatToken($"{Math.Abs(x[0]):F}").Simplify()));
150+
absNode.ReplaceWith(new GenericSyntaxNode(FloatToken.From(Math.Abs(x[0]), MaxDp)));
151+
didChange = true;
152+
}
153+
}
154+
155+
// vecN(...) + <float>
156+
foreach (var vectorNode in rootNode.TheTree
157+
.OfType<GenericSyntaxNode>()
158+
.Where(o => o.Token is TypeToken t && t.Content?.StartsWith("vec") == true))
159+
{
160+
// Find the brackets.
161+
if (vectorNode.Next is not RoundBracketSyntaxNode brackets)
162+
continue;
163+
164+
// Brackets must be filled with floats.
165+
if (!brackets.IsSimpleCsv())
166+
continue;
167+
168+
// Find the math symbol.
169+
var symbolNode = brackets.Next;
170+
if (symbolNode?.Token is not SymbolOperatorToken symbol)
171+
continue;
172+
switch (symbol.GetMathSymbolType())
173+
{
174+
case TokenExtensions.MathSymbolType.AddSubtract:
175+
case TokenExtensions.MathSymbolType.MultiplyDivide:
176+
break; // Supported.
177+
178+
default:
179+
continue; // Not supported - Continue search.
180+
}
181+
182+
// Find the float number.
183+
var numberNode = symbolNode.Next;
184+
if (numberNode?.Token is not FloatToken)
185+
continue;
186+
187+
// Number must not be used in a following math operation.
188+
if (numberNode.Next?.Token is SymbolOperatorToken nextMath && nextMath.GetMathSymbolType() == TokenExtensions.MathSymbolType.MultiplyDivide)
189+
continue;
190+
191+
// Perform math on each bracketed value.
192+
var newCsv =
193+
brackets
194+
.GetCsv()
195+
.Select(o => DoNodeMath(o.Single(), symbolNode, numberNode))
196+
.Select(o => new GenericSyntaxNode(FloatToken.From(o, MaxDp)));
197+
198+
// Replace bracket content and sum (if it shrinks the code).
199+
var newBrackets = new RoundBracketSyntaxNode(newCsv.ToCsv());
200+
var oldSize = brackets.ToCode().Length + 1 + numberNode.ToCode().Length;
201+
var newSize = newBrackets.ToCode().Length;
202+
if (newSize <= oldSize)
203+
{
204+
brackets.ReplaceWith(newBrackets);
205+
symbolNode.Remove();
206+
numberNode.Remove();
207+
151208
didChange = true;
152209
}
153210
}
@@ -179,66 +236,17 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
179236
continue;
180237
}
181238

182-
var a = double.Parse(numNodeA.Token.Content);
183-
var b = double.Parse(numNodeB.Token.Content);
184-
185-
var symbol = symbolNode.Token.Content[0];
186-
187-
// Invert * or / if preceded by a /.
188-
// E.g. 1.2 / 2.3 * 4.5 = 1.2 / (2.3 / 4.5)
189-
// = 1.2 / 0.51111
190-
// = 2.3478
191-
if (numNodeA.Previous?.Token?.GetMathSymbolType() == TokenExtensions.MathSymbolType.MultiplyDivide &&
192-
numNodeA.Previous.HasNodeContent("/"))
193-
{
194-
symbol = symbol == '*' ? '/' : '*';
195-
}
196-
197-
// Invert + or - if preceded by a -.
198-
// E.g. -3.0 + 0.1 = - (3.0 - 0.1)
199-
// = - (2.9)
200-
// = -2.9
201-
if (numNodeA.Previous.HasNodeContent("-") &&
202-
symbolNode.Token.GetMathSymbolType() == TokenExtensions.MathSymbolType.AddSubtract)
203-
{
204-
symbol = symbol == '+' ? '-' : '+';
205-
}
206-
207-
double c;
208-
switch (symbol)
209-
{
210-
case '+':
211-
c = a + b;
212-
break;
213-
case '-':
214-
c = a - b;
215-
break;
216-
case '*':
217-
c = a * b;
218-
break;
219-
case '/':
220-
c = a / b;
221-
if (double.IsInfinity(c))
222-
c = 0.0;
223-
break;
224-
default:
225-
throw new InvalidOperationException($"Unrecognized math operation '{symbol}'.");
226-
}
239+
var c = DoNodeMath(numNodeA, symbolNode, numNodeB);
227240

228241
var isFloatResult = numNodeA.Token is FloatToken || numNodeB.Token is FloatToken;
229242
numNodeB.Remove();
230243
symbolNode.Remove();
231244

232245
SyntaxNode newNode;
233246
if (isFloatResult)
234-
{
235-
var numberToken = Math.Abs(c) < 0.0001 && Math.Abs(c) > 0.0 ? new FloatToken(c.ToString($".#{new string('#', MaxDp - 1)}e0")) : new FloatToken(c.ToString($"F{MaxDp}"));
236-
newNode = numNodeA.ReplaceWith(new GenericSyntaxNode(numberToken.Simplify()));
237-
}
247+
newNode = numNodeA.ReplaceWith(new GenericSyntaxNode(FloatToken.From(c, MaxDp)));
238248
else
239-
{
240249
newNode = numNodeA.ReplaceWith(new GenericSyntaxNode(new IntToken((int)c)));
241-
}
242250

243251
// If new node is the sole child of a group, promote it.
244252
if (newNode.IsOnlyChild() && newNode.Parent.GetType() == typeof(GroupSyntaxNode))
@@ -255,5 +263,50 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
255263

256264
return repeatSimplifications;
257265
}
266+
267+
private static double DoNodeMath(SyntaxNode numNodeA, SyntaxNode symbolNode, SyntaxNode numNodeB)
268+
{
269+
var a = double.Parse(numNodeA.Token.Content);
270+
var b = double.Parse(numNodeB.Token.Content);
271+
272+
var symbol = symbolNode.Token.Content[0];
273+
274+
// Invert * or / if preceded by a /.
275+
// E.g. 1.2 / 2.3 * 4.5 = 1.2 / (2.3 / 4.5)
276+
// = 1.2 / 0.51111
277+
// = 2.3478
278+
if (numNodeA.Previous?.Token?.GetMathSymbolType() == TokenExtensions.MathSymbolType.MultiplyDivide &&
279+
numNodeA.Previous.HasNodeContent("/"))
280+
{
281+
symbol = symbol == '*' ? '/' : '*';
282+
}
283+
284+
// Invert + or - if preceded by a -.
285+
// E.g. -3.0 + 0.1 = - (3.0 - 0.1)
286+
// = - (2.9)
287+
// = -2.9
288+
if (numNodeA.Previous.HasNodeContent("-") &&
289+
symbolNode.Token.GetMathSymbolType() == TokenExtensions.MathSymbolType.AddSubtract)
290+
{
291+
symbol = symbol == '+' ? '-' : '+';
292+
}
293+
294+
switch (symbol)
295+
{
296+
case '+':
297+
return a + b;
298+
case '-':
299+
return a - b;
300+
case '*':
301+
return a * b;
302+
case '/':
303+
var c = a / b;
304+
if (double.IsInfinity(c))
305+
c = 0.0;
306+
return c;
307+
default:
308+
throw new InvalidOperationException($"Unrecognized math operation '{symbol}'.");
309+
}
310+
}
258311
}
259312
}

ShaderShrinker/Shrinker.Parser/SyntaxNodes/SyntaxNodeExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,16 @@ public static IEnumerable<VariableAssignmentSyntaxNode> GlobalVariables(this Syn
7676
public static bool IsComment(this SyntaxNode node) => node is CommentSyntaxNodeBase || node.Token is CommentTokenBase;
7777

7878
public static bool HasNodeContent(this SyntaxNode node, string s) => node?.Token?.Content == s;
79+
80+
public static IEnumerable<SyntaxNode> ToCsv(this IEnumerable<SyntaxNode> values)
81+
{
82+
var nodes = values.ToList();
83+
foreach (var node in nodes)
84+
{
85+
yield return node;
86+
if (node != nodes.Last())
87+
yield return new GenericSyntaxNode(new CommaToken());
88+
}
89+
}
7990
}
8091
}

ShaderShrinker/Shrinker.WpfApp/OptionsDialog.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,8 @@
545545
* Change ```f + 0.0``` or ```f - 0.0``` to ```f```
546546
* Remove ```f * 0.0``` (when safe).
547547
* Change ```pow(3.0, 2.0)``` to ```9.0```
548-
* Change ```float a = 1.2 / 2.3 * 4.5``` to ```a = 2.3478```
548+
* Change ```float a = 1.2 / 2.3 * 4.5;``` to ```float a = 2.3478;```
549+
* Change ```vec2 f = vec2(1.1, 2.2) + 3.3 * 4.4;``` to ```vec2 f = vec2(15.62, 16.72);```
549550
</MdXaml:MarkdownScrollViewer>
550551
</CheckBox.ToolTip>
551552
</CheckBox>

ShaderShrinker/UnitTests/ShrinkerTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,39 @@ public void CheckArithmeticWithAbsFunction(
15101510
Assert.That(rootNode.ToCode().ToSimple(), Is.EqualTo(expected));
15111511
}
15121512

1513+
[Test, Sequential]
1514+
public void CheckArithmeticWithVectorAndScalar(
1515+
[Values("vec2 f = vec2(1.1, 2.2) + 3.3;",
1516+
"vec3 f = vec3(1.1, 2.2, 3.3) - 4.4;",
1517+
"vec4 f = vec4(1.1) * 2.2;",
1518+
"vec4 f = vec4(10) / 2.0;",
1519+
"vec4 f = vec4(22, 23, 24) / 7.0;",
1520+
"void f(vec2 v) { } void main() { f(vec2(1.1, 2.2) + 3.3); }",
1521+
"vec2 f = vec2(1.1, 2.2) + 3.3 * 4.4;",
1522+
"float i = 2.0; vec2 f = vec2(1.1, 2.2) + 3.3 * i;",
1523+
"vec4 f = vec4(1.1) * 2.2 + 3.3;")] string code,
1524+
[Values("vec2 f = vec2(4.4, 5.5);",
1525+
"vec3 f = vec3(-3.3, -2.2, -1.1);",
1526+
"vec4 f = vec4(2.42);",
1527+
"vec4 f = vec4(5.);",
1528+
"vec4 f = vec4(22, 23, 24) / 7.0;",
1529+
"void f(vec2 v) { } void main() { f(vec2(4.4, 5.5)); }",
1530+
"vec2 f = vec2(15.62, 16.72);",
1531+
"float i = 2.0; vec2 f = vec2(1.1, 2.2) + 3.3 * i;",
1532+
"vec4 f = vec4(5.72);")] string expected)
1533+
{
1534+
var lexer = new Lexer();
1535+
lexer.Load(code);
1536+
1537+
var options = CustomOptions.None();
1538+
options.PerformArithmetic = true;
1539+
var rootNode = new Parser(lexer)
1540+
.Parse()
1541+
.Simplify(options);
1542+
1543+
Assert.That(rootNode.ToCode().ToSimple(), Is.EqualTo(expected));
1544+
}
1545+
15131546
[Test, Sequential]
15141547
public void CheckRemovingUnusedVariables(
15151548
[Values("void main() { int a; }",

0 commit comments

Comments
 (0)