add raylib based interactive visualization of the rendering

This commit is contained in:
profan 2026-06-04 23:49:15 +01:00
parent 3625fce0c9
commit c6f5411f91
2 changed files with 186 additions and 109 deletions

View File

@ -5,53 +5,170 @@ 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();
string combinedOutputString = string.Empty;
(bool success, double totalTimeTakenSecondsOutput) = BenchmarkFunction(() => // STAThread is required if you deploy using NativeAOT on Windows
// See https://github.com/raylib-cs/raylib-cs/issues/301
[STAThread]
void Main()
{ {
(float[] result, double timeTakenSecondsEvaluate) = BenchmarkFunction(() => 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
{ {
Instruction[] instructions = Parsing.Parse("Programs/prospero.vm"); fixed (float* currentOutputImageDataPtr = currentOutputImageData)
return Interpreter.Evaluate(instructions, imageSize: currentImageSize); {
}); Image currentOutputImage = new Image()
{
Format = PixelFormat.UncompressedR32,
Data = currentOutputImageDataPtr,
Width = currentOutputImageSize,
Height = currentOutputImageSize,
Mipmaps = 1
};
currentOutputTexture = Raylib.LoadTextureFromImage(currentOutputImage);
}
}
combinedOutputString += $" - took: {timeTakenSecondsEvaluate} seconds to evaluate the image!" + Environment.NewLine; bool shouldEvaluate = false;
bool shouldCancelUpdateTexture = false;
bool shouldUpdateTexture = false;
(bool success, double timeTakenSecondsOutput) = BenchmarkFunction(() => while (!Raylib.WindowShouldClose())
{ {
CreateOutputImage(currentImageSize, result, "prospero.jpg"); Raylib.BeginDrawing();
return true;
}); Raylib.ClearBackground(Color.White);
combinedOutputString += $" - took: {timeTakenSecondsOutput} seconds to write out image!" + Environment.NewLine; 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;
}
return true; 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);
Console.Write($"Sharpero took: {totalTimeTakenSecondsOutput} seconds to evaluate {currentImageSize}x{currentImageSize} image!" + Environment.NewLine + combinedOutputString); if (Raylib.IsKeyPressed(KeyboardKey.R))
{
shouldEvaluate = true;
}
(T, double) BenchmarkFunction<T>(Func<T> benchmarkedFunction) if (Raylib.IsKeyPressed(KeyboardKey.O))
{ {
var sw = Stopwatch.StartNew(); Task.Run(() =>
T benchmarkedResult = benchmarkedFunction(); {
return (benchmarkedResult, sw.Elapsed.TotalSeconds); GenerateOutputImage(currentImageSize: currentOutputImageSize, shouldWriteOutputImage: true);
});
}
Raylib.EndDrawing();
}
Raylib.UnloadShader(currentShader);
Raylib.UnloadTexture(currentOutputTexture);
Raylib.CloseWindow();
} }
void CreateOutputImage(int imageSize, float[] imageData, string imageOutputPath) float[] GenerateOutputImage(int currentImageSize, bool shouldWriteOutputImage = false)
{ {
byte[] imageDataBytes = imageData.Select(p => (byte)(p < 0 ? 255 : 0)).ToArray(); string combinedOutputString = string.Empty;
using var image = SKImage.FromPixelCopy(new SKImageInfo(imageSize, imageSize, SKColorType.Gray8), imageDataBytes);
using var data = image.Encode(SKEncodedImageFormat.Jpeg, 100); (float[] result, double totalTimeTakenSecondsOutput) = BenchmarkFunction(() =>
using var stream = File.OpenWrite(imageOutputPath); {
data.SaveTo(stream); (float[] result, double timeTakenSecondsEvaluate) = BenchmarkFunction(() =>
{
Instruction[] instructions = Parsing.Parse("Programs/prospero.vm");
return Interpreter.Evaluate(instructions, imageSize: currentImageSize);
});
combinedOutputString += $" - took: {timeTakenSecondsEvaluate} seconds to evaluate the image!" + Environment.NewLine;
if (shouldWriteOutputImage)
{
(bool success, double timeTakenSecondsOutput) = BenchmarkFunction(() =>
{
WriteOutputImage(currentImageSize, result, "prospero.jpg");
return true;
});
combinedOutputString += $" - took: {timeTakenSecondsOutput} seconds to write out image!" + Environment.NewLine;
}
return result;
});
Console.Write($"Sharpero took: {totalTimeTakenSecondsOutput} seconds to evaluate {currentImageSize}x{currentImageSize} image!" + Environment.NewLine + combinedOutputString);
(T, double) BenchmarkFunction<T>(Func<T> benchmarkedFunction)
{
var sw = Stopwatch.StartNew();
T benchmarkedResult = benchmarkedFunction();
return (benchmarkedResult, sw.Elapsed.TotalSeconds);
}
void WriteOutputImage(int imageSize, float[] imageData, string imageOutputPath)
{
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 data = image.Encode(SKEncodedImageFormat.Jpeg, 100);
using var stream = File.OpenWrite(imageOutputPath);
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 }
@ -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
{ {
{ A.IsConstant: true, B.IsConstant: false } => instruction with case { A: { IsConstant: true } a, B.IsConstant: false }:
{ instruction = instruction with { C = instructions[a].C };
C = instructions[instruction.A].C break;
}, case { A.IsConstant: false, B: { IsConstant: true } b }:
{ A.IsConstant: false, B.IsConstant: true } => instruction with instruction = instruction with { C = instructions[b].C };
{ break;
C = instructions[instruction.B].C case { OpCode: var opCode, A: { IsConstant: true } a, B: { IsConstant: true } b }:
}, instruction = instruction with { C = EvaluateExpression(opCode, instructions[a].C, instructions[b].C) };
{ A.IsConstant: true, B.IsConstant: true } => instruction with break;
{ case { A.IsConstant: false, B.IsConstant: false }:
C = EvaluateExpression(instruction.OpCode, instructions[instruction.A].C, break;
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 Out = new Operand(newOperandOut),
{ A = new Operand(newOperandA, isConstant: otherInstruction.A.IsConstant),
A = new Operand(otherInstruction.A - 1, 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 =>

View File

@ -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>