--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+internal class Camera
+{
+ public Vector Pos;
+ public Vector Forward;
+ public Vector Up;
+ public Vector Right;
+
+ public Camera(Vector pos, Vector forward, Vector up, Vector right) { Pos = pos; Forward = forward; Up = up; Right = right; }
+
+ public static Camera Create(Vector pos, Vector lookAt)
+ {
+ Vector forward = Vector.Norm(Vector.Minus(lookAt, pos));
+ Vector down = new Vector(0, -1, 0);
+ Vector right = Vector.Times(1.5F, Vector.Norm(Vector.Cross(forward, down)));
+ Vector up = Vector.Times(1.5F, Vector.Norm(Vector.Cross(forward, right)));
+
+ return new Camera(pos, forward, up, right);
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Numerics;
+
+public struct Color
+{
+ private Vector3 _simdVector;
+ public float R { get { return _simdVector.X; } }
+ public float G { get { return _simdVector.Y; } }
+ public float B { get { return _simdVector.Z; } }
+
+ public Color(double r, double g, double b)
+ {
+ _simdVector = new Vector3((float)r, (float)g, (float)b);
+ }
+ public Color(string str)
+ {
+ string[] nums = str.Split(',');
+ if (nums.Length != 3) throw new ArgumentException();
+ _simdVector = new Vector3(float.Parse(nums[0]), float.Parse(nums[1]), float.Parse(nums[2]));
+ }
+
+
+ public static Color Times(double n, Color v)
+ {
+ Color result;
+ result._simdVector = (float)n * v._simdVector;
+ return result;
+ }
+ public static Color Times(Color v1, Color v2)
+ {
+ Color result;
+ result._simdVector = v1._simdVector * v2._simdVector;
+ return result;
+ }
+
+ public static Color Plus(Color v1, Color v2)
+ {
+ Color result;
+ result._simdVector = v1._simdVector + v2._simdVector;
+ return result;
+ }
+ public static Color Minus(Color v1, Color v2)
+ {
+ Color result;
+ result._simdVector = v1._simdVector - v2._simdVector;
+ return result;
+ }
+
+ public static Color Background { get { Color result; result._simdVector = Vector3.Zero; return result; } }
+ public static Color DefaultColor { get { Color result; result._simdVector = Vector3.Zero; return result; } }
+
+ public static float Legalize(float d)
+ {
+ return d > 1 ? 1 : d;
+ }
+
+ public static byte ToByte(float c)
+ {
+ return (byte)(255 * Legalize(c));
+ }
+
+ public static Int32 ToInt32(float c)
+ {
+ Int32 r = (Int32)(255 * c);
+ return (r > 255 ? 255 : r);
+ }
+
+ public Int32 ToInt32()
+ {
+ return (ToInt32(B) | ToInt32(G) << 8 | ToInt32(R) << 16 | 255 << 24);
+ }
+
+ public float Brightness()
+ {
+ float r = (float)R / 255.0f;
+ float g = (float)G / 255.0f;
+ float b = (float)B / 255.0f;
+
+ float max, min;
+
+ max = r; min = r;
+
+ if (g > max) max = g;
+ if (b > max) max = b;
+
+ if (g < min) min = g;
+ if (b < min) min = b;
+
+ return (max + min) / 2;
+ }
+
+ public void ChangeHue(float hue)
+ {
+ float H, S, L, Br;
+
+ Br = Brightness();
+ H = hue;
+ S = 0.9F;
+ L = ((Br - 0.5F) * 0.5F) + 0.5F;
+
+ if (L == 0)
+ {
+ _simdVector = Vector3.Zero;
+ }
+ else
+ {
+ if (S == 0)
+ {
+ _simdVector = new Vector3(L);
+ }
+ else
+ {
+ float temp2 = ((L <= 0.5F) ? L * (1.0F + S) : L + S - (L * S));
+ float temp1 = 2.0F * L - temp2;
+
+ float[] t3 = new float[] { H + 1.0F / 3.0F, H, H - 1.0F / 3.0F };
+ float[] clr = new float[] { 0, 0, 0 };
+
+ for (int i = 0; i < 3; i++)
+ {
+ if (t3[i] < 0) t3[i] += 1.0F;
+ if (t3[i] > 1) t3[i] -= 1.0F;
+ if (6.0 * t3[i] < 1.0)
+ clr[i] = temp1 + (temp2 - temp1) * t3[i] * 6.0F;
+ else if (2.0 * t3[i] < 1.0)
+ clr[i] = temp2;
+ else if (3.0 * t3[i] < 2.0)
+ clr[i] = (temp1 + (temp2 - temp1) * ((2.0F / 3.0F) - t3[i]) * 6.0F);
+ else
+ clr[i] = temp1;
+ }
+
+ _simdVector = new Vector3(clr[0], clr[1], clr[2]);
+ }
+ }
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+internal class ISect
+{
+ public SceneObject Thing;
+ public Ray Ray;
+ public double Dist;
+
+ public ISect(SceneObject thing, Ray ray, double dist) { Thing = thing; Ray = ray; Dist = dist; }
+
+ public static bool IsNull(ISect sect) { return sect == null; }
+ public readonly static ISect Null = null;
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+internal class Light
+{
+ public Vector Pos;
+ public Color Color;
+
+ public Light(Vector pos, Color color) { Pos = pos; Color = color; }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Collections.Concurrent
+{
+ /// <summary>Provides a thread-safe object pool.</summary>
+ /// <typeparam name="T">Specifies the type of the elements stored in the pool.</typeparam>
+ [DebuggerDisplay("Count={Count}")]
+ [DebuggerTypeProxy(typeof(IProducerConsumerCollection_DebugView<>))]
+ public sealed class ObjectPool<T> : ProducerConsumerCollectionBase<T>
+ {
+ private readonly Func<T> _generator;
+
+ /// <summary>Initializes an instance of the ObjectPool class.</summary>
+ /// <param name="generator">The function used to create items when no items exist in the pool.</param>
+ public ObjectPool(Func<T> generator) : this(generator, new ConcurrentQueue<T>()) { }
+
+ /// <summary>Initializes an instance of the ObjectPool class.</summary>
+ /// <param name="generator">The function used to create items when no items exist in the pool.</param>
+ /// <param name="collection">The collection used to store the elements of the pool.</param>
+ public ObjectPool(Func<T> generator, IProducerConsumerCollection<T> collection)
+ : base(collection)
+ {
+ if (generator == null) throw new ArgumentNullException("generator");
+ _generator = generator;
+ }
+
+ /// <summary>Adds the provided item into the pool.</summary>
+ /// <param name="item">The item to be added.</param>
+ public void PutObject(T item) { base.TryAdd(item); }
+
+ /// <summary>Gets an item from the pool.</summary>
+ /// <returns>The removed or created item.</returns>
+ /// <remarks>If the pool is empty, a new item will be created and returned.</remarks>
+ public T GetObject()
+ {
+ T value;
+ return base.TryTake(out value) ? value : _generator();
+ }
+
+ /// <summary>Clears the object pool, returning all of the data that was in the pool.</summary>
+ /// <returns>An array containing all of the elements in the pool.</returns>
+ public T[] ToArrayAndClear()
+ {
+ var items = new List<T>();
+ T value;
+ while (base.TryTake(out value)) items.Add(value);
+ return items.ToArray();
+ }
+
+ protected override bool TryAdd(T item)
+ {
+ PutObject(item);
+ return true;
+ }
+
+ protected override bool TryTake(out T item)
+ {
+ item = GetObject();
+ return true;
+ }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+internal class Plane : SceneObject
+{
+ public Vector Norm;
+ public double Offset;
+
+ public Plane(Vector norm, double offset, Surface surface) : base(surface) { Norm = norm; Offset = offset; }
+
+ public override ISect Intersect(Ray ray)
+ {
+ double denom = Vector.Dot(Norm, ray.Dir);
+ if (denom > 0) return ISect.Null;
+ return new ISect(this, ray, (Vector.Dot(Norm, ray.Start) + Offset) / (-denom));
+ }
+
+ public override Vector Normal(Vector pos)
+ {
+ return Norm;
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace System.Collections.Concurrent
+{
+ /// <summary>Debug view for the IProducerConsumerCollection.</summary>
+ /// <typeparam name="T">Specifies the type of the data being aggregated.</typeparam>
+ internal sealed class IProducerConsumerCollection_DebugView<T>
+ {
+ private IProducerConsumerCollection<T> _collection;
+
+ public IProducerConsumerCollection_DebugView(IProducerConsumerCollection<T> collection)
+ {
+ _collection = collection;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Values { get { return _collection.ToArray(); } }
+ }
+
+ /// <summary>
+ /// Provides a base implementation for producer-consumer collections that wrap other
+ /// producer-consumer collections.
+ /// </summary>
+ /// <typeparam name="T">Specifies the type of elements in the collection.</typeparam>
+ public abstract class ProducerConsumerCollectionBase<T> : IProducerConsumerCollection<T>
+ {
+ private readonly IProducerConsumerCollection<T> _contained;
+
+ /// <summary>Initializes the ProducerConsumerCollectionBase instance.</summary>
+ /// <param name="contained">The collection to be wrapped by this instance.</param>
+ protected ProducerConsumerCollectionBase(IProducerConsumerCollection<T> contained)
+ {
+ if (contained == null) throw new ArgumentNullException("contained");
+ _contained = contained;
+ }
+
+ /// <summary>Gets the contained collection.</summary>
+ protected IProducerConsumerCollection<T> ContainedCollection { get { return _contained; } }
+
+ /// <summary>Attempts to add the specified value to the end of the deque.</summary>
+ /// <param name="item">The item to add.</param>
+ /// <returns>true if the item could be added; otherwise, false.</returns>
+ protected virtual bool TryAdd(T item) { return _contained.TryAdd(item); }
+
+ /// <summary>Attempts to remove and return an item from the collection.</summary>
+ /// <param name="item">
+ /// When this method returns, if the operation was successful, item contains the item removed. If
+ /// no item was available to be removed, the value is unspecified.
+ /// </param>
+ /// <returns>
+ /// true if an element was removed and returned from the collection; otherwise, false.
+ /// </returns>
+ protected virtual bool TryTake(out T item) { return _contained.TryTake(out item); }
+
+ /// <summary>Attempts to add the specified value to the end of the deque.</summary>
+ /// <param name="item">The item to add.</param>
+ /// <returns>true if the item could be added; otherwise, false.</returns>
+ bool IProducerConsumerCollection<T>.TryAdd(T item) { return TryAdd(item); }
+
+ /// <summary>Attempts to remove and return an item from the collection.</summary>
+ /// <param name="item">
+ /// When this method returns, if the operation was successful, item contains the item removed. If
+ /// no item was available to be removed, the value is unspecified.
+ /// </param>
+ /// <returns>
+ /// true if an element was removed and returned from the collection; otherwise, false.
+ /// </returns>
+ bool IProducerConsumerCollection<T>.TryTake(out T item) { return TryTake(out item); }
+
+ /// <summary>Gets the number of elements contained in the collection.</summary>
+ public int Count { get { return _contained.Count; } }
+
+ /// <summary>Creates an array containing the contents of the collection.</summary>
+ /// <returns>The array.</returns>
+ public T[] ToArray() { return _contained.ToArray(); }
+
+ /// <summary>Copies the contents of the collection to an array.</summary>
+ /// <param name="array">The array to which the data should be copied.</param>
+ /// <param name="index">The starting index at which data should be copied.</param>
+ public void CopyTo(T[] array, int index) { _contained.CopyTo(array, index); }
+
+ /// <summary>Copies the contents of the collection to an array.</summary>
+ /// <param name="array">The array to which the data should be copied.</param>
+ /// <param name="index">The starting index at which data should be copied.</param>
+ void ICollection.CopyTo(Array array, int index) { _contained.CopyTo(array, index); }
+
+ /// <summary>Gets an enumerator for the collection.</summary>
+ /// <returns>An enumerator.</returns>
+ public IEnumerator<T> GetEnumerator() { return _contained.GetEnumerator(); }
+
+ /// <summary>Gets an enumerator for the collection.</summary>
+ /// <returns>An enumerator.</returns>
+ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
+
+ /// <summary>Gets whether the collection is synchronized.</summary>
+ bool ICollection.IsSynchronized { get { return _contained.IsSynchronized; } }
+
+ /// <summary>Gets the synchronization root object for the collection.</summary>
+ object ICollection.SyncRoot { get { return _contained.SyncRoot; } }
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+internal struct Ray
+{
+ public Vector Start;
+ public Vector Dir;
+
+ public Ray(Vector start, Vector dir) { Start = start; Dir = dir; }
+}
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages</ReferencePath>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="$(JitPackagesConfigFileDirectory)benchmark\project.json" />
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="RayTracerBench.cs" />
+ <Compile Include="Camera.cs" />
+ <Compile Include="Color.cs" />
+ <Compile Include="ISect.cs" />
+ <Compile Include="Light.cs" />
+ <Compile Include="ObjectPool.cs" />
+ <Compile Include="Plane.cs" />
+ <Compile Include="ProducerConsumerCollectionBase.cs" />
+ <Compile Include="Ray.cs" />
+ <Compile Include="Raytracer.cs" />
+ <Compile Include="Scene.cs" />
+ <Compile Include="SceneObject.cs" />
+ <Compile Include="Sphere.cs" />
+ <Compile Include="Surface.cs" />
+ <Compile Include="Surfaces.cs" />
+ <Compile Include="Vector.cs" />
+ </ItemGroup>
+ <PropertyGroup>
+ <ProjectJson>$(JitPackagesConfigFileDirectory)benchmark\project.json</ProjectJson>
+ <ProjectLockJson>$(JitPackagesConfigFileDirectory)benchmark\project.lock.json</ProjectLockJson>
+ </PropertyGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
+ </PropertyGroup>
+</Project>
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+// Based on the Raytracer example from
+// Samples for Parallel Programming with the .NET Framework
+// https://code.msdn.microsoft.com/windowsdesktop/Samples-for-Parallel-b4b76364
+
+using Microsoft.Xunit.Performance;
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Concurrent;
+
+[assembly: OptimizeForBenchmarks]
+[assembly: MeasureInstructionsRetired]
+
+public class RayTracerBench
+{
+#if DEBUG
+
+ private const int RunningTime = 200;
+ private const int Width = 100;
+ private const int Height = 100;
+ private const int Iterations = 1;
+ private const int MaxIterations = 1000;
+
+#else
+
+ private const int RunningTime = 1000;
+ private const int Width = 250;
+ private const int Height = 250;
+ private const int Iterations = 7;
+ private const int MaxIterations = 1000;
+
+#endif
+
+ public RayTracerBench()
+ {
+ _width = Width;
+ _height = Height;
+ _parallel = false;
+ _showThreads = false;
+ _freeBuffers = new ObjectPool<int[]>(() => new int[_width * _height]);
+ }
+
+ private double _framesPerSecond;
+ private bool _parallel;
+ private bool _showThreads;
+ private int _width, _height;
+ private int _degreeOfParallelism = Environment.ProcessorCount;
+ private int _frames;
+ private CancellationTokenSource _cancellation;
+ private ObjectPool<int[]> _freeBuffers;
+
+ private void RenderTest()
+ {
+ _cancellation = new CancellationTokenSource(RunningTime);
+ RenderLoop(MaxIterations);
+ }
+
+ private void RenderBench()
+ {
+ _cancellation = new CancellationTokenSource();
+ RenderLoop(Iterations);
+ }
+
+ private void RenderLoop(int iterations)
+ {
+ // Create a ray tracer, and create a reference to "sphere2" that we are going to bounce
+ var rayTracer = new RayTracer(_width, _height);
+ var scene = rayTracer.DefaultScene;
+ var sphere2 = (Sphere)scene.Things[0]; // The first item is assumed to be our sphere
+ var baseY = sphere2.Radius;
+ sphere2.Center.Y = sphere2.Radius;
+
+ // Timing determines how fast the ball bounces as well as diagnostics frames/second info
+ var renderingTime = new Stopwatch();
+ var totalTime = Stopwatch.StartNew();
+
+ // Keep rendering until the iteration count is hit
+ for (_frames = 0; _frames < iterations; _frames++)
+ {
+ // Or the rendering task has been canceled
+ if (_cancellation.IsCancellationRequested)
+ {
+ break;
+ }
+
+ // Get the next buffer
+ var rgb = _freeBuffers.GetObject();
+
+ // Determine the new position of the sphere based on the current time elapsed
+ double dy2 = 0.8 * Math.Abs(Math.Sin(totalTime.ElapsedMilliseconds * Math.PI / 3000));
+ sphere2.Center.Y = (float)(baseY + dy2);
+
+ // Render the scene
+ renderingTime.Reset();
+ renderingTime.Start();
+ ParallelOptions options = new ParallelOptions
+ {
+ MaxDegreeOfParallelism = _degreeOfParallelism,
+ CancellationToken = _cancellation.Token
+ };
+ if (!_parallel) rayTracer.RenderSequential(scene, rgb);
+ else if (_showThreads) rayTracer.RenderParallelShowingThreads(scene, rgb, options);
+ else rayTracer.RenderParallel(scene, rgb, options);
+ renderingTime.Stop();
+
+ _framesPerSecond = (1000.0 / renderingTime.ElapsedMilliseconds);
+ _freeBuffers.PutObject(rgb);
+ }
+ }
+
+ [Benchmark]
+ public static void Bench()
+ {
+ var m = new RayTracerBench();
+ foreach (var iteration in Benchmark.Iterations)
+ {
+ using (iteration.StartMeasurement())
+ {
+ m.RenderBench();
+ }
+ }
+ }
+
+ public bool Run()
+ {
+ RenderTest();
+ Console.WriteLine("{0} frames, {1} frames/sec",
+ _frames,
+ _framesPerSecond.ToString("F2"));
+ return true;
+ }
+
+ public static int Main(string[] args)
+ {
+ var r = new RayTracerBench();
+ bool result = r.Run();
+ return (result ? 100 : -1);
+ }
+}
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Numerics;
+
+internal sealed class RayTracer
+{
+ private int _screenWidth;
+ private int _screenHeight;
+ private const int MaxDepth = 5;
+
+ public RayTracer(int screenWidth, int screenHeight)
+ {
+ _screenWidth = screenWidth;
+ _screenHeight = screenHeight;
+ }
+
+ internal void RenderSequential(Scene scene, Int32[] rgb)
+ {
+ for (int y = 0; y < _screenHeight; y++)
+ {
+ int stride = y * _screenWidth;
+ Camera camera = scene.Camera;
+ for (int x = 0; x < _screenWidth; x++)
+ {
+ Color color = TraceRay(new Ray(camera.Pos, GetPoint(x, y, camera)), scene, 0);
+ rgb[x + stride] = color.ToInt32();
+ }
+ }
+ }
+
+ internal void RenderParallel(Scene scene, Int32[] rgb, ParallelOptions options)
+ {
+ Parallel.For(0, _screenHeight, options, y =>
+ {
+ int stride = y * _screenWidth;
+ Camera camera = scene.Camera;
+ for (int x = 0; x < _screenWidth; x++)
+ {
+ Color color = TraceRay(new Ray(camera.Pos, GetPoint(x, y, camera)), scene, 0);
+ rgb[x + stride] = color.ToInt32();
+ }
+ });
+ }
+
+ internal void RenderParallelShowingThreads(Scene scene, Int32[] rgb, ParallelOptions options)
+ {
+ int id = 0;
+ Parallel.For<float>(0, _screenHeight, options, () => GetHueShift(Interlocked.Increment(ref id)), (y, state, hue) =>
+ {
+ int stride = y * _screenWidth;
+ Camera camera = scene.Camera;
+ for (int x = 0; x < _screenWidth; x++)
+ {
+ Color color = TraceRay(new Ray(camera.Pos, GetPoint(x, y, camera)), scene, 0);
+ color.ChangeHue(hue);
+ rgb[x + stride] = color.ToInt32();
+ }
+ return hue;
+ },
+ hue => Interlocked.Decrement(ref id));
+ }
+
+ private Dictionary<int, float> _numToHueShiftLookup = new Dictionary<int, float>();
+ private Random _rand = new Random();
+
+ private float GetHueShift(int id)
+ {
+ float shift;
+ lock (_numToHueShiftLookup)
+ {
+ if (!_numToHueShiftLookup.TryGetValue(id, out shift))
+ {
+ shift = (float)_rand.NextDouble();
+ _numToHueShiftLookup.Add(id, shift);
+ }
+ }
+ return shift;
+ }
+
+ internal readonly Scene DefaultScene = CreateDefaultScene();
+
+ private static Scene CreateDefaultScene()
+ {
+ SceneObject[] things = {
+ new Sphere( new Vector(-0.5,1,1.5), 0.5, Surfaces.MatteShiny),
+ new Sphere( new Vector(0,1,-0.25), 1, Surfaces.Shiny),
+ new Plane( new Vector(0,1,0), 0, Surfaces.CheckerBoard)
+ };
+ Light[] lights = {
+ new Light(new Vector(-2,2.5,0),new Color(.5,.45,.41)),
+ new Light(new Vector(2,4.5,2), new Color(.99,.95,.8))
+ };
+ Camera camera = Camera.Create(new Vector(2.75, 2, 3.75), new Vector(-0.6, .5, 0));
+
+ return new Scene(things, lights, camera);
+ }
+
+
+ private ISect MinIntersection(Ray ray, Scene scene)
+ {
+ ISect min = ISect.Null;
+ foreach (SceneObject obj in scene.Things)
+ {
+ ISect isect = obj.Intersect(ray);
+ if (!ISect.IsNull(isect))
+ {
+ if (ISect.IsNull(min) || min.Dist > isect.Dist)
+ {
+ min = isect;
+ }
+ }
+ }
+ return min;
+ }
+
+ private double TestRay(Ray ray, Scene scene)
+ {
+ ISect isect = MinIntersection(ray, scene);
+ if (ISect.IsNull(isect))
+ return 0;
+ return isect.Dist;
+ }
+
+ private Color TraceRay(Ray ray, Scene scene, int depth)
+ {
+ ISect isect = MinIntersection(ray, scene);
+ if (ISect.IsNull(isect))
+ return Color.Background;
+ return Shade(isect, scene, depth);
+ }
+
+ private Color GetNaturalColor(SceneObject thing, Vector pos, Vector norm, Vector rd, Scene scene)
+ {
+ Color ret = new Color(0, 0, 0);
+ foreach (Light light in scene.Lights)
+ {
+ Vector ldis = Vector.Minus(light.Pos, pos);
+ Vector livec = Vector.Norm(ldis);
+ double neatIsect = TestRay(new Ray(pos, livec), scene);
+ bool isInShadow = !((neatIsect > Vector.Mag(ldis)) || (neatIsect == 0));
+ if (!isInShadow)
+ {
+ float illum = Vector.Dot(livec, norm);
+ Color lcolor = illum > 0 ? Color.Times(illum, light.Color) : new Color(0, 0, 0);
+ float specular = Vector.Dot(livec, Vector.Norm(rd));
+ Color scolor = specular > 0 ? Color.Times(Math.Pow(specular, thing.Surface.Roughness), light.Color) : new Color(0, 0, 0);
+ ret = Color.Plus(ret, Color.Plus(Color.Times(thing.Surface.Diffuse(pos), lcolor),
+ Color.Times(thing.Surface.Specular(pos), scolor)));
+ }
+ }
+ return ret;
+ }
+
+ private Color GetReflectionColor(SceneObject thing, Vector pos, Vector norm, Vector rd, Scene scene, int depth)
+ {
+ return Color.Times(thing.Surface.Reflect(pos), TraceRay(new Ray(pos, rd), scene, depth + 1));
+ }
+
+ private Color Shade(ISect isect, Scene scene, int depth)
+ {
+ Vector d = isect.Ray.Dir;
+ Vector pos = Vector.Plus(Vector.Times(isect.Dist, isect.Ray.Dir), isect.Ray.Start);
+ Vector normal = isect.Thing.Normal(pos);
+ Vector reflectDir = Vector.Minus(d, Vector.Times(2 * Vector.Dot(normal, d), normal));
+ Color ret = Color.DefaultColor;
+ ret = Color.Plus(ret, GetNaturalColor(isect.Thing, pos, normal, reflectDir, scene));
+ if (depth >= MaxDepth)
+ {
+ return Color.Plus(ret, new Color(.5, .5, .5));
+ }
+ return Color.Plus(ret, GetReflectionColor(isect.Thing, Vector.Plus(pos, Vector.Times(.001, reflectDir)), normal, reflectDir, scene, depth));
+ }
+
+ private double RecenterX(double x)
+ {
+ return (x - (_screenWidth / 2.0)) / (2.0 * _screenWidth);
+ }
+ private double RecenterY(double y)
+ {
+ return -(y - (_screenHeight / 2.0)) / (2.0 * _screenHeight);
+ }
+
+ private Vector GetPoint(double x, double y, Camera camera)
+ {
+ return Vector.Norm(Vector.Plus(camera.Forward, Vector.Plus(Vector.Times(RecenterX(x), camera.Right),
+ Vector.Times(RecenterY(y), camera.Up))));
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Collections.Generic;
+
+internal class Scene
+{
+ public SceneObject[] Things;
+ public Light[] Lights;
+ public Camera Camera;
+
+ public Scene(SceneObject[] things, Light[] lights, Camera camera) { Things = things; Lights = lights; Camera = camera; }
+
+ public IEnumerable<ISect> Intersect(Ray r)
+ {
+ foreach (SceneObject obj in Things)
+ {
+ yield return obj.Intersect(r);
+ }
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+internal abstract class SceneObject
+{
+ public Surface Surface;
+ public abstract ISect Intersect(Ray ray);
+ public abstract Vector Normal(Vector pos);
+
+ public SceneObject(Surface surface) { Surface = surface; }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+
+internal class Sphere : SceneObject
+{
+ public Vector Center;
+ public float Radius;
+
+ public Sphere(Vector center, double radius, Surface surface) : base(surface) { Center = center; Radius = (float)radius; }
+
+ public override ISect Intersect(Ray ray)
+ {
+ Vector eo = Vector.Minus(Center, ray.Start);
+ float v = Vector.Dot(eo, ray.Dir);
+ float dist;
+ if (v < 0)
+ {
+ dist = 0;
+ }
+ else
+ {
+ double disc = Math.Pow(Radius, 2) - (Vector.Dot(eo, eo) - Math.Pow(v, 2));
+ dist = disc < 0 ? 0 : v - (float)Math.Sqrt(disc);
+ }
+ if (dist == 0) return ISect.Null;
+ return new ISect(this, ray, dist);
+ }
+
+ public override Vector Normal(Vector pos)
+ {
+ return Vector.Norm(Vector.Minus(pos, Center));
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+
+internal class Surface
+{
+ public Func<Vector, Color> Diffuse;
+ public Func<Vector, Color> Specular;
+ public Func<Vector, double> Reflect;
+ public double Roughness;
+
+ public Surface(Func<Vector, Color> Diffuse,
+ Func<Vector, Color> Specular,
+ Func<Vector, double> Reflect,
+ double Roughness)
+ {
+ this.Diffuse = Diffuse;
+ this.Specular = Specular;
+ this.Reflect = Reflect;
+ this.Roughness = Roughness;
+ }
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+
+internal static class Surfaces
+{
+ // Only works with X-Z plane.
+ public static readonly Surface CheckerBoard =
+ new Surface(
+ delegate (Vector pos)
+ {
+ return ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0)
+ ? new Color(1, 1, 1)
+ : new Color(0.02, 0.0, 0.14);
+ },
+ delegate (Vector pos) { return new Color(1, 1, 1); },
+ delegate (Vector pos)
+ {
+ return ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0)
+ ? .1
+ : .5;
+ },
+ 150);
+
+
+
+ public static readonly Surface Shiny =
+ new Surface(
+ delegate (Vector pos) { return new Color(1, 1, 1); },
+ delegate (Vector pos) { return new Color(.5, .5, .5); },
+ delegate (Vector pos) { return .7; },
+ 250);
+
+ public static readonly Surface MatteShiny =
+ new Surface(
+ delegate (Vector pos) { return new Color(1, 1, 1); },
+ delegate (Vector pos) { return new Color(.25, .25, .25); },
+ delegate (Vector pos) { return .7; },
+ 250);
+}
+
--- /dev/null
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+internal struct Vector
+{
+ private Vector3 _simdVector;
+ public float X { get { return _simdVector.X; } }
+ public float Y { get { return _simdVector.Y; } set { _simdVector = new Vector3(_simdVector.X, value, _simdVector.Z); } }
+ public float Z { get { return _simdVector.Z; } }
+
+ public Vector(double x, double y, double z)
+ {
+ _simdVector = new Vector3((float)x, (float)y, (float)z);
+ }
+ public Vector(string str)
+ {
+ string[] nums = str.Split(',');
+ if (nums.Length != 3) throw new ArgumentException();
+ _simdVector = new Vector3(float.Parse(nums[0]), float.Parse(nums[1]), float.Parse(nums[2]));
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector Times(double n, Vector v)
+ {
+ Vector result;
+ result._simdVector = (float)n * v._simdVector;
+ return result;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector Minus(Vector v1, Vector v2)
+ {
+ Vector result;
+ result._simdVector = v1._simdVector - v2._simdVector;
+ return result;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector Plus(Vector v1, Vector v2)
+ {
+ Vector result;
+ result._simdVector = v1._simdVector + v2._simdVector;
+ return result;
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static float Dot(Vector v1, Vector v2)
+ {
+ return Vector3.Dot(v1._simdVector, v2._simdVector);
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static float Mag(Vector v) { return (float)Math.Sqrt(Dot(v, v)); }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector Norm(Vector v)
+ {
+ float mag = Mag(v);
+ float div = mag == 0 ? float.PositiveInfinity : 1 / mag;
+ return Times(div, v);
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static Vector Cross(Vector v1, Vector v2)
+ {
+ return new Vector(((v1.Y * v2.Z) - (v1.Z * v2.Y)),
+ ((v1.Z * v2.X) - (v1.X * v2.Z)),
+ ((v1.X * v2.Y) - (v1.Y * v2.X)));
+ }
+ [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
+ public static bool Equals(Vector v1, Vector v2)
+ {
+ return v1._simdVector.Equals(v2._simdVector);
+ }
+
+ public static Vector Null { get { Vector result; result._simdVector = Vector3.Zero; return result; } }
+}
+