features/modernize #1
@@ -1,8 +1,7 @@
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -10,168 +9,119 @@ namespace SimpleRayTracer.Interop;
|
||||
|
||||
internal sealed partial class InteropSmokeTest : IDisposable
|
||||
{
|
||||
private IntPtr _scene;
|
||||
private IntPtr _job;
|
||||
private SrtScene? _scene;
|
||||
private SrtRenderJob? _job;
|
||||
private WriteableBitmap? _bitmap;
|
||||
private byte[]? _scratchBgra;
|
||||
|
||||
public WriteableBitmap Bitmap => _bitmap ?? throw new InvalidOperationException("Not started");
|
||||
|
||||
private static Quaternion FromEulerDegrees(Vector3 degrees)
|
||||
{
|
||||
var radians = degrees * (MathF.PI / 180f);
|
||||
var cy = MathF.Cos(radians.Z * 0.5f);
|
||||
var sy = MathF.Sin(radians.Z * 0.5f);
|
||||
var cp = MathF.Cos(radians.Y * 0.5f);
|
||||
var sp = MathF.Sin(radians.Y * 0.5f);
|
||||
var cr = MathF.Cos(radians.X * 0.5f);
|
||||
var sr = MathF.Sin(radians.X * 0.5f);
|
||||
return new Quaternion
|
||||
{
|
||||
W = cr * cp * cy + sr * sp * sy,
|
||||
X = sr * cp * cy - cr * sp * sy,
|
||||
Y = cr * sp * cy + sr * cp * sy,
|
||||
Z = cr * cp * sy - sr * sp * cy,
|
||||
};
|
||||
}
|
||||
|
||||
public void StartAsync(uint width = 640, uint height = 360)
|
||||
{
|
||||
if (_scene != IntPtr.Zero || _job != IntPtr.Zero)
|
||||
if (_scene is not null || _job is not null)
|
||||
throw new InvalidOperationException("Already started");
|
||||
|
||||
_scene = SrtNative.srt_scene_create(
|
||||
triangle_count: 0,
|
||||
texture_count: 1,
|
||||
material_count: 8,
|
||||
punctual_light_count: 1);
|
||||
_scene = SrtScene.Create(triangleCount: 0, textureCount: 1, materialCount: 8, punctualLightCount: 1);
|
||||
|
||||
if (_scene == IntPtr.Zero)
|
||||
throw new InvalidOperationException("srt_scene_create failed (native DLL not found or init failed)");
|
||||
|
||||
unsafe
|
||||
{
|
||||
// Camera similar to native defaults: at (0,0,5) looking towards -Z.
|
||||
var cam = new SrtNative.CameraParams
|
||||
{
|
||||
position = new Vector3(0, 0, 5),
|
||||
rotation = Quaternion.Identity,
|
||||
focal_length = 0.025f,
|
||||
size_x = 0.036f,
|
||||
aspect_ratio = width / (float)height,
|
||||
};
|
||||
ThrowIfNotOk(SrtNative.srt_scene_camera_set(_scene, &cam));
|
||||
_scene.SetCamera(position: new Vector3(-7.5f, 2.5f, 0.0f), rotation: FromEulerDegrees(new Vector3(10, -90, 0)), focalLength: 0.025f, sizeX: 0.036f, aspectRatio: width / (float)height);
|
||||
|
||||
// Basic sky so we don't render into black/undefined environment.
|
||||
// (This also avoids confusion while sky HDR bindings are still being wired into the app UI.)
|
||||
ThrowIfNotOk(SrtNative.srt_scene_set_sky_constant(_scene, SrtNative.Vec3.Of(1, 1, 1), intensity: 0.0f));
|
||||
_scene.SetSky(SrtSky.Constant(new Vector3(1, 1, 1), intensity: 1.0f));
|
||||
|
||||
// Directional light
|
||||
var lightDir = Vector3.Normalize(new Vector3(0.6f, 1.0f, 0.25f));
|
||||
var sun = new SrtNative.DirectionalLight
|
||||
{
|
||||
direction = new Vector3(lightDir.X, lightDir.Y, lightDir.Z),
|
||||
color = new Vector3(1.0f, 0.93f, 0.87f),
|
||||
intensity = 2.0f,
|
||||
angular_diameter = 0.53f,
|
||||
};
|
||||
_ = SrtNative.srt_scene_add_directional_light(_scene, &sun, out _);
|
||||
_scene.AddDirectionalLight(direction: lightDir, color: new Vector3(1.0f, 0.93f, 0.87f), intensity: 2.0f, angularDiameter: 0.53f);
|
||||
|
||||
// StandardLit material (id 0 in fresh collection)
|
||||
var props = new SrtNative.StandardLitProperties
|
||||
{
|
||||
albedo = new Vector3(0.8f, 0.2f, 0.2f),
|
||||
diffuse_roughness = 0.0f,
|
||||
roughness = 0.6f,
|
||||
metallic = 0.0f,
|
||||
_scene.LoadMeshFromFile("F:/c/SimpleRayTracer/native/assets/sponza.fbx");
|
||||
_scene.Commit();
|
||||
|
||||
// Match native defaults: "no texture" uses INVALID_TEXTURE_ID (0xFFFF).
|
||||
albedo_texture = SrtNative.TextureHandle.Invalid(),
|
||||
normal_texture = SrtNative.TextureHandle.Invalid(),
|
||||
roughness_texture = SrtNative.TextureHandle.Invalid(),
|
||||
metallic_texture = SrtNative.TextureHandle.Invalid(),
|
||||
};
|
||||
ThrowIfNotOk(SrtNative.srt_scene_create_standard_lit_material(_scene, &props, emission: SrtNative.Vec3.Of(0, 0, 0), out var mat));
|
||||
|
||||
// A single triangle in front of the camera.
|
||||
ThrowIfNotOk(SrtNative.srt_scene_add_mesh_model(_scene, triangle_reserve: 1, out var modelId));
|
||||
|
||||
var tri = new SrtNative.Triangle
|
||||
{
|
||||
v0 = new SrtNative.Vertex
|
||||
{
|
||||
position = new Vector3(-1.0f, -1.0f, 0.0f),
|
||||
normal = new Vector3(0, 0, 1),
|
||||
tangent = new Vector3(1, 0, 0),
|
||||
color = new Vector3(1, 1, 1),
|
||||
uv = new Vector2 { X = 0, Y = 0 },
|
||||
},
|
||||
v1 = new SrtNative.Vertex
|
||||
{
|
||||
position = new Vector3(1.0f, -1.0f, 0.0f),
|
||||
normal = new Vector3(0, 0, 1),
|
||||
tangent = new Vector3(1, 0, 0),
|
||||
color = new Vector3(1, 1, 1),
|
||||
uv = new Vector2 { X = 1, Y = 0 },
|
||||
},
|
||||
v2 = new SrtNative.Vertex
|
||||
{
|
||||
position = new Vector3(0.0f, 1.0f, 0.0f),
|
||||
normal = new Vector3(0, 0, 1),
|
||||
tangent = new Vector3(1, 0, 0),
|
||||
color = new Vector3(1, 1, 1),
|
||||
uv = new Vector2 { X = 0.5f, Y = 1 },
|
||||
},
|
||||
material_id = mat.id,
|
||||
};
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_scene_mesh_model_set_triangles(_scene, modelId, &tri, 1));
|
||||
|
||||
var identity = SrtNative.Mat4.Identity();
|
||||
ThrowIfNotOk(SrtNative.srt_scene_add_mesh_instance(_scene, modelId, &identity, out _));
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_scene_commit(_scene));
|
||||
|
||||
var cfg = new SrtNative.RenderingConfig
|
||||
{
|
||||
width = width,
|
||||
height = height,
|
||||
sample_count = 64,
|
||||
max_depth = 4,
|
||||
bucket_size = 64,
|
||||
};
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_render_job_create(
|
||||
_scene,
|
||||
&cfg,
|
||||
aov_flags: SrtNative.SRT_AOV_BEAUTY,
|
||||
rendering_mode: SrtNative.SRT_RENDER_TILE_BASED,
|
||||
seed: 1,
|
||||
out _job));
|
||||
}
|
||||
_job = _scene.CreateRenderJob(width: width, height: height, sampleCount: 64, maxDepth: 4, bucketSize: 64, seed: 1);
|
||||
|
||||
_bitmap = new WriteableBitmap((int)width, (int)height);
|
||||
|
||||
// Run renderer on background thread.
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var r = SrtNative.srt_render_job_start(_job);
|
||||
if (r != SrtNative.Result.Ok)
|
||||
try
|
||||
{
|
||||
_job.StartBlocking();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// No UI exception throwing from background thread; this is a smoke test.
|
||||
System.Diagnostics.Debug.WriteLine($"Native render failed: {r}");
|
||||
System.Diagnostics.Debug.WriteLine($"Native render failed: {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public unsafe void CopyLatestToBitmap()
|
||||
{
|
||||
if (_job == IntPtr.Zero || _bitmap is null)
|
||||
if (_job is null || _bitmap is null)
|
||||
return;
|
||||
|
||||
var pixelBuffer = _bitmap.PixelBuffer;
|
||||
using var stream = pixelBuffer.AsStream();
|
||||
|
||||
// Ensure we have enough capacity
|
||||
var res = SrtNative.srt_render_job_get_aov_desc(_job, SrtNative.SRT_AOV_BEAUTY_INDEX, out var w, out var h, out _);
|
||||
var res = SrtNative.srt_render_job_get_aov_desc(_job.Handle, SrtNative.SRT_AOV_BEAUTY_INDEX, out var w, out var h, out _);
|
||||
if (res != SrtNative.Result.Ok)
|
||||
return;
|
||||
|
||||
var expectedSize = checked((int)(w * h * 4));
|
||||
if (stream.Length != expectedSize)
|
||||
if ((uint)_bitmap.PixelWidth != w || (uint)_bitmap.PixelHeight != h)
|
||||
{
|
||||
stream.SetLength(expectedSize);
|
||||
// Bitmap size mismatch (shouldn't happen in this smoke test).
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = new byte[expectedSize];
|
||||
fixed (byte* dst = temp)
|
||||
var pixelBuffer = _bitmap.PixelBuffer;
|
||||
var expectedBytes = checked(w * h * 4u);
|
||||
if (pixelBuffer.Length < expectedBytes)
|
||||
{
|
||||
_ = SrtNative.srt_render_job_copy_aov_bgra8(_job, SrtNative.SRT_AOV_BEAUTY_INDEX, dst, w * 4);
|
||||
// Unexpected: WriteableBitmap pixel buffer is smaller than required.
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast path: try raw pointer write (true zero-copy into PixelBuffer).
|
||||
if (WinRtBufferByteAccess.TryGetPointer(pixelBuffer, out var dst) && dst != null)
|
||||
{
|
||||
_job.CopyBeautyBgra8(dst, w * 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: stream write. Still avoids per-frame allocations by reusing a scratch buffer.
|
||||
int n = checked((int)expectedBytes);
|
||||
_scratchBgra ??= new byte[n];
|
||||
if (_scratchBgra.Length != n)
|
||||
{
|
||||
_scratchBgra = new byte[n];
|
||||
}
|
||||
|
||||
fixed (byte* tmp = _scratchBgra)
|
||||
{
|
||||
_job.CopyBeautyBgra8(tmp, w * 4);
|
||||
}
|
||||
|
||||
using var stream = pixelBuffer.AsStream();
|
||||
stream.Position = 0;
|
||||
stream.Write(temp, 0, temp.Length);
|
||||
stream.Write(_scratchBgra, 0, _scratchBgra.Length);
|
||||
}
|
||||
|
||||
_bitmap.Invalidate();
|
||||
}
|
||||
|
||||
@@ -179,33 +129,19 @@ internal sealed partial class InteropSmokeTest : IDisposable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_job == IntPtr.Zero)
|
||||
if (_job is null)
|
||||
return true;
|
||||
|
||||
var r = SrtNative.srt_render_job_is_done(_job, out var done);
|
||||
return r == SrtNative.Result.Ok && done != 0;
|
||||
return _job.IsDone;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowIfNotOk(SrtNative.Result r, [CallerArgumentExpression(nameof(r))] string? call = null)
|
||||
{
|
||||
if (r != SrtNative.Result.Ok)
|
||||
throw new InvalidOperationException($"Native call failed ({call}): {r}");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_job != IntPtr.Zero)
|
||||
{
|
||||
SrtNative.srt_render_job_request_stop(_job);
|
||||
SrtNative.srt_render_job_destroy(_job);
|
||||
_job = IntPtr.Zero;
|
||||
}
|
||||
_job?.Dispose();
|
||||
_job = null;
|
||||
|
||||
if (_scene != IntPtr.Zero)
|
||||
{
|
||||
SrtNative.srt_scene_destroy(_scene);
|
||||
_scene = IntPtr.Zero;
|
||||
}
|
||||
_scene?.Dispose();
|
||||
_scene = null;
|
||||
}
|
||||
}
|
||||
|
||||
295
managed/SimpleRayTracer/Interop/SrtApi.cs
Normal file
295
managed/SimpleRayTracer/Interop/SrtApi.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.IO;
|
||||
|
||||
namespace SimpleRayTracer.Interop;
|
||||
|
||||
internal sealed class SrtScene : IDisposable
|
||||
{
|
||||
internal IntPtr Handle { get; private set; }
|
||||
|
||||
public static SrtScene Create(ulong triangleCount, ushort textureCount, byte materialCount, uint punctualLightCount)
|
||||
{
|
||||
var h = SrtNative.srt_scene_create(triangleCount, textureCount, materialCount, punctualLightCount);
|
||||
if (h == IntPtr.Zero)
|
||||
throw new InvalidOperationException("srt_scene_create failed (native DLL not found or init failed)");
|
||||
|
||||
return new SrtScene(h);
|
||||
}
|
||||
|
||||
private SrtScene(IntPtr handle) => Handle = handle;
|
||||
|
||||
public void SetCamera(Vector3 position, Quaternion rotation, float focalLength, float sizeX, float aspectRatio)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var cam = new SrtNative.CameraParams
|
||||
{
|
||||
position = SrtNative.Vec3.Of(position.X, position.Y, position.Z),
|
||||
rotation = new SrtNative.Quat { x = rotation.X, y = rotation.Y, z = rotation.Z, w = rotation.W },
|
||||
focal_length = focalLength,
|
||||
size_x = sizeX,
|
||||
aspect_ratio = aspectRatio,
|
||||
};
|
||||
ThrowIfNotOk(SrtNative.srt_scene_camera_set(Handle, &cam));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSky(SrtSky sky)
|
||||
{
|
||||
if (sky is null) throw new ArgumentNullException(nameof(sky));
|
||||
sky.Apply(this);
|
||||
}
|
||||
|
||||
public uint AddDirectionalLight(Vector3 direction, Vector3 color, float intensity, float angularDiameter)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var l = new SrtNative.DirectionalLight
|
||||
{
|
||||
direction = SrtNative.Vec3.Of(direction.X, direction.Y, direction.Z),
|
||||
color = SrtNative.Vec3.Of(color.X, color.Y, color.Z),
|
||||
intensity = intensity,
|
||||
angular_diameter = angularDiameter,
|
||||
};
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_scene_add_directional_light(Handle, &l, out var id));
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public SrtNative.MaterialHandle CreateStandardLitMaterial(Vector3 albedo, float roughness, float diffuseRoughness, float metallic,
|
||||
SrtNative.TextureHandle albedoTexture, SrtNative.TextureHandle normalTexture, SrtNative.TextureHandle roughnessTexture, SrtNative.TextureHandle metallicTexture,
|
||||
Vector3 emission)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var props = new SrtNative.StandardLitProperties
|
||||
{
|
||||
albedo = SrtNative.Vec3.Of(albedo.X, albedo.Y, albedo.Z),
|
||||
roughness = roughness,
|
||||
diffuse_roughness = diffuseRoughness,
|
||||
metallic = metallic,
|
||||
albedo_texture = albedoTexture,
|
||||
normal_texture = normalTexture,
|
||||
roughness_texture = roughnessTexture,
|
||||
metallic_texture = metallicTexture,
|
||||
};
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_scene_create_standard_lit_material(
|
||||
Handle,
|
||||
&props,
|
||||
SrtNative.Vec3.Of(emission.X, emission.Y, emission.Z),
|
||||
out var mat));
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
|
||||
public uint AddMeshModel(ulong triangleReserve)
|
||||
{
|
||||
ThrowIfNotOk(SrtNative.srt_scene_add_mesh_model(Handle, triangleReserve, out var id));
|
||||
return id;
|
||||
}
|
||||
|
||||
public void SetMeshTriangles(uint modelId, ReadOnlySpan<SrtNative.Triangle> triangles)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SrtNative.Triangle* p = triangles)
|
||||
{
|
||||
ThrowIfNotOk(SrtNative.srt_scene_mesh_model_set_triangles(Handle, modelId, p, (uint)triangles.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint AddMeshInstance(uint modelId, in SrtNative.Mat4 localToWorld)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (SrtNative.Mat4* p = &localToWorld)
|
||||
{
|
||||
ThrowIfNotOk(SrtNative.srt_scene_add_mesh_instance(Handle, modelId, p, out var instanceId));
|
||||
return instanceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit() => ThrowIfNotOk(SrtNative.srt_scene_commit(Handle));
|
||||
|
||||
public SrtNative.TextureHandle LoadTextureFromFile(string path, bool srgb = true, bool mipmap = true, SrtNative.TextureStride stride = SrtNative.TextureStride.UInt8)
|
||||
{
|
||||
if (path is null) throw new ArgumentNullException(nameof(path));
|
||||
path = ResolveAssetPath(path);
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_scene_texture_load(
|
||||
Handle,
|
||||
path,
|
||||
srgb: (byte)(srgb ? 1 : 0),
|
||||
mipmap: (byte)(mipmap ? 1 : 0),
|
||||
stride: (uint)stride,
|
||||
out var tex));
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
public SrtNative.MeshHandle LoadMeshFromFile(string path)
|
||||
{
|
||||
if (path is null) throw new ArgumentNullException(nameof(path));
|
||||
path = ResolveAssetPath(path);
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_scene_load_mesh(Handle, path, out var mesh));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private static string ResolveAssetPath(string path)
|
||||
{
|
||||
if (Path.IsPathRooted(path))
|
||||
return path;
|
||||
|
||||
// Prefer app base directory (WinUI packaged/unpackaged), then fall back to CWD.
|
||||
var baseDir = AppContext.BaseDirectory;
|
||||
var candidate = Path.GetFullPath(Path.Combine(baseDir, path));
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
|
||||
candidate = Path.GetFullPath(path);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
public SrtRenderJob CreateRenderJob(uint width, uint height, uint sampleCount, byte maxDepth, uint bucketSize, uint seed,
|
||||
uint aovFlags = SrtNative.SRT_AOV_BEAUTY, uint renderingMode = SrtNative.SRT_RENDER_TILE_BASED)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var cfg = new SrtNative.RenderingConfig
|
||||
{
|
||||
width = width,
|
||||
height = height,
|
||||
sample_count = sampleCount,
|
||||
max_depth = maxDepth,
|
||||
bucket_size = bucketSize,
|
||||
};
|
||||
|
||||
ThrowIfNotOk(SrtNative.srt_render_job_create(Handle, &cfg, aovFlags, renderingMode, seed, out var job));
|
||||
return new SrtRenderJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != IntPtr.Zero)
|
||||
{
|
||||
SrtNative.srt_scene_destroy(Handle);
|
||||
Handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ThrowIfNotOk(SrtNative.Result r)
|
||||
{
|
||||
if (r != SrtNative.Result.Ok)
|
||||
throw new InvalidOperationException($"Native call failed: {r}");
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SrtRenderJob : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; private set; }
|
||||
|
||||
internal SrtRenderJob(IntPtr handle) => Handle = handle;
|
||||
|
||||
public void StartBlocking() => SrtScene.ThrowIfNotOk(SrtNative.srt_render_job_start(Handle));
|
||||
|
||||
public bool IsDone
|
||||
{
|
||||
get
|
||||
{
|
||||
var r = SrtNative.srt_render_job_is_done(Handle, out var done);
|
||||
return r == SrtNative.Result.Ok && done != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void CopyBeautyBgra8(byte* dst, uint dstStrideBytes)
|
||||
{
|
||||
_ = SrtNative.srt_render_job_copy_aov_bgra8(Handle, SrtNative.SRT_AOV_BEAUTY_INDEX, dst, dstStrideBytes);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != IntPtr.Zero)
|
||||
{
|
||||
SrtNative.srt_render_job_request_stop(Handle);
|
||||
SrtNative.srt_render_job_destroy(Handle);
|
||||
Handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class SrtSky
|
||||
{
|
||||
internal abstract unsafe void Apply(SrtScene scene);
|
||||
|
||||
public static SrtSky None() => new NoneSky();
|
||||
public static SrtSky Constant(Vector3 color, float intensity) => new ConstantSky(color, intensity);
|
||||
public static SrtSky Hdr(SrtNative.TextureHandle hdri, float intensity) => new HdrSky(hdri, intensity);
|
||||
|
||||
private sealed class NoneSky : SrtSky
|
||||
{
|
||||
internal override unsafe void Apply(SrtScene scene)
|
||||
{
|
||||
var desc = new SrtNative.SkyDesc { kind = (uint)SrtNative.SkyKind.None, data_size = 0, data = null };
|
||||
SrtScene.ThrowIfNotOk(SrtNative.srt_scene_set_sky(scene.Handle, &desc));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ConstantSky : SrtSky
|
||||
{
|
||||
private readonly Vector3 _color;
|
||||
private readonly float _intensity;
|
||||
|
||||
public ConstantSky(Vector3 color, float intensity)
|
||||
{
|
||||
_color = color;
|
||||
_intensity = intensity;
|
||||
}
|
||||
|
||||
internal override unsafe void Apply(SrtScene scene)
|
||||
{
|
||||
var cd = new SrtNative.SkyConstantDesc
|
||||
{
|
||||
color = SrtNative.Vec3.Of(_color.X, _color.Y, _color.Z),
|
||||
intensity = _intensity,
|
||||
};
|
||||
var desc = new SrtNative.SkyDesc
|
||||
{
|
||||
kind = (uint)SrtNative.SkyKind.Constant,
|
||||
data_size = (uint)sizeof(SrtNative.SkyConstantDesc),
|
||||
data = &cd,
|
||||
};
|
||||
SrtScene.ThrowIfNotOk(SrtNative.srt_scene_set_sky(scene.Handle, &desc));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class HdrSky : SrtSky
|
||||
{
|
||||
private readonly SrtNative.TextureHandle _hdri;
|
||||
private readonly float _intensity;
|
||||
|
||||
public HdrSky(SrtNative.TextureHandle hdri, float intensity)
|
||||
{
|
||||
_hdri = hdri;
|
||||
_intensity = intensity;
|
||||
}
|
||||
|
||||
internal override unsafe void Apply(SrtScene scene)
|
||||
{
|
||||
var hd = new SrtNative.SkyHdrDesc { hdri = _hdri, intensity = _intensity };
|
||||
var desc = new SrtNative.SkyDesc
|
||||
{
|
||||
kind = (uint)SrtNative.SkyKind.Hdr,
|
||||
data_size = (uint)sizeof(SrtNative.SkyHdrDesc),
|
||||
data = &hd,
|
||||
};
|
||||
SrtScene.ThrowIfNotOk(SrtNative.srt_scene_set_sky(scene.Handle, &desc));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SimpleRayTracer.Interop;
|
||||
@@ -22,6 +21,8 @@ internal static unsafe class SrtNative
|
||||
internal struct Vec2
|
||||
{
|
||||
public float x, y;
|
||||
|
||||
public static Vec2 Of(float x, float y) => new() { x = x, y = y };
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
@@ -60,8 +61,8 @@ internal static unsafe class SrtNative
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CameraParams
|
||||
{
|
||||
public Vector3 position;
|
||||
public Quaternion rotation;
|
||||
public Vec3 position;
|
||||
public Quat rotation;
|
||||
|
||||
public float focal_length;
|
||||
public float size_x;
|
||||
@@ -71,8 +72,8 @@ internal static unsafe class SrtNative
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DirectionalLight
|
||||
{
|
||||
public Vector3 direction;
|
||||
public Vector3 color;
|
||||
public Vec3 direction;
|
||||
public Vec3 color;
|
||||
public float intensity;
|
||||
public float angular_diameter;
|
||||
}
|
||||
@@ -94,7 +95,7 @@ internal static unsafe class SrtNative
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct StandardLitProperties
|
||||
{
|
||||
public Vector3 albedo;
|
||||
public Vec3 albedo;
|
||||
public float diffuse_roughness;
|
||||
public float roughness;
|
||||
public float metallic;
|
||||
@@ -137,11 +138,11 @@ internal static unsafe class SrtNative
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct Vertex
|
||||
{
|
||||
public Vector3 position;
|
||||
public Vector3 normal;
|
||||
public Vector3 tangent;
|
||||
public Vector3 color;
|
||||
public Vector2 uv;
|
||||
public Vec3 position;
|
||||
public Vec3 normal;
|
||||
public Vec3 tangent;
|
||||
public Vec3 color;
|
||||
public Vec2 uv;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
@@ -153,6 +154,47 @@ internal static unsafe class SrtNative
|
||||
public uint material_id;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MeshHandle
|
||||
{
|
||||
public uint model_id;
|
||||
public uint instance_id;
|
||||
|
||||
public ulong triangle_id;
|
||||
public ulong triangle_count;
|
||||
public ushort material_id;
|
||||
public ushort material_count;
|
||||
}
|
||||
|
||||
internal enum SkyKind : uint
|
||||
{
|
||||
None = 0,
|
||||
Constant = 1,
|
||||
Hdr = 2,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SkyDesc
|
||||
{
|
||||
public uint kind;
|
||||
public uint data_size;
|
||||
public void* data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SkyConstantDesc
|
||||
{
|
||||
public Vec3 color;
|
||||
public float intensity;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SkyHdrDesc
|
||||
{
|
||||
public TextureHandle hdri;
|
||||
public float intensity;
|
||||
}
|
||||
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern IntPtr srt_scene_create(ulong triangle_count, ushort texture_count, byte material_count, uint punctual_light_count);
|
||||
|
||||
@@ -180,6 +222,9 @@ internal static unsafe class SrtNative
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Result srt_scene_set_sky_none(IntPtr scene);
|
||||
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Result srt_scene_set_sky(IntPtr scene, SkyDesc* desc);
|
||||
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Result srt_scene_set_sky_constant(IntPtr scene, Vec3 color, float intensity);
|
||||
|
||||
@@ -195,6 +240,9 @@ internal static unsafe class SrtNative
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Result srt_scene_add_mesh_instance(IntPtr scene, uint model_id, Mat4* local_to_world, out uint instance_id);
|
||||
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Result srt_scene_load_mesh(IntPtr scene, [MarshalAs(UnmanagedType.LPUTF8Str)] string filename_utf8, out MeshHandle mesh);
|
||||
|
||||
[DllImport(_DLL, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Result srt_render_job_create(
|
||||
IntPtr scene,
|
||||
|
||||
93
managed/SimpleRayTracer/Interop/WinRtBufferByteAccess.cs
Normal file
93
managed/SimpleRayTracer/Interop/WinRtBufferByteAccess.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Storage.Streams;
|
||||
using WinRT;
|
||||
|
||||
namespace SimpleRayTracer.Interop;
|
||||
|
||||
// Allows zero-copy access to WriteableBitmap.PixelBuffer.
|
||||
internal static class WinRtBufferByteAccess
|
||||
{
|
||||
private static readonly Guid IID_IBufferByteAccess = new("905a0fef-bc53-11df-8c49-001e4fc686da");
|
||||
|
||||
public static unsafe bool TryGetPointer(IBuffer buffer, out byte* ptr)
|
||||
{
|
||||
ptr = null;
|
||||
if (buffer is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var unk = IntPtr.Zero;
|
||||
var releaseUnk = false;
|
||||
|
||||
var acc = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
if (buffer is not IWinRTObject winrt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
unk = winrt.NativeObject.ThisPtr;
|
||||
if (unk == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Marshal.AddRef(unk);
|
||||
releaseUnk = true;
|
||||
|
||||
var iid = IID_IBufferByteAccess;
|
||||
var hr = Marshal.QueryInterface(unk, in iid, out acc);
|
||||
if (hr < 0 || acc == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// vtable: [0]=QI, [1]=AddRef, [2]=Release, [3]=Buffer
|
||||
var vtbl = *(IntPtr*)acc;
|
||||
var bufferFnPtr = *((IntPtr*)vtbl + 3);
|
||||
if (bufferFnPtr == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// IMPORTANT: COM uses stdcall on Windows.
|
||||
var bufferFn = (delegate* unmanaged[Stdcall]<IntPtr, IntPtr*, int>)bufferFnPtr;
|
||||
|
||||
var raw = IntPtr.Zero;
|
||||
hr = bufferFn(acc, &raw);
|
||||
if (hr < 0 || raw == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = (byte*)raw;
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (acc != IntPtr.Zero)
|
||||
{
|
||||
Marshal.Release(acc);
|
||||
}
|
||||
|
||||
if (releaseUnk && unk != IntPtr.Zero)
|
||||
{
|
||||
Marshal.Release(unk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe byte* GetPointerOrThrow(IBuffer buffer)
|
||||
{
|
||||
if (!TryGetPointer(buffer, out var ptr))
|
||||
{
|
||||
throw new NotSupportedException("WriteableBitmap.PixelBuffer does not expose IBufferByteAccess in this configuration.");
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
@@ -274,6 +274,35 @@ SRT_API srt_result_e srt_scene_texture_set_sampler(srt_scene_t* scene,
|
||||
uint32_t filter_mode /* srt_texture_filter_mode_e */);
|
||||
|
||||
/* Sky */
|
||||
typedef enum srt_sky_kind_e
|
||||
{
|
||||
SRT_SKY_NONE = 0,
|
||||
SRT_SKY_CONSTANT = 1,
|
||||
SRT_SKY_HDR = 2,
|
||||
} srt_sky_kind_e;
|
||||
|
||||
typedef struct srt_sky_desc_t
|
||||
{
|
||||
uint32_t kind; /* srt_sky_kind_e */
|
||||
uint32_t data_size; /* bytes at `data` */
|
||||
const void* data; /* points to a kind-specific POD struct */
|
||||
} srt_sky_desc_t;
|
||||
|
||||
typedef struct srt_sky_constant_desc_t
|
||||
{
|
||||
srt_vec3_t color;
|
||||
float intensity;
|
||||
} srt_sky_constant_desc_t;
|
||||
|
||||
typedef struct srt_sky_hdr_desc_t
|
||||
{
|
||||
srt_texture_handle_t hdri;
|
||||
float intensity;
|
||||
} srt_sky_hdr_desc_t;
|
||||
|
||||
/* Generic, extensible sky setter (preferred). */
|
||||
SRT_API srt_result_e srt_scene_set_sky(srt_scene_t* scene, const srt_sky_desc_t* desc);
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_none(srt_scene_t* scene);
|
||||
SRT_API srt_result_e srt_scene_set_sky_constant(srt_scene_t* scene, srt_vec3_t color, float intensity);
|
||||
SRT_API srt_result_e srt_scene_set_sky_hdr(srt_scene_t* scene, srt_texture_handle_t hdri, float intensity);
|
||||
|
||||
@@ -65,10 +65,9 @@ static inline float next_float_down(float value)
|
||||
|
||||
vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s w)
|
||||
{
|
||||
float c = glms_vec3_max(glms_vec3_abs(point)) * gamma(15);
|
||||
vec3s error = (vec3s){c, c, c};
|
||||
// float g = gamma(10);
|
||||
// vec3s error = {fabsf(point.x) * g, fabsf(point.y) * g, fabsf(point.z) * g};
|
||||
float g = gamma(3);
|
||||
vec3s error = {fabsf(point.x) * g + 1e-5f, fabsf(point.y) * g + 1e-5f, fabsf(point.z) * g + 1e-5f}; // TODO: More accurate error estimation
|
||||
|
||||
float d = glms_vec3_dot(glms_vec3_abs(normal), error);
|
||||
|
||||
vec3s offset = glms_vec3_scale(normal, d);
|
||||
@@ -530,6 +529,7 @@ hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene)
|
||||
hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
|
||||
{
|
||||
hit_result_t result = {0};
|
||||
|
||||
result.distance = FLT_MAX;
|
||||
|
||||
if (scene == NULL)
|
||||
@@ -642,5 +642,6 @@ hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
|
||||
result.model_id = UINT32_MAX;
|
||||
result.instance_id = UINT32_MAX;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -665,54 +665,101 @@ SRT_API srt_result_e srt_scene_texture_set_sampler(srt_scene_t* scene,
|
||||
|
||||
/* ---------------- Sky ---------------- */
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_none(srt_scene_t* scene)
|
||||
SRT_API srt_result_e srt_scene_set_sky(srt_scene_t* scene, const srt_sky_desc_t* desc)
|
||||
{
|
||||
if (scene == NULL)
|
||||
if (scene == NULL || desc == NULL)
|
||||
{
|
||||
return SRT_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
switch ((srt_sky_kind_e)desc->kind)
|
||||
{
|
||||
case SRT_SKY_NONE:
|
||||
{
|
||||
free_current_sky(&scene->scene.lights);
|
||||
return SRT_OK;
|
||||
}
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_constant(srt_scene_t* scene, srt_vec3_t color, float intensity)
|
||||
case SRT_SKY_CONSTANT:
|
||||
{
|
||||
if (scene == NULL)
|
||||
if (desc->data == NULL || desc->data_size != sizeof(srt_sky_constant_desc_t))
|
||||
{
|
||||
return SRT_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
const srt_sky_constant_desc_t* d = (const srt_sky_constant_desc_t*)desc->data;
|
||||
free_current_sky(&scene->scene.lights);
|
||||
|
||||
constant_sky_data_t data = {
|
||||
.color = srt_to_vec3(color),
|
||||
.intensity = intensity,
|
||||
.color = srt_to_vec3(d->color),
|
||||
.intensity = d->intensity,
|
||||
};
|
||||
scene->scene.lights.sky_light = sky_create_constant_sky(&data);
|
||||
|
||||
return SRT_OK;
|
||||
}
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_hdr(srt_scene_t* scene, srt_texture_handle_t hdri, float intensity)
|
||||
case SRT_SKY_HDR:
|
||||
{
|
||||
if (scene == NULL)
|
||||
if (desc->data == NULL || desc->data_size != sizeof(srt_sky_hdr_desc_t))
|
||||
{
|
||||
return SRT_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
texture_handle_t h = {.id = hdri.id};
|
||||
const srt_sky_hdr_desc_t* d = (const srt_sky_hdr_desc_t*)desc->data;
|
||||
texture_handle_t h = {.id = d->hdri.id};
|
||||
if (get_texture(&scene->scene.textures, h) == NULL)
|
||||
{
|
||||
return SRT_NOT_FOUND;
|
||||
}
|
||||
|
||||
free_current_sky(&scene->scene.lights);
|
||||
scene->scene.lights.sky_light = sky_create_hdr_sky(&scene->scene.textures, h, intensity);
|
||||
|
||||
scene->scene.lights.sky_light = sky_create_hdr_sky(&scene->scene.textures, h, d->intensity);
|
||||
return SRT_OK;
|
||||
}
|
||||
|
||||
default:
|
||||
return SRT_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_none(srt_scene_t* scene)
|
||||
{
|
||||
const srt_sky_desc_t d = {
|
||||
.kind = SRT_SKY_NONE,
|
||||
.data_size = 0,
|
||||
.data = NULL,
|
||||
};
|
||||
return srt_scene_set_sky(scene, &d);
|
||||
}
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_constant(srt_scene_t* scene, srt_vec3_t color, float intensity)
|
||||
{
|
||||
const srt_sky_constant_desc_t cd = {
|
||||
.color = color,
|
||||
.intensity = intensity,
|
||||
};
|
||||
const srt_sky_desc_t d = {
|
||||
.kind = SRT_SKY_CONSTANT,
|
||||
.data_size = (uint32_t)sizeof(cd),
|
||||
.data = &cd,
|
||||
};
|
||||
return srt_scene_set_sky(scene, &d);
|
||||
}
|
||||
|
||||
SRT_API srt_result_e srt_scene_set_sky_hdr(srt_scene_t* scene, srt_texture_handle_t hdri, float intensity)
|
||||
{
|
||||
const srt_sky_hdr_desc_t hd = {
|
||||
.hdri = hdri,
|
||||
.intensity = intensity,
|
||||
};
|
||||
const srt_sky_desc_t d = {
|
||||
.kind = SRT_SKY_HDR,
|
||||
.data_size = (uint32_t)sizeof(hd),
|
||||
.data = &hd,
|
||||
};
|
||||
return srt_scene_set_sky(scene, &d);
|
||||
}
|
||||
|
||||
/* ---------------- Mesh loading (Assimp) ---------------- */
|
||||
|
||||
SRT_API srt_result_e srt_scene_load_mesh(srt_scene_t* scene, const char* filename_utf8, srt_mesh_handle_t* out_mesh)
|
||||
|
||||
@@ -382,16 +382,19 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
return output;
|
||||
}
|
||||
|
||||
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi, 0.0f, 0.0f);
|
||||
hit_result_t shadow_hit = {0};
|
||||
vec3s origin = offset_ray_origin(context->position, context->normal, context->wo);
|
||||
ray_t shadow_ray = ray_create(origin, wi, 0.0f, context->spread_angle);
|
||||
hit_result_t shadow_hit;
|
||||
if (context->scene != NULL)
|
||||
{
|
||||
shadow_hit = ray_intersect_scene_any(&shadow_ray, context->scene);
|
||||
}
|
||||
else
|
||||
{
|
||||
shadow_hit = (hit_result_t){0};
|
||||
ray_intersect_bvh_any(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &shadow_hit);
|
||||
}
|
||||
|
||||
if (shadow_hit.hit)
|
||||
{
|
||||
return output;
|
||||
|
||||
@@ -206,7 +206,7 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
|
||||
rendering_config_t config = {
|
||||
.width = 1920 / 2,
|
||||
.height = 1080 / 2,
|
||||
.sample_count = 16 * 4,
|
||||
.sample_count = 16 * 1,
|
||||
.max_depth = 4,
|
||||
.bucket_size = 64,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user