dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.97k stars 4.66k forks source link

[API Proposal]: Adding and subtracting floats with vectors #79960

Closed PavielKraskouski closed 1 year ago

PavielKraskouski commented 1 year ago

Background and motivation

Multiplication and division operators involving floats and vectors are currently available. At the same time, only two vectors can be added and subtracted. For example, in my software renderer, there are situations where you need to add or subtract a float from a vector. I propose to add methods for adding and subtracting floats with vectors.

API Proposal

namespace System.Numerics;

public struct Vector2
{
    public static Vector2 operator +(Vector2 left, float right)
    {
        return left + new Vector2(right);
    }

    public static Vector2 operator +(float left, Vector2 right)
    {
        return right + left;
    }

    public static Vector2 operator -(Vector2 left, float right)
    {
        return left - new Vector2(right);
    }
}

public struct Vector3
{
    public static Vector3 operator +(Vector3 left, float right)
    {
        return left + new Vector3(right);
    }

    public static Vector3 operator +(float left, Vector3 right)
    {
        return right + left;
    }

    public static Vector3 operator -(Vector3 left, float right)
    {
        return left - new Vector3(right);
    }
}

public struct Vector4
{
    public static Vector4 operator +(Vector4 left, float right)
    {
        return left + new Vector4(right);
    }

    public static Vector4 operator +(float left, Vector4 right)
    {
        return right + left;
    }

    public static Vector4 operator -(Vector4 left, float right)
    {
        return left - new Vector4(right);
    }
}

API Usage

float f = 1.234f;
Vector2 v2 = new Vector2(1, 2);
Vector3 v3 = new Vector3(1, 2, 3);
Vector4 v4 = new Vector4(1, 2, 3, 4);

v2 = v2 + f;
v3 = v3 - f;
v4 = f + v4;

Alternative Designs

No response

Risks

No response

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.

Issue Details
### Background and motivation Multiplication and division operators involving floats and vectors are currently available. At the same time, only two vectors can be added and subtracted. For example, in my software renderer, there are situations where you need to add or subtract a float from a vector. I propose to add methods for adding and subtracting floats with vectors. ### API Proposal ```csharp namespace System.Numerics; public struct Vector2 { public static Vector2 operator +(Vector2 left, float right) { return left + new Vector2(right); } public static Vector2 operator +(float left, Vector2 right) { return right + left; } public static Vector2 operator -(Vector2 left, float right) { return left - new Vector2(right); } } public struct Vector3 { public static Vector3 operator +(Vector3 left, float right) { return left + new Vector3(right); } public static Vector3 operator +(float left, Vector3 right) { return right + left; } public static Vector3 operator -(Vector3 left, float right) { return left - new Vector3(right); } } public struct Vector4 { public static Vector4 operator +(Vector4 left, float right) { return left + new Vector4(right); } public static Vector4 operator +(float left, Vector4 right) { return right + left; } public static Vector4 operator -(Vector4 left, float right) { return left - new Vector4(right); } } ``` ### API Usage ```csharp float f = 1.234f; Vector2 v2 = new Vector2(1, 2); Vector3 v3 = new Vector3(1, 2, 3); Vector4 v4 = new Vector4(1, 2, 3, 4); v2 = v2 + f; v3 = v3 - f; v4 = f + v4; ``` ### Alternative Designs _No response_ ### Risks _No response_
Author: PavielKraskouski
Assignees: -
Labels: `api-suggestion`, `area-System.Numerics`
Milestone: -
danmoseley commented 1 year ago

Is this also relevant to Vector<T>

tannergooding commented 1 year ago

This isn't a standard operation exposed by other libraries with vector support. It likewise doesn't really "fit" from a mathematical perspective and wouldn't provide any benefits performance wise nor would it do anything like reduce allocations. This is really just saving the user from typing a few characters and at the cost of making the code harder to read/understand.

Noting this is different from multiplication where multiplying by a scalar is a well-defined operation. It is likewise different from multiplication by a matrix where the Vector can be viewed as a 4x1 or a 1x4 matrix and thus can be multiplied by or multiply a 4x4 matrix (depending on if the input is interpreted as row or column major, etc).

PavielKraskouski commented 1 year ago

But then why does HLSL have the float3 type and GLSL have the vec3 type, which allow these operations? I needed to adapt this HLSL code: https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl Let's take a look at this function:

float3 RRTAndODTFit(float3 v)
{
    float3 a = v * (v + 0.0245786f) - 0.000090537f;
    float3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
    return a / b;
}

The same in C# would look like this:

Vector3 RRTAndODTFit(Vector3 v)
{
    Vector3 a = v * (v + new Vector3(0.0245786f)) - new Vector3(0.000090537f);
    Vector3 b = v * (0.983729f * v + new Vector3(0.4329510f)) + new Vector3(0.238081f);
    return a / b;
}

Or like this:

Vector3 RRTAndODTFit(Vector3 v)
{
    Vector3 a = v * (v + 0.0245786f * Vector3.One) - 0.000090537f * Vector3.One;
    Vector3 b = v * (0.983729f * v + 0.4329510f * Vector3.One) + 0.238081f * Vector3.One;
    return a / b;
}

In my opinion, the HLSL code looks more elegant and simple, while the C# code looks bloated.

tannergooding commented 1 year ago

But then why does HLSL have the float3 type and GLSL have the vec3 type, which allow these operations?

Those are shader languages which are designed around GPU computation and which may be able to accelerate or optimize such operations. This is possible because GPUs are often SIMT rather than SIMD.

It is not typical for the corresponding CPU side libraries to provide such operations. Some examples of widely popular libraries that do not provide such operations includes Unity, Unreal, XNA, DirectX Math, and many other vector libraries. GLM is possibly the one notable exception, but it specifically attempts to be a CPU side mirror of GLSL and doesn't strictly take into account other design points or considerations.

In my opinion, the HLSL code looks more elegant and simple, while the C# code looks bloated.

It is ultimately a matter of opinion. To me, the C# code makes the actual steps necessary for computation explicit and clear. I've found this is important for perf critical code which is a typical use case for the vector types.

The type of CPU you'll typically find is SIMD and none of the current major hardware vendors provide any instructions for accelerating vector addition or subtraction by a scalar. Because of this, the real world impact is that v + scalar would have to compile down to some form of "broadcast". In the case scalar is a constant, this can be an 8/12/16-byte constant loaded from memory. In the case it isn't a constant, it will be an actual broadcast/duplicate/splat instruction.

This is unlike for multiplication or division where they are actual operations that are well-defined for euclidean vectors and where hardware often provides acceleration for them. Arm64 for example has both MultiplyByScalar and MultiplyBySelectedScalar instructions available for use.