add raylib based interactive visualization of the rendering
This commit is contained in:
parent
3625fce0c9
commit
c6f5411f91
244
Program.cs
244
Program.cs
|
|
@ -5,17 +5,128 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
|
||||||
using System.Reflection.Emit;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Raylib_cs;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
int currentImageSize = 1024;
|
Main();
|
||||||
|
|
||||||
|
// STAThread is required if you deploy using NativeAOT on Windows
|
||||||
|
// See https://github.com/raylib-cs/raylib-cs/issues/301
|
||||||
|
[STAThread]
|
||||||
|
void Main()
|
||||||
|
{
|
||||||
|
string fragmentShader = """
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 fragTexCoord;
|
||||||
|
in vec4 fragColor;
|
||||||
|
|
||||||
|
uniform sampler2D texture0;
|
||||||
|
|
||||||
|
out vec4 outputColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
float v = texture(texture0, fragTexCoord).r< 0 ? 1.0 : 0.0;
|
||||||
|
outputColor = vec4(v, v, v, 1.0f) * fragColor;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
int currentOutputImageSize = 1024;
|
||||||
|
Raylib.InitWindow(currentOutputImageSize, currentOutputImageSize, "Sharpero");
|
||||||
|
|
||||||
|
Texture2D currentOutputTexture;
|
||||||
|
Shader currentShader = Raylib.LoadShaderFromMemory(null, fragmentShader);
|
||||||
|
float[] currentOutputImageData = new float[currentOutputImageSize * currentOutputImageSize];
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (float* currentOutputImageDataPtr = currentOutputImageData)
|
||||||
|
{
|
||||||
|
Image currentOutputImage = new Image()
|
||||||
|
{
|
||||||
|
Format = PixelFormat.UncompressedR32,
|
||||||
|
Data = currentOutputImageDataPtr,
|
||||||
|
Width = currentOutputImageSize,
|
||||||
|
Height = currentOutputImageSize,
|
||||||
|
Mipmaps = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
currentOutputTexture = Raylib.LoadTextureFromImage(currentOutputImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldEvaluate = false;
|
||||||
|
bool shouldCancelUpdateTexture = false;
|
||||||
|
bool shouldUpdateTexture = false;
|
||||||
|
|
||||||
|
while (!Raylib.WindowShouldClose())
|
||||||
|
{
|
||||||
|
Raylib.BeginDrawing();
|
||||||
|
|
||||||
|
Raylib.ClearBackground(Color.White);
|
||||||
|
|
||||||
|
if (shouldEvaluate)
|
||||||
|
{
|
||||||
|
shouldUpdateTexture = true;
|
||||||
|
currentOutputImageData.AsSpan()[..currentOutputImageData.Length].Clear();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
Instruction[] instructions = Parsing.Parse("Programs/prospero.vm");
|
||||||
|
Interpreter.Evaluate(instructions, imageSize: currentOutputImageSize, currentOutputImageData);
|
||||||
|
Raylib.UpdateTexture(currentOutputTexture, currentOutputImageData);
|
||||||
|
shouldCancelUpdateTexture = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
shouldEvaluate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdateTexture)
|
||||||
|
{
|
||||||
|
Raylib.UpdateTexture(currentOutputTexture, currentOutputImageData);
|
||||||
|
if (shouldCancelUpdateTexture)
|
||||||
|
{
|
||||||
|
shouldCancelUpdateTexture = false;
|
||||||
|
shouldUpdateTexture = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Raylib.BeginShaderMode(currentShader);
|
||||||
|
Raylib.DrawTexture(currentOutputTexture, 0, 0, Color.White);
|
||||||
|
Raylib.EndShaderMode();
|
||||||
|
|
||||||
|
Raylib.DrawText("Sharpero (press R to evaluate, O to output to file)", 12, 12, 20, Color.White);
|
||||||
|
|
||||||
|
if (Raylib.IsKeyPressed(KeyboardKey.R))
|
||||||
|
{
|
||||||
|
shouldEvaluate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Raylib.IsKeyPressed(KeyboardKey.O))
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
GenerateOutputImage(currentImageSize: currentOutputImageSize, shouldWriteOutputImage: true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Raylib.EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
Raylib.UnloadShader(currentShader);
|
||||||
|
Raylib.UnloadTexture(currentOutputTexture);
|
||||||
|
Raylib.CloseWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] GenerateOutputImage(int currentImageSize, bool shouldWriteOutputImage = false)
|
||||||
|
{
|
||||||
string combinedOutputString = string.Empty;
|
string combinedOutputString = string.Empty;
|
||||||
|
|
||||||
(bool success, double totalTimeTakenSecondsOutput) = BenchmarkFunction(() =>
|
(float[] result, double totalTimeTakenSecondsOutput) = BenchmarkFunction(() =>
|
||||||
{
|
{
|
||||||
(float[] result, double timeTakenSecondsEvaluate) = BenchmarkFunction(() =>
|
(float[] result, double timeTakenSecondsEvaluate) = BenchmarkFunction(() =>
|
||||||
{
|
{
|
||||||
|
|
@ -25,15 +136,18 @@ string combinedOutputString = string.Empty;
|
||||||
|
|
||||||
combinedOutputString += $" - took: {timeTakenSecondsEvaluate} seconds to evaluate the image!" + Environment.NewLine;
|
combinedOutputString += $" - took: {timeTakenSecondsEvaluate} seconds to evaluate the image!" + Environment.NewLine;
|
||||||
|
|
||||||
|
if (shouldWriteOutputImage)
|
||||||
|
{
|
||||||
(bool success, double timeTakenSecondsOutput) = BenchmarkFunction(() =>
|
(bool success, double timeTakenSecondsOutput) = BenchmarkFunction(() =>
|
||||||
{
|
{
|
||||||
CreateOutputImage(currentImageSize, result, "prospero.jpg");
|
WriteOutputImage(currentImageSize, result, "prospero.jpg");
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
combinedOutputString += $" - took: {timeTakenSecondsOutput} seconds to write out image!" + Environment.NewLine;
|
combinedOutputString += $" - took: {timeTakenSecondsOutput} seconds to write out image!" + Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
Console.Write($"Sharpero took: {totalTimeTakenSecondsOutput} seconds to evaluate {currentImageSize}x{currentImageSize} image!" + Environment.NewLine + combinedOutputString);
|
Console.Write($"Sharpero took: {totalTimeTakenSecondsOutput} seconds to evaluate {currentImageSize}x{currentImageSize} image!" + Environment.NewLine + combinedOutputString);
|
||||||
|
|
@ -45,7 +159,7 @@ Console.Write($"Sharpero took: {totalTimeTakenSecondsOutput} seconds to evaluate
|
||||||
return (benchmarkedResult, sw.Elapsed.TotalSeconds);
|
return (benchmarkedResult, sw.Elapsed.TotalSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOutputImage(int imageSize, float[] imageData, string imageOutputPath)
|
void WriteOutputImage(int imageSize, float[] imageData, string imageOutputPath)
|
||||||
{
|
{
|
||||||
byte[] imageDataBytes = imageData.Select(p => (byte)(p < 0 ? 255 : 0)).ToArray();
|
byte[] imageDataBytes = imageData.Select(p => (byte)(p < 0 ? 255 : 0)).ToArray();
|
||||||
using var image = SKImage.FromPixelCopy(new SKImageInfo(imageSize, imageSize, SKColorType.Gray8), imageDataBytes);
|
using var image = SKImage.FromPixelCopy(new SKImageInfo(imageSize, imageSize, SKColorType.Gray8), imageDataBytes);
|
||||||
|
|
@ -54,6 +168,9 @@ void CreateOutputImage(int imageSize, float[] imageData, string imageOutputPath)
|
||||||
data.SaveTo(stream);
|
data.SaveTo(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
internal enum OpCode { VarX, VarY, Const, Add, Sub, Mul, Max, Min, Neg, Square, Sqrt }
|
internal enum OpCode { VarX, VarY, Const, Add, Sub, Mul, Max, Min, Neg, Square, Sqrt }
|
||||||
|
|
||||||
internal readonly record struct Operand
|
internal readonly record struct Operand
|
||||||
|
|
@ -184,7 +301,7 @@ internal static class Parsing
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool shouldEliminateConstants = false;
|
bool shouldEliminateConstants = true;
|
||||||
|
|
||||||
if (shouldEliminateConstants)
|
if (shouldEliminateConstants)
|
||||||
{
|
{
|
||||||
|
|
@ -194,29 +311,25 @@ internal static class Parsing
|
||||||
switch (instruction.OpCode)
|
switch (instruction.OpCode)
|
||||||
{
|
{
|
||||||
case (OpCode.Add or OpCode.Sub or OpCode.Mul) when instruction.A.IsConstant || instruction.B.IsConstant:
|
case (OpCode.Add or OpCode.Sub or OpCode.Mul) when instruction.A.IsConstant || instruction.B.IsConstant:
|
||||||
|
switch (instruction)
|
||||||
{
|
{
|
||||||
instruction = instruction switch
|
case { A: { IsConstant: true } a, B.IsConstant: false }:
|
||||||
{
|
instruction = instruction with { C = instructions[a].C };
|
||||||
{ A.IsConstant: true, B.IsConstant: false } => instruction with
|
break;
|
||||||
{
|
case { A.IsConstant: false, B: { IsConstant: true } b }:
|
||||||
C = instructions[instruction.A].C
|
instruction = instruction with { C = instructions[b].C };
|
||||||
},
|
break;
|
||||||
{ A.IsConstant: false, B.IsConstant: true } => instruction with
|
case { OpCode: var opCode, A: { IsConstant: true } a, B: { IsConstant: true } b }:
|
||||||
{
|
instruction = instruction with { C = EvaluateExpression(opCode, instructions[a].C, instructions[b].C) };
|
||||||
C = instructions[instruction.B].C
|
break;
|
||||||
},
|
case { A.IsConstant: false, B.IsConstant: false }:
|
||||||
{ A.IsConstant: true, B.IsConstant: true } => instruction with
|
break;
|
||||||
{
|
}
|
||||||
C = EvaluateExpression(instruction.OpCode, instructions[instruction.A].C,
|
|
||||||
instructions[instruction.B].C)
|
|
||||||
},
|
|
||||||
{ A.IsConstant: false, B.IsConstant: false } => instruction
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// relocate all offsets, now that we've nuked all the constants
|
||||||
foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions))
|
foreach (ref Instruction instruction in CollectionsMarshal.AsSpan(instructions))
|
||||||
{
|
{
|
||||||
switch (instruction.OpCode)
|
switch (instruction.OpCode)
|
||||||
|
|
@ -225,33 +338,36 @@ internal static class Parsing
|
||||||
int offset = instruction.Out;
|
int offset = instruction.Out;
|
||||||
foreach (ref Instruction otherInstruction in CollectionsMarshal.AsSpan(instructions))
|
foreach (ref Instruction otherInstruction in CollectionsMarshal.AsSpan(instructions))
|
||||||
{
|
{
|
||||||
if (otherInstruction.Out >= offset)
|
int newOperandOut = otherInstruction.Out >= offset
|
||||||
|
? otherInstruction.Out - 1
|
||||||
|
: otherInstruction.Out;
|
||||||
|
|
||||||
|
int newOperandA = otherInstruction.A >= offset
|
||||||
|
? otherInstruction.A - 1
|
||||||
|
: otherInstruction.A;
|
||||||
|
|
||||||
|
int newOperandB = otherInstruction.B >= offset
|
||||||
|
? otherInstruction.B - 1
|
||||||
|
: otherInstruction.B;
|
||||||
|
|
||||||
|
if (newOperandOut == otherInstruction.Out
|
||||||
|
&& newOperandA == otherInstruction.A
|
||||||
|
&& newOperandB == otherInstruction.B)
|
||||||
{
|
{
|
||||||
otherInstruction = otherInstruction with { Out = new Operand(otherInstruction.Out - 1) };
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherInstruction.A >= offset)
|
|
||||||
{
|
|
||||||
otherInstruction = otherInstruction with
|
otherInstruction = otherInstruction with
|
||||||
{
|
{
|
||||||
A = new Operand(otherInstruction.A - 1, isConstant: otherInstruction.A.IsConstant)
|
Out = new Operand(newOperandOut),
|
||||||
|
A = new Operand(newOperandA, isConstant: otherInstruction.A.IsConstant),
|
||||||
|
B = new Operand(newOperandB, isConstant: otherInstruction.B.IsConstant),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherInstruction.B >= offset)
|
|
||||||
{
|
|
||||||
otherInstruction = otherInstruction with
|
|
||||||
{
|
|
||||||
B = new Operand(otherInstruction.B - 1, otherInstruction.B.IsConstant)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// eliminate all constants now :)
|
// eliminate all constants now :)
|
||||||
int totalNumberOfInstructions = instructions.Count;
|
int totalNumberOfInstructions = instructions.Count;
|
||||||
int numberOfRemovedInstructions = instructions.RemoveAll(i => i.OpCode == OpCode.Const);
|
int numberOfRemovedInstructions = instructions.RemoveAll(i => i.OpCode == OpCode.Const);
|
||||||
|
|
@ -262,51 +378,11 @@ internal static class Parsing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class Compiler
|
|
||||||
{
|
|
||||||
static (AssemblyBuilder, MethodBuilder, TypeBuilder) CreateDynamicAssemblyWithMethodBuilder(string assemblyName,
|
|
||||||
string moduleName, string typeName, string methodName)
|
|
||||||
{
|
|
||||||
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
|
|
||||||
var moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName);
|
|
||||||
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);
|
|
||||||
var methodBuilder =
|
|
||||||
typeBuilder.DefineMethod(
|
|
||||||
methodName,
|
|
||||||
MethodAttributes.Public | MethodAttributes.Static,
|
|
||||||
typeof(float),
|
|
||||||
[typeof(float), typeof(float)]);
|
|
||||||
|
|
||||||
// #TODO: how do this?
|
|
||||||
// assemblyBuilder.EntryPoint = methodBuilder;
|
|
||||||
|
|
||||||
return (assemblyBuilder, methodBuilder, typeBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void BuildProgramToAssembly(Instruction[] instructions)
|
|
||||||
{
|
|
||||||
var (assemblyBuilder, methodBuilder, typeBuilder) = CreateDynamicAssemblyWithMethodBuilder(
|
|
||||||
assemblyName: "SharperoAssembly",
|
|
||||||
moduleName: "SharperoModule",
|
|
||||||
typeName: "SharperoMain",
|
|
||||||
methodName: "Evaluate");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ExecuteProgramInAssembly()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float[] Evaluate(Instruction[] instructions, int imageSize)
|
|
||||||
{
|
|
||||||
return Array.Empty<float>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class Interpreter
|
internal static class Interpreter
|
||||||
{
|
{
|
||||||
public static float[] Evaluate(Instruction[] instructions, int imageSize)
|
public static float[] Evaluate(Instruction[] instructions, int imageSize, float[]? result = null)
|
||||||
{
|
{
|
||||||
float[] result = new float[imageSize * imageSize];
|
result ??= new float[imageSize * imageSize];
|
||||||
|
|
||||||
int chunkSize = Vector<float>.Count;
|
int chunkSize = Vector<float>.Count;
|
||||||
Parallel.For(0, (imageSize * imageSize) / chunkSize, chunkIdx =>
|
Parallel.For(0, (imageSize * imageSize) / chunkSize, chunkIdx =>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Lokad.ILPack" Version="0.3.1" />
|
<PackageReference Include="Lokad.ILPack" Version="0.3.1" />
|
||||||
<PackageReference Include="SkiaSharp" Version="3.119.2" />
|
<PackageReference Include="Raylib-cs" Version="8.0.0" />
|
||||||
|
<PackageReference Include="SkiaSharp" Version="3.119.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue