DirectX shader bytecode cross compiler

Related tags

Graphics HLSLcc
Overview

HLSLcc

DirectX shader bytecode cross compiler.

Originally based on https://github.com/James-Jones/HLSLCrossCompiler.

This library takes DirectX bytecode as input, and translates it into the following languages:

  • GLSL (OpenGL 3.2 and later)
  • GLSL ES (OpenGL ES 2.0 and later)
  • GLSL for Vulkan consumption (as input for Glslang to generate SPIR-V)
  • Metal Shading Language

This library is used to generate all shaders in Unity for OpenGL, OpenGL ES 3.0+, Metal and Vulkan.

Changes from original HLSLCrossCompiler:

  • Codebase changed to C++11, with major code reorganizations.
  • Support for multiple language output backends (currently ToGLSL and ToMetal)
  • Metal language output support
  • Temp register type analysis: In DX bytecode the registers are typeless 32-bit 4-vectors. We do code analysis to infer the actual data types (to prevent the need for tons of bitcasts).
  • Loop transformation: Detect constructs that look like for-loops and transform them back to their original form
  • Support for partial precision variables in HLSL (min16float etc). Do extra analysis pass to infer the intended precision of samplers.
  • Reflection interface to retrieve the shader inputs and their types.
  • Lots of workarounds for various driver/shader compiler bugs.
  • Lots of minor fixes and improvements for correctness
  • Lots of Unity-specific tweaks to allow extending HLSL without having to change the D3D compiler itself.

Note

This project is originally integrated into the Unity build systems. However, building this library should be fairly straightforward: just compile src/*.cpp (in C++11 mode!) and src/cbstring/*.c with the following include paths:

  • include
  • src/internal_includes
  • src/cbstrinc
  • src

Alternatively, a CMakeLists.txt is provided to build the project using cmake.

The main entry point is TranslateHLSLFromMem() function in HLSLcc.cpp (taking DX bytecode as input).

Contributors

  • Mikko Strandborg
  • Juho Oravainen
  • David Rogers
  • Marton Ekler
  • Antti Tapaninen
  • Florian Penzkofer
  • Alexey Orlov
  • Povilas Kanapickas
  • Aleksandr Kirillov
  • Kay Chang

License

MIT license for HLSLcc itself, BSD license for the bstring library. See license.txt.

Comments
  • Minor fixes

    Minor fixes

    For some reason, when compiling some simple shaders (with no uniforms), psDependencies ends up null in a bunch of places.

    Caught a missing break;.

    In addition, there were some misuses of vector iterators, that MSVC's debug mode caught (you can't take the address of the first item of a zero length vector, for example).

    opened by hrydgard 5
  • Fix Metal function signature for popcount()

    Fix Metal function signature for popcount()

    Problem

    I get the following error when trying to compile my compute shader on iOS:

    Compilation failed: 
    
    program_source:60:20: error: use of undeclared identifier 'popCount'; did you mean 'popcount'?
            u_xlatu4 = popCount(u_xlati4);
                       ^~~~~~~~
                       popcount
    /System/Library/PrivateFrameworks/GPUCompiler.framework/./Libraries/lib/clang/902.1/include/metal/metal_integer:174:14: note: 'popcount' declared here
    METAL_FUNC T popcount(T x)
    

    When looking at the function signature for popcount it is indeed all lower-case: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf (search for "popcount")

    Solution

    Fix the problem in toMetalInstruction.cpp.

    Is there any way for me to fix this locally in my build while I wait for this fix to propagate through the release cycle?

    opened by gustavolsson 2
  • Compile error on Metal when SV_POSITION interpolator uses half4 instead of float4

    Compile error on Metal when SV_POSITION interpolator uses half4 instead of float4

    HLSL Code

    SubShader
    	{
    		Tags { "RenderType"="Opaque" }
    		LOD 100
    
    		Pass
    		{
    			CGPROGRAM
    			#pragma vertex vert
    			#pragma fragment frag
    
    			#include "UnityCG.cginc"
    
    			struct v2f
    			{
    				half4 pos : SV_POSITION;
    			};
    
    			v2f vert (appdata_full v)
    			{
    				v2f o;
    				o.pos = UnityObjectToClipPos(v.vertex);
    				return o;
    			}
    
    			fixed4 frag (v2f i) : COLOR
    			{
    				return i.pos;
    			}
    			ENDCG
    		}
    	}
    

    iOS Console Log

    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
    
    Compilation failed: 
    
    program_source:27:25: error: use of undeclared identifier 'mtl_FragCoord'
        output.SV_Target0 = mtl_FragCoord;
                            ^
    
    
    
    #include <metal_stdlib>
    #include <metal_texture>
    using namespace metal;
    #if !(__HAVE_FMA__)
    #define fma(a,b,c) ((a) * (b) + (c))
    #endif
    #ifndef XLT_REMAP_O
    #define XLT_REMAP_O {0, 1, 2, 3, 4, 5, 6, 7}
    #endif
    constexpr constant uint xlt_remap_o[] = XLT_REMAP_O;
    struct Mtl_FragmentIn
    {
        half4 mtl_FragCoord [[ user(mtl_FragCoord) ]] ;
    };
    struct Mtl_FragmentOut
    {
        half4 SV_Target0 [[ color(xlt_remap_o[0]) ]];
    };
    fragment Mtl_FragmentOut xlatMtlMain(
        Mtl_FragmentIn input [[ stage_in ]])
    {
        Mtl_FragmentOut output;
        output.SV_Target0 = mtl_FragCoord;
        return output;
    }
    

    Interestingly, mtl_FragCoord is properly defined when using Compile and show code menu in UnityEditor

    -- Fragment shader for "metal":
    Shader Disassembly:
    #include <metal_stdlib>
    #include <metal_texture>
    using namespace metal;
    
    #if !(__HAVE_FMA__)
    #define fma(a,b,c) ((a) * (b) + (c))
    #endif
    
    #ifndef XLT_REMAP_O
    #define XLT_REMAP_O {0, 1, 2, 3, 4, 5, 6, 7}
    #endif
    constexpr constant uint xlt_remap_o[] = XLT_REMAP_O;
    struct Mtl_FragmentOut
    {
        float4 SV_Target0 [[ color(xlt_remap_o[0]) ]];
    };
    
    fragment Mtl_FragmentOut xlatMtlMain(
        float4 mtl_FragCoord [[ position ]])
    {
        Mtl_FragmentOut output;
        output.SV_Target0 = mtl_FragCoord;
        return output;
    }
    
    opened by hitorijanai 2
  • Metal tess support is coming in Unity 2018.1.. support here soon?

    Metal tess support is coming in Unity 2018.1.. support here soon?

    Hi, just seen Unity roadmap mentions "Tessellation for Metal" being on track for 2018.1 release.. so considering we should be seeing support for it in first public beta possibly coming this year it might be very well implemented by now.. would be nice if HLSLcc Metal tesselation support could be posted soon (christmas present for this year? :-) ).. thanks..

    opened by oscarbg 2
  • ClearDependencyData() usage question

    ClearDependencyData() usage question

    Would someone be able to clarify the need for the call to psContext->ClearDependencyData() in ToGLSL::Translate()? I'm trying to get separable shader stages working, but the pixel shader translation responds to this call by wiping out the GLSLCrossDependencyData struct before processing its declarations (so every varying gets put in the wrong location).

    I'm sure there must be a reason for it, but I'm not sure what it is!

    Thanks, Mike

    opened by fitzymj 2
  • Keeped in sync up to 5.6b10?

    Keeped in sync up to 5.6b10?

    Hi, just searching "Metal" in release notes up to b10 I see:

    Enabled UAV writes on fragment shaders for Metal. Fixed some corner cases in Metal compute buffers with counters Added cube array Texture support to OSX Metal

    Just asking if that improvements required changes to HLSLcc or outside of it.. if changes needed will be nice to have HLSLcc updated also but for me big change will be Metal tesselation support in HLSLcc.. hope it's in 2017.1 beta in April and HLSLcc get's it by April also..

    opened by oscarbg 2
  • Incorrect translation of OPCODE_F32TOF16 and posibly OPCODE_F16TOF32

    Incorrect translation of OPCODE_F32TOF16 and posibly OPCODE_F16TOF32

    Hi,

    as the register type analysis of OPCODE_F32TOF16 and OPCODE_F16TOF32 are not implemented, the resulting translation into GLSL results in invalid GLSL as the target/source register type may be incorrect.

    opened by TiemoJung 2
  • CMake Error at CMakeLists.txt:52 (set_target_properties):

    CMake Error at CMakeLists.txt:52 (set_target_properties):

    How can I fix the following error?

    CMake Error at CMakeLists.txt:52 (set_target_properties): set_target_properties called with incorrect number of arguments.

    screen shot 2018-12-13 at 19 23 51
    opened by alelordelo 1
  • Support for Separate texture and sampler

    Support for Separate texture and sampler

    Currently the cross compiler outputs combined image sampler in the glsl output. Is there any way to keep the Sampler and Texture separate? I looked at the flags and there seems to be no way to do this. Adding this will be really helpful

    opened by manas-kulkarni 1
  • Updated to support basic parsing of resources for Shader Model 5.1

    Updated to support basic parsing of resources for Shader Model 5.1

    The stride size for a Shader Model 5.1 is 40 bytes and not 32 bytes like 5.0 and older. Seems like the fields before space remain the same. Just added two additional reads from the tokens. First field is spaces, second field is range id.

    opened by chaoticbob 1
  • fix issue with negation of (bool->int) result

    fix issue with negation of (bool->int) result

    An OPCODE_NOT followed by a SVT_BOOL->SVT_INT conversion can result in the following when the needsBoolUpscale block is hit:

    bool myBool = false;
    int result = ~(myBool) * int(0xffffffffu);
    

    This PR emits the correct GLSL, wrapping the upscale operation in parentheses:

    int result = ~((myBool) * int(0xffffffffu));
    

    It applies the same fix to generated Metal Shading Language.

    opened by fitzymj 1
  • Fix decoding of INDEX_IMMEDIATE64_PLUS_RELATIVE

    Fix decoding of INDEX_IMMEDIATE64_PLUS_RELATIVE

    Note: processing of INDEX_IMMEDIATE64_PLUS_RELATIVE will still fail later in an Assert(0) but that's better than silently mistaking it for OPERAND_INDEX_IMMEDIATE32

    opened by rrika 0
  • Primitive ID is supported on recent versions of Metal

    Primitive ID is supported on recent versions of Metal

    https://github.com/Unity-Technologies/HLSLcc/blob/3ea1fcd6bd0ac445bc078de0bf32f0950188577b/src/toMetalDeclaration.cpp#L262

    This line states that NAME_PRIMITIVE_ID is not supported on Metal - but according to the language spec (page 92… why is this a PDF, Apple 😠) it's supported since Metal 2.2 on macOS and 2.3 on iOS - i.e. macOS 10.15 and iOS 14.0.

    There's a few other places in the Metal translation with comments saying that primitive ID is not supported on Metal, which presumably would also have to be fixed.

    According to the feature set tables this requires MTLGPUFamilyApple7, MTLGPUFamilyMac2 or MTLGPUFamilyMacCatalyst2 (I think). iPhone 12 and above and… no idea which Macs count for that. Not totally sure how that maps on to the targeting system in HLSLcc? Assuming there is some way of doing this..?

    opened by gormster 0
  • Incorrect emitted code when doing bitfield operation

    Incorrect emitted code when doing bitfield operation

    hi, I found a glsl/spirv shader code issue when doing bitfield packing in Unity, the same code works well in dx11(hlsl/dxbc), but things went wrong after converted to glsl/spirv by hlslcc. Shader code below is tested in Shader-Playground hlsl glsl 'u_xlatu0.x' should not be overwritten in the first 'bitfieldExtract' and used as input for the second 'bitfieldExtract'

    opened by yxsamurai 1
  • Incorrect GLSL logic about the reuse variables and countbits function

    Incorrect GLSL logic about the reuse variables and countbits function

    Hi,

    I found 2 issues in the generated GLSL logic

    The HLSL source:

    cbuffer csInput_CB : register(b0)
    {
        uint g_MaskDefault;
        uint g_MaskX;
        uint g_MaskY;
    };
    
    RWBuffer<uint> csOutput : register(u0);
    // ------------------------------------------------------------------------------
    
    void CalculateMask(inout uint mask, in uint index, in uint maskComponent)
    {
        uint curBit = 1;
        mask = g_MaskDefault;
    
        [loop]
        for (uint i = 0; i < index; ++i)
        {
            [flatten]
            if ((index & 1) && (maskComponent & 1))
            {
                mask = curBit;
                break;
            }
            curBit <<= 1;
        }
    }
    
    // ------------------------------------------------------------------------------
    [numthreads(1, 1, 1)]
    void main(uint3 threadID : SV_DispatchThreadID)
    {
        uint2 masks = 0;
        uint outputOffset = (threadID.x << 1);
        uint checkIndex = threadID.x + 1;
    
        CalculateMask(masks.x, checkIndex, g_MaskX);
        CalculateMask(masks.y, checkIndex, g_MaskY);
    
        uint count = countbits(masks.x) + countbits(masks.y);
    
        if (count > 0)
        {
            csOutput[outputOffset + 0] = masks.x;
            csOutput[outputOffset + 1] = masks.y;
        }
    }
    

    The generated GLSL source:

    #version 430
    #extension GL_ARB_shading_language_420pack : require
    
    layout(location = 0) uniform 	uint g_MaskDefault;
    uniform 	uint g_MaskX;
    uniform 	uint g_MaskY;
    writeonly layout(binding=0) uniform uimageBuffer csOutput;
    ivec3 u_xlati0;
    uint u_xlatu0;
    uint u_xlatu1;
    ivec3 u_xlati2;
    uvec3 u_xlatu2;
    bvec3 u_xlatb2;
    uint u_xlatu3;
    int u_xlati4;
    int u_xlati5;
    uint u_xlatu5;
    bool u_xlatb5;
    layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
    void main()
    {
        u_xlatu0 = gl_GlobalInvocationID.x + 1u;
        u_xlati2.x = int(u_xlatu0 & 1u);
        u_xlati2.yz = ivec2(csInput_CBCS.uvec2(g_MaskX, g_MaskY) & uvec2(1u, 1u));
        u_xlatb2.xyz = notEqual(u_xlati2.xyzz, ivec4(0, 0, 0, 0)).xyz;
        u_xlatb2.x = u_xlatb2.y && u_xlatb2.x; 
        u_xlatb2.y = u_xlatb2.z && u_xlatb2.x;Incorrect
        u_xlatu2.z = csInput_CBCS.g_MaskDefault;
        u_xlatu1 = uint(1u);
        for(uint u_xlatu_loop_1 = uint(0u) ; u_xlatu_loop_1<u_xlatu0 ; u_xlatu_loop_1++)
        {
            u_xlatu5 = (u_xlatb2.x) ? u_xlatu1 : u_xlatu2.z;
            u_xlatu2.z = u_xlatu5;
            u_xlati5 = int(u_xlatb2.x);
            if(u_xlati5 != 0) {break;}
            u_xlatu1 = u_xlatu1 << 1u;
        }
        u_xlatu2.x = csInput_CBCS.g_MaskDefault;
        u_xlatu1 = uint(1u);
        for(uint u_xlatu_loop_2 = uint(0u) ; u_xlatu_loop_2<u_xlatu0 ; u_xlatu_loop_2++)
        {
            u_xlatu5 = (u_xlatb2.y) ? u_xlatu1 : u_xlatu2.x;
            u_xlatu2.x = u_xlatu5;
            u_xlati5 = int(u_xlatb2.y);
            if(u_xlati5 != 0) {break;}
            u_xlatu1 = u_xlatu1 << 1u;
        }
        u_xlati0.xz = bitCount(ivec4(u_xlatu2.zzxz));
        u_xlati0.x = u_xlati0.z + u_xlati0.x;
        if(u_xlati0.x != 0) {
            u_xlati0.x = int(gl_GlobalInvocationID.x) << 1;
            imageStore(csOutput, u_xlati0.x, u_xlatu2.zzzz);
            u_xlati4 = int(gl_GlobalInvocationID.x) * 2 + 1;
            imageStore(csOutput, int(u_xlati4), u_xlatu2.xxxx);
        //ENDIF
        }
        return;
    }
    

    Issue1: Incorrect reuse variables

    'u_xlatb2.y' will get an incorrect result, as 'u_xlatb2.x' in the previous step has been changed. So the next logics which're using 'x_xlatb2.y', will get the error results

    u_xlatu0 = gl_GlobalInvocationID.x + 1u; // u_xlatu0 --> checkIndex (in HLSL)
    u_xlatb2.xyz = notEqual(u_xlati2.xyzz, ivec4(0, 0, 0, 0)).xyz; //  x --> (checkIndex&1), y --> (g_MaskX&1), z --> (g_MaskY&1)
    u_xlatb2.x = u_xlatb2.y && u_xlatb2.x;  // u_xlatb2.x --> (checkIndex&1) && (g_MaskX&1)
    u_xlatb2.y = u_xlatb2.z && u_xlatb2.x; // Error: u_xlatb2.y --> (checkIndex&1) && (g_MaskX&1) && (g_MaskY&1),
                                           // it should be (checkIndex&1) && (g_MaskY&1)
    

    Issue2: countbits function convert

    The bitCount argument is incorrect, when 'z' is 0 and 'x' greater 0, the 'u_xlati0' will get an incorrect result.

    u_xlati0.xz = bitCount(ivec4(u_xlatu2.zzxz));
    

    For the detail compilation information, please check Here

    opened by xlincuts 0
  • GetInputSignatureFromRegister does not resolve input signature for PrimativeID operands correctly

    GetInputSignatureFromRegister does not resolve input signature for PrimativeID operands correctly

    When declaring an input such as

    // Input signature:
    //
    // Name                 Index   Mask Register SysValue  Format   Used
    // -------------------- ----- ------ -------- -------- ------- ------
    // SV_PrimitiveID           0    N/A   primID   PRIMID    uint    YES
    gs_4_0
    dcl_input vPrim
    

    vPrim in dcl_input vPrim is encoded as 0x0, while primID in the input signature is encoded as 0xFFFFFFFF, so GetInputSignatureFromRegister will either return the wrong input signature when there are multiple, or null if SV_PrimitiveID is the only input signature.

    I would expect there would need to be a new GetInputSignatureFromX method to resolve it properly, similiar to how GetOutputSignatureFromSystemValue works, but dcl_input vPrim does not have an eSpecialName.

    An example of a shader that will trigger this bug:

    Shader "Unlit/TestShader"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma geometry geom
    
                struct appdata {
    
                };
    
                struct v2g {
                };
    
                struct g2f
                {
                    float4 pos : SV_POSITION;
                    float4 color: texcoord1;
                    float3 normal : TEXCOORD2;
                };
    
                v2g vert(appdata v) 
                {
                    v2g o = (v2g)0;
                    return o;
                }
    
                [maxvertexcount(3)]
                void geom(triangle v2g IN[3], uint prim : SV_PrimitiveID, inout LineStream<g2f> tristream)
                {
                    g2f o;
                    for (int i = 0; i < 2; i++)
                    {
                        o.pos = 0;
                        o.color = 0;
                        o.normal = 0;
                        tristream.Append(o);
                    }
                }
    
    
                float4 frag(g2f i) : COLOR{
    
                    float3 col = i.color;
                    return fixed4(col, 1);
                }
                ENDCG
            }
        }
    }
    
    opened by spacehamster 0
Owner
Unity Technologies
Unity Technologies
The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++

DirectX Tool Kit for DirectX 11 http://go.microsoft.com/fwlink/?LinkId=248929 Copyright (c) Microsoft Corporation. All rights reserved. January 9, 202

Microsoft 2.2k Jan 3, 2023
SPIRV-Reflect is a lightweight library that provides a C/C++ reflection API for SPIR-V shader bytecode in Vulkan applications.

SPIRV-Reflect SPIRV-Reflect is a lightweight library that provides a C/C++ reflection API for SPIR-V shader bytecode in Vulkan applications. SPIRV-Ref

The Khronos Group 457 Dec 26, 2022
A C++/DirectX 11 implementation of "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"

Atmosphere Renderer A C++/DirectX 11 implementation of "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" Features interactive e

Z Guan 37 Nov 20, 2022
Playground for DirectX 11 / 12 simple graphics demo examples ...

graphicsdemoskeleton Playground for DirectX 11 / 12 simple graphics demo examples ... If anyone from Microsoft reads this: C99 support is broken in Di

Wolfgang Engel 44 Dec 10, 2022
DirectX 11 and 12 library that provides a scalable and GCN-optimized solution for deferred shadow filtering

AMD ShadowFX The ShadowFX library provides a scalable and GCN-optimized solution for deferred shadow filtering. Currently the library supports uniform

GPUOpen Effects 163 Dec 9, 2022
A real-time DirectX 11 renderer. The renderer is named by my girlfriend's english name.

sophia Sophia is a real-time DirectX 11 renderer. It is not quite a rich graphics engine, only packages some low-level DirectX functions and contains

BB 6 Dec 11, 2021
This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows.

DirectX-Graphics-Samples This repo contains the DirectX 12 Graphics samples that demonstrate how to build graphics intensive applications for Windows

Microsoft 4.9k Dec 26, 2022
✖🌱 A DirectX 12 starter repo that you could use to get the ball rolling.

DirectX 12 Seed A DirectX 12 repo you can use to get started with your own renderer. Setup First install: Git CMake Visual Studio Then type the follow

Alain Galvan 74 Dec 6, 2022
My computer graphics playground. Currently has a raytracer implemented with D3D11 compute shader.

Graphics Playground I use this project as my "toy" engine. I'll be implementing various graphics projects in this repository. The code here is not sui

Berk Emre Sarıbaş 4 Aug 26, 2021
Blend text in a HLSL shader and have it look like native DirectWrite

dwrite-hlsl This project demonstrates how to blend text in a HLSL shader and have it look like native DirectWrite. License This project is an extract

Leonard Hecker 11 May 24, 2022
A physically based shader for woven cloth

ThunderLoom A physically based shader for woven cloth This projects consits of three main parts: Irawan shading model At its core is an implementation

null 92 Oct 29, 2022
Basic framework for D3D11 init, model/texture loading, shader compilation and camera movement.

reed-framework Basic framework for D3D11 init, model/texture loading, camera movement, etc. Instructions: #include <framework.h> Link with framework.l

Nathan Reed 34 May 18, 2022
2D Vector Graphics Engine Powered by a JIT Compiler

Blend2D 2D Vector Graphics Powered by a JIT Compiler. Official Home Page (blend2d.com) Official Repository (blend2d/blend2d) Public Chat Channel Zlib

Blend2D 1.2k Dec 27, 2022
A header-only C-like shading language compiler that writes Metal, HLSL, GLSL

GPUC A generic shading language compiler that writes metal, HLSL, and GLSL GPUC is a work in progress, not ready for prime time. The primary motivatio

Garett Bass 61 Oct 18, 2022
Cross-platform, graphics API agnostic, "Bring Your Own Engine/Framework" style rendering library.

bgfx - Cross-platform rendering library GitHub Discussions Discord Chat What is it? Cross-platform, graphics API agnostic, "Bring Your Own Engine/Fram

Бранимир Караџић 12.6k Jan 8, 2023
A modern cross-platform low-level graphics library and rendering framework

Diligent Engine A Modern Cross-Platform Low-Level 3D Graphics Library Diligent Engine is a lightweight cross-platform graphics API abstraction library

Diligent Graphics 2.6k Dec 30, 2022
Cross-platform 2D and 3D game engine.

Urho3D Urho3D is a free lightweight, cross-platform 2D and 3D game engine implemented in C++ and released under the MIT license. Greatly inspired by O

null 4.2k Jan 4, 2023
Cross-platform, sophisticated frontend for the libretro API. Licensed GPLv3.

RetroArch RetroArch is the reference frontend for the libretro API. Popular examples of implementations for this API includes video game system emulat

null 7.4k Dec 30, 2022
Gearcoleco is a cross-platform ColecoVision emulator written in C++.

This is an open source project with its ongoing development made possible thanks to the support by these awesome backers.

Ignacio Sanchez Gines 41 Nov 12, 2022