From da203c80f36bf7338ccd173729e06283c2164b51 Mon Sep 17 00:00:00 2001 From: Patedam Date: Sat, 8 Mar 2025 22:36:15 -0500 Subject: [PATCH] Finished first version of shader compiler. HLSL -> DXIL. Submitting vertex and frag shader needed to display a triangle. --- Assets/compiled/SolidColor.frag.dxil | Bin 0 -> 2812 bytes Assets/compiled/Triangle.vert.dxil | Bin 0 -> 3108 bytes Assets/source/SolidColor.frag.hlsl | 4 + Assets/source/Triangle.vert.hlsl | 39 +++ Juliet/include/Core/Common/CoreTypes.h | 2 +- Juliet/include/Core/Common/CoreUtils.h | 43 ++- Juliet/include/Core/Common/String.h | 20 +- Juliet/include/Core/HAL/IO/IOStream.h | 8 +- Juliet/include/Core/Logging/LogManager.h | 5 +- Juliet/include/Core/Logging/LogTypes.h | 2 +- Juliet/include/Core/Memory/Allocator.h | 8 +- Juliet/include/Core/Memory/Utils.h | 1 + .../include/Core/Networking/NetworkPacket.h | 2 +- Juliet/src/Core/Common/CoreUtils.cpp | 13 +- Juliet/src/Core/Common/String.cpp | 240 ++++++++++++----- Juliet/src/Core/HAL/Filesystem/Filesystem.cpp | 4 +- Juliet/src/Core/HAL/IO/IOStream.cpp | 49 ++-- .../src/Core/HAL/IO/Win32/Win32IOStream.cpp | 16 +- Juliet/src/Core/Logging/LogManager.cpp | 36 ++- Juliet/src/Core/Networking/NetworkPacket.cpp | 6 +- Juliet/src/Graphics/D3D12/D3D12Common.cpp | 2 + JulietApp/main.cpp | 12 +- .../{ => DXShaderCompiler}/DXCompiler.h | 0 .../JulietShaderCompiler.vcxproj | 8 +- JulietShaderCompiler/ShaderCompiler.cpp | 199 ++++++++++++++ JulietShaderCompiler/ShaderCompiler.h | 42 +++ JulietShaderCompiler/main.cpp | 249 +++++++++++++++--- 27 files changed, 837 insertions(+), 173 deletions(-) create mode 100644 Assets/compiled/SolidColor.frag.dxil create mode 100644 Assets/compiled/Triangle.vert.dxil create mode 100644 Assets/source/SolidColor.frag.hlsl create mode 100644 Assets/source/Triangle.vert.hlsl rename JulietShaderCompiler/{ => DXShaderCompiler}/DXCompiler.h (100%) create mode 100644 JulietShaderCompiler/ShaderCompiler.cpp create mode 100644 JulietShaderCompiler/ShaderCompiler.h diff --git a/Assets/compiled/SolidColor.frag.dxil b/Assets/compiled/SolidColor.frag.dxil new file mode 100644 index 0000000000000000000000000000000000000000..c9f2b868c8b7984dd48dd628b9d1fe6cf0ed81ad GIT binary patch literal 2812 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 CQLi}w literal 0 HcmV?d00001 diff --git a/Assets/source/SolidColor.frag.hlsl b/Assets/source/SolidColor.frag.hlsl new file mode 100644 index 0000000..a8e6d9f --- /dev/null +++ b/Assets/source/SolidColor.frag.hlsl @@ -0,0 +1,4 @@ +float4 main(float4 Color : TEXCOORD0) : SV_Target0 +{ + return Color; +} diff --git a/Assets/source/Triangle.vert.hlsl b/Assets/source/Triangle.vert.hlsl new file mode 100644 index 0000000..56c2885 --- /dev/null +++ b/Assets/source/Triangle.vert.hlsl @@ -0,0 +1,39 @@ +struct Input +{ + uint VertexIndex : SV_VertexID; +}; + +struct Output +{ + float4 Color : TEXCOORD0; + float4 Position : SV_Position; +}; + +Output main(Input input) +{ + Output output; + float2 pos; + if (input.VertexIndex == 0) + { + pos = (-1.0f).xx; + output.Color = float4(1.0f, 0.0f, 0.0f, 1.0f); + } + else + { + if (input.VertexIndex == 1) + { + pos = float2(1.0f, -1.0f); + output.Color = float4(0.0f, 1.0f, 0.0f, 1.0f); + } + else + { + if (input.VertexIndex == 2) + { + pos = float2(0.0f, 1.0f); + output.Color = float4(0.0f, 0.0f, 1.0f, 1.0f); + } + } + } + output.Position = float4(pos, 0.0f, 1.0f); + return output; +} diff --git a/Juliet/include/Core/Common/CoreTypes.h b/Juliet/include/Core/Common/CoreTypes.h index a8e3034..1cf3cd0 100644 --- a/Juliet/include/Core/Common/CoreTypes.h +++ b/Juliet/include/Core/Common/CoreTypes.h @@ -20,7 +20,7 @@ using size_t = std::size_t; struct ByteBuffer { - const Byte* Data; + Byte* Data; size_t Size; }; diff --git a/Juliet/include/Core/Common/CoreUtils.h b/Juliet/include/Core/Common/CoreUtils.h index e03efc4..1c5f86e 100644 --- a/Juliet/include/Core/Common/CoreUtils.h +++ b/Juliet/include/Core/Common/CoreUtils.h @@ -1,9 +1,10 @@ #pragma once -#include #include #include +#include + namespace Juliet { @@ -24,8 +25,6 @@ namespace Juliet extern void JULIET_API JulietAssert(const char* expression); -#define DEFER(cleanup) for (bool done_ = 0; !done_; (cleanup), done_ = 1) - #define ZeroStruct(structInstance) ZeroSize(sizeof(structInstance), &(structInstance)) #define ZeroArray(array) ZeroSize(sizeof((array)), (array)) #define ZeroDynArray(Count, Pointer) ZeroSize((Count) * sizeof((Pointer)[0]), Pointer) @@ -38,8 +37,44 @@ namespace Juliet } } -#define MemCopy memcpy + template + class DeferredFunction + { + public: + explicit DeferredFunction(const Function& otherFct) noexcept + : Callback(otherFct) + { + } + explicit DeferredFunction(Function&& otherFct) noexcept + : Callback(std::move(otherFct)) + { + } + ~DeferredFunction() noexcept { Callback(); } + + DeferredFunction(const DeferredFunction&) = delete; + DeferredFunction(const DeferredFunction&&) = delete; + void operator=(const DeferredFunction&) = delete; + void operator=(DeferredFunction&&) = delete; + + private: + Function Callback; + }; + + template + auto Defer(Function&& fct) noexcept + { + return DeferredFunction>{ std::forward(fct) }; + } + + inline bool IsValid(ByteBuffer buffer) + { + return buffer.Size > 0 && buffer.Data; + } + + extern JULIET_API void Free(ByteBuffer& buffer); + + // TODO move to math utils template constexpr Type Min(Type lhs, Type rhs) { diff --git a/Juliet/include/Core/Common/String.h b/Juliet/include/Core/Common/String.h index fd7c20f..a2b8a29 100644 --- a/Juliet/include/Core/Common/String.h +++ b/Juliet/include/Core/Common/String.h @@ -33,6 +33,13 @@ namespace Juliet size_t Size; }; + struct StringBuffer : String + { + size_t Capacity; + }; + + constexpr uint32 kInvalidUTF8 = 0xFFFD; + inline size_t StringLength(String str) { return str.Size; @@ -56,7 +63,7 @@ namespace Juliet return length; } - inline bool StringIsValid(String str) + inline bool IsValid(String str) { return str.Size > 0 && str.Data != nullptr && *str.Data; } @@ -89,7 +96,7 @@ namespace Juliet inline bool ContainsChar(String str, char c) { - return StringIsValid(FindChar(str, c)); + return IsValid(FindChar(str, c)); } // Return: @@ -117,6 +124,9 @@ namespace Juliet return result; } + JULIET_API uint32 StepUTF8(String& inStr); + JULIET_API String FindString(String strLeft, String strRight); + // Case insensitive compare. Supports ASCII only // TODO: Support UNICODE extern JULIET_API int8 StringCompareCaseInsensitive(String str1, String str2); @@ -127,10 +137,8 @@ namespace Juliet // src and dst will be casted based on the encoding. // size will correspond to the number of characters // Will convert \0 character if present. - extern JULIET_API bool ConvertString(StringEncoding from, StringEncoding to, const char* src, size_t srcLen, - char*& dst, size_t& dstLen, size_t dstCapacity); - extern JULIET_API bool ConvertString(String from, String to, const char* src, size_t srcLen, char*& dst, - size_t& dstLen, size_t dstCapacity); + extern JULIET_API bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst); + extern JULIET_API bool ConvertString(String from, String to, String src, StringBuffer& dst); } // namespace Juliet diff --git a/Juliet/include/Core/HAL/IO/IOStream.h b/Juliet/include/Core/HAL/IO/IOStream.h index a022deb..d94db3e 100644 --- a/Juliet/include/Core/HAL/IO/IOStream.h +++ b/Juliet/include/Core/HAL/IO/IOStream.h @@ -41,7 +41,7 @@ namespace Juliet int64 (*Seek)(NonNullPtr data, int64 offset, IOStreamSeekPivot pivot); size_t (*Read)(NonNullPtr data, void* outBuffer, size_t size, NonNullPtr status); - size_t (*Write)(NonNullPtr data, const void* inBuffer, size_t size, NonNullPtr status); + size_t (*Write)(NonNullPtr data, ByteBuffer inBuffer, NonNullPtr status); bool (*Flush)(NonNullPtr data, NonNullPtr status); bool (*Close)(NonNullPtr data); @@ -54,7 +54,7 @@ namespace Juliet // Write formatted string into the stream. extern JULIET_API size_t IOPrintf(NonNullPtr stream, _Printf_format_string_ const char* format, ...); - extern JULIET_API size_t IOWrite(NonNullPtr stream, const void* ptr, size_t size); + extern JULIET_API size_t IOWrite(NonNullPtr stream, ByteBuffer inBuffer); extern JULIET_API size_t IORead(NonNullPtr stream, void* ptr, size_t size); extern JULIET_API int64 IOSeek(NonNullPtr stream, int64 offset, IOStreamSeekPivot pivot); @@ -62,8 +62,8 @@ namespace Juliet extern JULIET_API int64 IOSize(NonNullPtr stream); // TODO : Use memory arena because that Allocates - extern JULIET_API uint8* LoadFile(String filename, size_t& outByteRead); - extern JULIET_API uint8* LoadFile(NonNullPtr stream, size_t& outByteRead, bool closeStreamWhenDone); + extern JULIET_API ByteBuffer LoadFile(String filename); + extern JULIET_API ByteBuffer LoadFile(NonNullPtr stream, bool closeStreamWhenDone); extern JULIET_API bool IOClose(NonNullPtr stream); } // namespace Juliet diff --git a/Juliet/include/Core/Logging/LogManager.h b/Juliet/include/Core/Logging/LogManager.h index b7d6999..bf8c5fe 100644 --- a/Juliet/include/Core/Logging/LogManager.h +++ b/Juliet/include/Core/Logging/LogManager.h @@ -35,7 +35,7 @@ namespace Juliet std::deque Entries; bool IsInitialized : 1; - friend void Log(LogLevel level, LogCategory category, const char* fmt, ...); + friend void Log(LogLevel level, LogCategory category, const char* fmt, va_list args); void AddEntry(Entry&& entry); static void OutputLog(Entry& entry); }; @@ -43,4 +43,7 @@ namespace Juliet extern void JULIET_API InitializeLogManager(); extern void JULIET_API ShutdownLogManager(); extern void JULIET_API Log(LogLevel level, LogCategory category, const char* fmt, ...); + extern void JULIET_API LogMessage(LogCategory category, const char* fmt, ...); + extern void JULIET_API LogWarning(LogCategory category, const char* fmt, ...); + extern void JULIET_API LogError(LogCategory category, const char* fmt, ...); } // namespace Juliet diff --git a/Juliet/include/Core/Logging/LogTypes.h b/Juliet/include/Core/Logging/LogTypes.h index 35984fd..005733e 100644 --- a/Juliet/include/Core/Logging/LogTypes.h +++ b/Juliet/include/Core/Logging/LogTypes.h @@ -15,7 +15,7 @@ namespace Juliet Graphics = 1, Networking = 2, Engine = 3, - Editor = 4, + Tool = 4, Game = 5, }; } // namespace Juliet diff --git a/Juliet/include/Core/Memory/Allocator.h b/Juliet/include/Core/Memory/Allocator.h index 2501d54..d91c6b7 100644 --- a/Juliet/include/Core/Memory/Allocator.h +++ b/Juliet/include/Core/Memory/Allocator.h @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include namespace Juliet { @@ -15,7 +17,7 @@ namespace Juliet void Free(Type* memory) { Assert(memory); - free(memory); + ::free(memory); } // Free and Set the ptr to nullptr template @@ -23,7 +25,7 @@ namespace Juliet { if (memory) { - free(memory); + ::free(memory); memory = nullptr; } } diff --git a/Juliet/include/Core/Memory/Utils.h b/Juliet/include/Core/Memory/Utils.h index 94dadc4..2a9ad8b 100644 --- a/Juliet/include/Core/Memory/Utils.h +++ b/Juliet/include/Core/Memory/Utils.h @@ -19,4 +19,5 @@ namespace Juliet } #define MemSet memset +#define MemCopy memcpy } // namespace Juliet diff --git a/Juliet/include/Core/Networking/NetworkPacket.h b/Juliet/include/Core/Networking/NetworkPacket.h index f121878..df2cc81 100644 --- a/Juliet/include/Core/Networking/NetworkPacket.h +++ b/Juliet/include/Core/Networking/NetworkPacket.h @@ -21,7 +21,7 @@ namespace Juliet // Pack NetworkPacket& operator<<(uint32 value); - NetworkPacket& operator<<(const char* data); + NetworkPacket& operator<<(char* data); protected: void Append(ByteBuffer buffer); diff --git a/Juliet/src/Core/Common/CoreUtils.cpp b/Juliet/src/Core/Common/CoreUtils.cpp index 46659d4..70bddf6 100644 --- a/Juliet/src/Core/Common/CoreUtils.cpp +++ b/Juliet/src/Core/Common/CoreUtils.cpp @@ -1,10 +1,21 @@ #include +#include + namespace Juliet { void JulietAssert(const char* expression) { - Juliet::Log(Juliet::LogLevel::Error, Juliet::LogCategory::Core, expression); + Log(LogLevel::Error, LogCategory::Core, expression); __debugbreak(); } + + void Free(ByteBuffer& buffer) + { + if (buffer.Data) + { + Free(buffer.Data); + } + buffer = {}; + } } // namespace Juliet diff --git a/Juliet/src/Core/Common/String.cpp b/Juliet/src/Core/Common/String.cpp index 9e23d3d..d9d8fae 100644 --- a/Juliet/src/Core/Common/String.cpp +++ b/Juliet/src/Core/Common/String.cpp @@ -7,7 +7,6 @@ namespace Juliet { namespace { - constexpr char kUnknown_ASCII = '?'; constexpr int32 kUnknown_UNICODE = 0xFFFD; struct @@ -57,52 +56,162 @@ namespace Juliet *to = from; return 1; } + } // namespace - void Step(String& inStr, size_t byteToStep) + uint32 StepUTF8(String& inStr) + { + // From rfc3629, the UTF-8 spec: + // https://www.ietf.org/rfc/rfc3629.txt + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+--------------------------------------------- + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // The function checks character validity and overlong (using too many bytes to encode the character) and return invalid utf8 char if detected. + + // If string is empty then it's done. + if (inStr.Size == 0) { - Assert(inStr.Size >= byteToStep); - - inStr.Data += byteToStep; - inStr.Size -= byteToStep; - } - - uint32 StepUTF8(String& inStr, size_t byteToRead) - { - /* - * From rfc3629, the UTF-8 spec: - * https://www.ietf.org/rfc/rfc3629.txt - * - * Char. number range | UTF-8 octet sequence - * (hexadecimal) | (binary) - * --------------------+--------------------------------------------- - * 0000 0000-0000 007F | 0xxxxxxx - * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx - * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx - * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - - // If string is empty then it's done. - if (inStr.Size == 0) - { - return 0; - } - - auto str = reinterpret_cast(CStr(inStr)); - const uint32 octet = byteToRead ? *str : 0; - - if (octet == 0) - { - return 0; - } - if ((octet & 0x80) == 0x0) // One byte code point - { - Step(inStr, 1); - return octet; - } - Assert(false && "StepUTF8 only supports one byte codepoints for now"); return 0; } - } // namespace + + auto str = reinterpret_cast(CStr(inStr)); + const uint32 octet = *str; + bool isOverlong = false; + bool isInvalid = false; + bool isUTF16Surrogate = false; + if (octet == 0) + { + return 0; + } + if ((octet & 0x80) == 0x0) // One byte code point: 0xxxxxxx + { + inStr.Data += 1; + inStr.Size -= 1; + return octet; + } + else if ((octet & 0xE0) == 0xC0) // Two bytes code point: 110xxxxx 10xxxxxx + { + const uint8 secondByte = str[1]; + if ((secondByte & 0xC0) == 0x80) // Make sure the trailing byte is correct + { + const uint32 result = ((octet & 0x1F) << 6) | (secondByte & 0x3F); + if (result >= 0x80) // If the result is smaller than 0x80 its an overlong! + { + inStr.Data += 2; + inStr.Size -= 1; + return result; + } + else + { + isOverlong = true; + } + } + else + { + isInvalid = true; + } + } + else if (((octet & 0xF0) == 0xE0)) // Three bytes code point: 1110xxxx 10xxxxxx 10xxxxxx + { + const uint8 secondByte = str[1]; + const uint8 thirdByte = str[2]; + if (((secondByte & 0xC0) == 0x80) && ((thirdByte & 0xC0) == 0x80)) // Make sure the trailing bytes are correct + { + const uint32 secondOctet = static_cast(secondByte & 0x3F) << 6; + const uint32 thirdOctet = static_cast(thirdByte & 0x3F); + const uint32 result = ((octet & 0x0F) << 12) | secondOctet | thirdOctet; + if (result >= 0x0800) // if the result is smaller its an overlong. + { + if ((result < 0xD800) || (result > 0xDFFF)) // If out of range its an UTF-16 surrogate + { + inStr.Data += 3; + inStr.Size -= 1; + return result; + } + else + { + isUTF16Surrogate = true; + } + } + else + { + isOverlong = true; + } + } + else + { + isInvalid = true; + } + } + else if (((octet & 0xF8) == 0xF0)) + { // Four bytes code point: 11110xxxx 10xxxxxx 10xxxxxx 10xxxxxx + const uint8 secondByte = str[1]; + const uint8 thirdByte = str[2]; + const uint8 fourthByte = str[3]; + if (((secondByte & 0xC0) == 0x80) && ((thirdByte & 0xC0) == 0x80) && + ((fourthByte & 0xC0) == 0x80)) // // Make sure the trailing bytes are correct + { + const uint32 secondOctet = static_cast(secondByte & 0x1F) << 12; + const uint32 thirdOctet = static_cast(thirdByte & 0x3F) << 6; + const uint32 fourthOctet = static_cast(fourthByte & 0x3F); + const uint32 result = ((octet & 0x07) << 18) | secondOctet | thirdOctet | fourthOctet; + if (result >= 0x10000) // If smaller its an overlong + { + inStr.Data += 4; + inStr.Size -= 1; + return result; + } + else + { + isOverlong = true; + } + } + else + { + isInvalid = true; + } + } + LogError(LogCategory::Core, "StepUTF8: Non supported codepoint. IsOverlong: %s. IsInvalid %s. IsUTF16Surrogate %s", + isOverlong ? "true" : "false", isInvalid ? "true" : "false", isUTF16Surrogate ? "true" : "false"); + inStr.Data += 1; + return kInvalidUTF8; + } + + String FindString(String haystack, String needle) + { + if (!IsValid(needle)) + { + return haystack; + } + + for (; IsValid(haystack); StepUTF8(haystack)) + { + String tempHaystack = haystack; + String testNeedle = needle; + + while (IsValid(tempHaystack) && IsValid(testNeedle)) + { + uint32 codepointHaystack = StepUTF8(tempHaystack); + uint32 codepointNeedle = StepUTF8(testNeedle); + if (codepointHaystack != codepointNeedle) + { + break; + } + } + + if (!IsValid(testNeedle)) + { + return haystack; + } + } + + return {}; + } int8 StringCompareCaseInsensitive(String str1, String str2) { @@ -113,13 +222,13 @@ namespace Juliet { { uint32 leftFolded[3]; - int8 num_folded = CaseFoldUnicode(StepUTF8(str1, 4), leftFolded); + int8 num_folded = CaseFoldUnicode(StepUTF8(str1), leftFolded); Assert(num_folded == 1); // Only one uint32 codepoint supported for now (low ascii) left = leftFolded[0]; } { uint32 rightFolded[3]; - int8 num_folded = CaseFoldUnicode(StepUTF8(str2, 4), rightFolded); + int8 num_folded = CaseFoldUnicode(StepUTF8(str2), rightFolded); Assert(num_folded == 1); // Only one uint32 codepoint supported for now (low ascii) right = rightFolded[0]; } @@ -139,15 +248,16 @@ namespace Juliet return 0; } - bool ConvertString(StringEncoding from, StringEncoding to, const char* src, size_t srcLen, char*& dst, size_t& dstLen, size_t dstCapacity) + bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst) { - Assert(src && *src); + Assert(IsValid(src)); - const char* srcStr = src; - char* dstStr = dst; + const char* srcStr = src.Data; + char* dstStr = dst.Data; + size_t remainingCapacity = dst.Capacity; uint32 character = 0; - while (srcLen > 0) + while (src.Size > 0) { // Decode in character switch (from) @@ -165,7 +275,7 @@ namespace Juliet } else { - if (p[0] == 0xF0 && srcLen > 1 && (p[1] & 0xF0) == 0x80) + if (p[0] == 0xF0 && src.Size > 1 && (p[1] & 0xF0) == 0x80) { overlong = true; } @@ -181,7 +291,7 @@ namespace Juliet } else { - if (p[0] == 0xE0 && srcLen > 1 && (p[1] & 0xE0) == 0x80) + if (p[0] == 0xE0 && src.Size > 1 && (p[1] & 0xE0) == 0x80) { overlong = true; } @@ -217,8 +327,8 @@ namespace Juliet } } ++srcStr; - --srcLen; - if (srcLen < left) + --src.Size; + if (src.Size < left) { Log(LogLevel::Error, LogCategory::Core, "ConvertString: Failed to convert string. Incomplete input sequence"); return false; @@ -234,7 +344,7 @@ namespace Juliet character <<= 6; character |= (p[0] & 0x3F); ++srcStr; - --srcLen; + --src.Size; } if (overlong) { @@ -268,7 +378,7 @@ namespace Juliet } if (character < 0x10000) { - if (dstCapacity < 2) + if (remainingCapacity < 2) { Log(LogLevel::Error, LogCategory::Core, "ConvertString: Destination buffer too short to fit UTF16"); return false; @@ -277,12 +387,12 @@ namespace Juliet p[0] = static_cast(character); dstStr += 2; - dstLen += 1; - dstCapacity -= 2; + dst.Size += 1; + remainingCapacity -= 2; } else { - if (dstCapacity < 4) + if (remainingCapacity < 4) { Log(LogLevel::Error, LogCategory::Core, "ConvertString: Destination buffer too short to fit UTF16"); return false; @@ -296,8 +406,8 @@ namespace Juliet p[2] = static_cast(word2); dstStr += 4; - dstLen += 1; - dstCapacity -= 4; + dst.Size += 1; + remainingCapacity -= 4; } break; } @@ -313,10 +423,10 @@ namespace Juliet return true; } - bool ConvertString(String from, String to, const char* src, size_t srcSize, char*& dst, size_t& dstSize, size_t dstCapacity) + bool ConvertString(String from, String to, String src, StringBuffer& dst) { - Assert(StringIsValid(from)); - Assert(StringIsValid(to)); + Assert(IsValid(from)); + Assert(IsValid(to)); // First find the encoding of the strings auto sourceFormat = StringEncoding::Unknown; @@ -346,6 +456,6 @@ namespace Juliet return false; } - return ConvertString(sourceFormat, destFormat, src, srcSize, dst, dstSize, dstCapacity); + return ConvertString(sourceFormat, destFormat, src, dst); } } // namespace Juliet diff --git a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp index ce0ea2b..4c45768 100644 --- a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp +++ b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp @@ -15,7 +15,7 @@ namespace Juliet String GetBasePath() { - if (!StringIsValid(CachedBasePath)) + if (!IsValid(CachedBasePath)) { CachedBasePath = Platform::GetBasePath(); } @@ -26,7 +26,7 @@ namespace Juliet void ShutdownFilesystem() { - if (StringIsValid(CachedBasePath)) + if (IsValid(CachedBasePath)) { CachedBasePath.Size = 0; SafeFree(CachedBasePath.Data); diff --git a/Juliet/src/Core/HAL/IO/IOStream.cpp b/Juliet/src/Core/HAL/IO/IOStream.cpp index d48f32f..7fd00a9 100644 --- a/Juliet/src/Core/HAL/IO/IOStream.cpp +++ b/Juliet/src/Core/HAL/IO/IOStream.cpp @@ -11,12 +11,12 @@ namespace Juliet { IOStream* IOFromFile(String filename, String mode) { - if (!StringIsValid(filename)) + if (!IsValid(filename)) { Log(LogLevel::Error, LogCategory::Core, "Trying to open IOStream on invalid filename"); return nullptr; } - if (!StringIsValid(mode)) + if (!IsValid(mode)) { Log(LogLevel::Error, LogCategory::Core, "Trying to open IOStream with invalid mode"); return nullptr; @@ -54,10 +54,11 @@ namespace Juliet Assert(writtenSize >= 0); - return IOWrite(stream, formattedBuffer, static_cast(writtenSize)); + ByteBuffer buffer = {.Data = reinterpret_cast(formattedBuffer), .Size = static_cast(writtenSize) }; + return IOWrite(stream, buffer); } - size_t IOWrite(NonNullPtr stream, const void* ptr, size_t size) + size_t IOWrite(NonNullPtr stream, ByteBuffer inBuffer) { if (!stream->Interface.Write) { @@ -68,12 +69,12 @@ namespace Juliet stream->Status = IOStreamStatus::Ready; - if (size == 0) + if (!IsValid(inBuffer)) { return 0; } - size_t writtenBytes = stream->Interface.Write(stream->Data, ptr, size, &stream->Status); + size_t writtenBytes = stream->Interface.Write(stream->Data, inBuffer, &stream->Status); if ((writtenBytes == 0) && (stream->Status == IOStreamStatus::Ready)) { stream->Status = IOStreamStatus::Error; @@ -131,23 +132,32 @@ namespace Juliet return stream->Interface.Size(stream->Data); } - uint8* LoadFile(String filename, size_t& outByteRead) + ByteBuffer LoadFile(String filename) { - IOStream* stream = IOFromFile(filename, ConstString("rb")); + IOStream* stream = IOFromFile(filename, WrapString("rb")); if (!stream) { - outByteRead = 0; - return nullptr; + return {}; } - return LoadFile(stream, outByteRead, true); + return LoadFile(stream, true); } - uint8* LoadFile(NonNullPtr stream, size_t& outByteRead, bool closeStreamWhenDone) + ByteBuffer LoadFile(NonNullPtr stream, bool closeStreamWhenDone) { constexpr size_t kFileChunkSize = 1024; uint8* data = nullptr; uint8* newData = nullptr; size_t totalSize = 0; + ByteBuffer resultBuffer = {}; + + auto deferred = Defer( + [&]() + { + if (closeStreamWhenDone) + { + IOClose(stream); + } + }); // Try reading the size from the stream, if failing we'll try to read it chunk by chunk bool loadChunks = false; @@ -160,7 +170,7 @@ namespace Juliet data = static_cast(Malloc(static_cast(size + 1))); if (!data) { - goto done; + return {}; } while (true) @@ -174,8 +184,7 @@ namespace Juliet if (!newData) { Free(data); - data = nullptr; - goto done; + return {}; } data = newData; } @@ -201,14 +210,10 @@ namespace Juliet // Adding null terminator data[totalSize] = '\0'; - done: - outByteRead = totalSize; + resultBuffer.Data = reinterpret_cast(data); + resultBuffer.Size = totalSize; - if (closeStreamWhenDone) - { - IOClose(stream); - } - return data; + return resultBuffer; } bool IOClose(NonNullPtr stream) diff --git a/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp b/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp index b149be4..f9b6c47 100644 --- a/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp +++ b/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp @@ -131,7 +131,7 @@ namespace Juliet::Internal return totalRead; } - size_t FileWrite(NonNullPtr payload, const void* inBuffer, size_t size, NonNullPtr status) + size_t FileWrite(NonNullPtr payload, ByteBuffer inBuffer, NonNullPtr status) { auto win32Payload = static_cast(payload.Get()); DWORD bytes; @@ -158,12 +158,12 @@ namespace Juliet::Internal } } - if (!WriteFile(win32Payload->Handle, inBuffer, static_cast(size), &bytes, nullptr)) + if (!WriteFile(win32Payload->Handle, inBuffer.Data, static_cast(inBuffer.Size), &bytes, nullptr)) { Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error writing to datastream: %d", GetLastError()); return 0; } - if (bytes == 0 && size > 0) + if (bytes == 0 && inBuffer.Size > 0) { *status = IOStreamStatus::NotReady; } @@ -199,18 +199,17 @@ namespace Juliet::Internal #if _DEBUG // Making sure the mode is valid - String modeView = mode; - size_t modeLength = StringLength(modeView); + size_t modeLength = StringLength(mode); Assert((modeLength <= 2) && "Mode should have at most 2 characters, one being either r,w or a and the other can only be +"); if (modeLength == 1) { - Assert((modeView.Data[0] == 'r' || modeView.Data[0] == 'w' || modeView.Data[0] == 'a') && + Assert((mode.Data[0] == 'r' || mode.Data[0] == 'w' || mode.Data[0] == 'a') && "Invalid Mode. First char is not r,w or a"); } else { - Assert((modeView.Data[1] == '+' || modeView.Data[1] == 'b') && "Invalid Mode. Second char is not +"); + Assert((mode.Data[1] == '+' || mode.Data[1] == 'b') && "Invalid Mode. Second char is not +"); } #endif @@ -229,11 +228,12 @@ namespace Juliet::Internal return nullptr; } + // TODO: Detect when folder is missing and create it if in w or a mode. HANDLE hFile = CreateFileA(CStr(filename), (canWrite | canRead), (canWrite) ? 0 : FILE_SHARE_READ, nullptr, (openExisting | createAlways | openAlways), FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) { - Log(LogLevel::Error, LogCategory::Core, "IOFromFile: couldn't open %s", CStr(filename)); + Log(LogLevel::Error, LogCategory::Core, "IOFromFile: couldn't open %s. Error: %d", CStr(filename), GetLastError()); return nullptr; } diff --git a/Juliet/src/Core/Logging/LogManager.cpp b/Juliet/src/Core/Logging/LogManager.cpp index b394c36..b44f59d 100644 --- a/Juliet/src/Core/Logging/LogManager.cpp +++ b/Juliet/src/Core/Logging/LogManager.cpp @@ -87,15 +87,12 @@ namespace Juliet LogManagerSingleton.Shutdown(); } - void Log(LogLevel level, LogCategory category, const char* fmt, ...) + void Log(LogLevel level, LogCategory category, const char* fmt, va_list args) { // TODO : Revisit, copy from https://github.com/Eclmist/Ether/blob/develop/src/common/logging/loggingmanager.cpp char formattedBuffer[4096]; - va_list args; - va_start(args, fmt); (void)vsprintf_s(formattedBuffer, fmt, args); // Cast to void to ignore the return type. TODO : Juliet format function - va_end(args); std::string formattedText(formattedBuffer); @@ -114,4 +111,35 @@ namespace Juliet } } + void Log(LogLevel level, LogCategory category, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + Log(level, category, fmt, args); + va_end(args); + } + + void LogMessage(LogCategory category, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + Log(LogLevel::Message, category, fmt, args); + va_end(args); + } + + void LogWarning(LogCategory category, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + Log(LogLevel::Warning, category, fmt, args); + va_end(args); + } + + void LogError(LogCategory category, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + Log(LogLevel::Error, category, fmt, args); + va_end(args); + } } // namespace Juliet diff --git a/Juliet/src/Core/Networking/NetworkPacket.cpp b/Juliet/src/Core/Networking/NetworkPacket.cpp index 7bac2f3..b032c85 100644 --- a/Juliet/src/Core/Networking/NetworkPacket.cpp +++ b/Juliet/src/Core/Networking/NetworkPacket.cpp @@ -3,7 +3,7 @@ #include #include -#define TO_BUFFER(ptr) reinterpret_cast(ptr) +#define TO_BUFFER(ptr) reinterpret_cast(ptr) namespace Juliet { @@ -29,12 +29,12 @@ namespace Juliet // Begin - Pack NetworkPacket& NetworkPacket::operator<<(uint32 value) { - const uint32 toWrite = htonl(value); + uint32 toWrite = htonl(value); Append({ TO_BUFFER(&toWrite), sizeof(toWrite) }); return *this; } - NetworkPacket& NetworkPacket::operator<<(const char* data) + NetworkPacket& NetworkPacket::operator<<(char* data) { Assert(data && "NetworkPacket::operator<< Data must not be null"); diff --git a/Juliet/src/Graphics/D3D12/D3D12Common.cpp b/Juliet/src/Graphics/D3D12/D3D12Common.cpp index 9e8ddd2..eaeaaf3 100644 --- a/Juliet/src/Graphics/D3D12/D3D12Common.cpp +++ b/Juliet/src/Graphics/D3D12/D3D12Common.cpp @@ -1,6 +1,8 @@ + #include #include +#include #include #include #include diff --git a/JulietApp/main.cpp b/JulietApp/main.cpp index ff2a678..12d06d1 100644 --- a/JulietApp/main.cpp +++ b/JulietApp/main.cpp @@ -44,9 +44,9 @@ namespace void JulietApplication::Init() { - Log(LogLevel::Message, LogCategory::Editor, "Initializing Juliet Application..."); + Log(LogLevel::Message, LogCategory::Tool, "Initializing Juliet Application..."); - Log(LogLevel::Message, LogCategory::Editor, "%s", CStr(GetBasePath())); + Log(LogLevel::Message, LogCategory::Tool, "%s", CStr(GetBasePath())); GraphicsConfig config; GraphicsDevice = CreateGraphicsDevice(config); @@ -72,7 +72,7 @@ void JulietApplication::Init() void JulietApplication::Shutdown() { - Log(LogLevel::Message, LogCategory::Editor, "Shutting down Juliet Application..."); + Log(LogLevel::Message, LogCategory::Tool, "Shutting down Juliet Application..."); Game.Shutdown(); ShutdownHotReloadCode(GameCode); @@ -92,7 +92,7 @@ void JulietApplication::Shutdown() DestroyGraphicsDevice(GraphicsDevice); } - Log(LogLevel::Message, LogCategory::Editor, "Juliet App shutdown Completed"); + Log(LogLevel::Message, LogCategory::Tool, "Juliet App shutdown Completed"); } void JulietApplication::Update() @@ -121,7 +121,7 @@ void JulietApplication::Update() CommandList* cmdList = AcquireCommandList(GraphicsDevice, QueueType::Graphics); if (cmdList == nullptr) { - Log(LogLevel::Error, LogCategory::Editor, "Failed to acquire command list."); + Log(LogLevel::Error, LogCategory::Tool, "Failed to acquire command list."); Running = false; return; } @@ -129,7 +129,7 @@ void JulietApplication::Update() Texture* swapChainTexture = nullptr; if (!AcquireSwapChainTexture(cmdList, MainWindow, &swapChainTexture)) { - Log(LogLevel::Error, LogCategory::Editor, "Failed to acquire swapchain texture."); + Log(LogLevel::Error, LogCategory::Tool, "Failed to acquire swapchain texture."); Running = false; return; } diff --git a/JulietShaderCompiler/DXCompiler.h b/JulietShaderCompiler/DXShaderCompiler/DXCompiler.h similarity index 100% rename from JulietShaderCompiler/DXCompiler.h rename to JulietShaderCompiler/DXShaderCompiler/DXCompiler.h diff --git a/JulietShaderCompiler/JulietShaderCompiler.vcxproj b/JulietShaderCompiler/JulietShaderCompiler.vcxproj index 24080bb..be69e12 100644 --- a/JulietShaderCompiler/JulietShaderCompiler.vcxproj +++ b/JulietShaderCompiler/JulietShaderCompiler.vcxproj @@ -99,9 +99,7 @@ - - - + @@ -111,6 +109,10 @@ PreserveNewest + + + + diff --git a/JulietShaderCompiler/ShaderCompiler.cpp b/JulietShaderCompiler/ShaderCompiler.cpp new file mode 100644 index 0000000..deebc05 --- /dev/null +++ b/JulietShaderCompiler/ShaderCompiler.cpp @@ -0,0 +1,199 @@ +#include + +#include +#include +#include +#include + +#pragma comment(lib, "dxcompiler.lib") + +using namespace Juliet; + +namespace +{ + ByteBuffer CompileWithDXC(HLSLShaderInfo& info, bool compileToSpirv) + { + Assert(!compileToSpirv && "SPIRV is not supported yet"); + + IDxcCompiler3* compiler = nullptr; + DxcCreateInstance(&CLSID_DxcCompiler, IID_IDxcCompiler3, reinterpret_cast(&compiler)); + + IDxcUtils* utils = nullptr; + DxcCreateInstance(&CLSID_DxcUtils, &IID_IDxcUtils, reinterpret_cast(&utils)); + + if (compiler == nullptr) + { + LogError(LogCategory::Tool, "Cannot create DXCompiler instance"); + return {}; + } + + IDxcResult* dxcResult = nullptr; + LPCWSTR* args = nullptr; + auto deferred = Defer( + [&]() + { + SafeFree(args); + if (dxcResult) + { + dxcResult->lpVtbl->Release(dxcResult); + } + if (compiler) + { + compiler->lpVtbl->Release(compiler); + } + if (utils) + { + utils->lpVtbl->Release(utils); + } + }); + + if (utils == nullptr) + { + LogError(LogCategory::Graphics, "Cannot create IDxcUtils instance"); + return {}; + } + + IDxcIncludeHandler* includeHandler; + utils->lpVtbl->CreateDefaultIncludeHandler(utils, &includeHandler); + if (includeHandler == nullptr) + { + LogError(LogCategory::Graphics, "Cannot create Default include handler"); + return {}; + } + + StringBuffer convertedBuffer = {}; + char inplaceBuffer[1024]; + convertedBuffer.Data = inplaceBuffer; + convertedBuffer.Capacity = sizeof(inplaceBuffer); + if (!ConvertString(StringEncoding::UTF8, StringEncoding::UTF16, info.EntryPoint, convertedBuffer)) + { + LogError(LogCategory::Tool, "Cannot convert entry point string to utf16"); + return {}; + } + + // Creating args for the compiler + // Allocating enough to fit them all + static constexpr size_t kExpectedArgCount = 32; + uint32 argCount = 0; + args = static_cast(Calloc(kExpectedArgCount, sizeof(LPCWSTR))); + if (!args) + { + LogError(LogCategory::Tool, "Couldn't allocate Args array"); + return {}; + } + + args[argCount++] = const_cast(L"-E"); + args[argCount++] = reinterpret_cast(inplaceBuffer); + + DxcBuffer source; + source.Ptr = info.ByteCodeBuffer.Data; + source.Size = info.ByteCodeBuffer.Size; + source.Encoding = DXC_CP_ACP; + + switch (info.Stage) + { + case ShaderStage::Vertex: + { + args[argCount++] = const_cast(L"-T"); + args[argCount++] = const_cast(L"vs_6_0"); + break; + } + case ShaderStage::Fragment: + { + args[argCount++] = const_cast(L"-T"); + args[argCount++] = const_cast(L"ps_6_0"); + break; + } + case ShaderStage::Compute: + { + args[argCount++] = const_cast(L"-T"); + args[argCount++] = const_cast(L"cs_6_0"); + break; + } + case ShaderStage::Invalid: + { + Assert(false && "Invalid shader stage"); + return {}; + } + } + + if (compileToSpirv) + { + args[argCount++] = const_cast(L"-spirv"); + args[argCount++] = const_cast(L"-fspv-flatten-resource-arrays"); + } + + char nameInplaceBuffer[1024]; + if (IsValid(info.Name)) + { + StringBuffer nameUtf16 = {}; + nameUtf16.Data = nameInplaceBuffer; + nameUtf16.Capacity = sizeof(nameInplaceBuffer); + if (ConvertString(StringEncoding::UTF8, StringEncoding::UTF16, info.Name, nameUtf16)) + { + args[argCount++] = reinterpret_cast(nameInplaceBuffer); + } + } + + HRESULT result = compiler->lpVtbl->Compile(compiler, &source, args, argCount, includeHandler, IID_IDxcResult, + reinterpret_cast(&dxcResult)); + if (result < 0) + { + LogError(LogCategory::Tool, "IDxcShaderCompiler3::Compile failed: %X", result); + return {}; + } + + if (dxcResult == nullptr) + { + LogError(LogCategory::Tool, "%s", "HLSL compilation failed with no IDxcResult"); + return {}; + } + + IDxcBlob* blob = nullptr; + IDxcBlobUtf8* errors = nullptr; + result = dxcResult->lpVtbl->GetOutput(dxcResult, DXC_OUT_OBJECT, IID_IDxcBlob, reinterpret_cast(&blob), nullptr); + if (result < 0) + { + // Compilation failed, display errors + dxcResult->lpVtbl->GetOutput(dxcResult, DXC_OUT_ERRORS, IID_IDxcBlobUtf8, reinterpret_cast(&errors), nullptr); + if (errors != nullptr && errors->lpVtbl->GetBufferSize(errors) != 0) + { + LogError(LogCategory::Tool, "HLSL compilation failed: %s", + static_cast(errors->lpVtbl->GetBufferPointer(errors))); + } + else + { + LogError(LogCategory::Tool, "Compilation failed with unknown error"); + } + + return {}; + } + + // If compilation succeeded, but there are errors, those are warnings + dxcResult->lpVtbl->GetOutput(dxcResult, DXC_OUT_ERRORS, IID_IDxcBlobUtf8, reinterpret_cast(&errors), nullptr); + if (errors != nullptr && errors->lpVtbl->GetBufferSize(errors) != 0) + { + LogWarning(LogCategory::Tool, "HLSL compiled with warnings: %s", + static_cast(errors->lpVtbl->GetBufferPointer(errors))); + } + + // TODO: Scratch allocator. No malloc + ByteBuffer buffer = {}; + buffer.Size = blob->lpVtbl->GetBufferSize(blob); + buffer.Data = static_cast(Malloc(buffer.Size)); + MemCopy(buffer.Data, blob->lpVtbl->GetBufferPointer(blob), buffer.Size); + + blob->lpVtbl->Release(blob); + + return buffer; + } +} // namespace + +ByteBuffer CompileDXIL(HLSLShaderInfo& info) +{ + // TODO: Use SPIRV an do hlsl -> spirv -> hlsl -> dxil. + // This is from Shadercross SDL_ShaderCross_CompileDXILFromHLSL. + // Not sure why yet. + + return CompileWithDXC(info, false); +} diff --git a/JulietShaderCompiler/ShaderCompiler.h b/JulietShaderCompiler/ShaderCompiler.h new file mode 100644 index 0000000..e83cf27 --- /dev/null +++ b/JulietShaderCompiler/ShaderCompiler.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +enum class SourceLanguage : uint8 +{ + HLSL = 0, + Count = 1, + Invalid = Count +}; + +enum class ShaderFormat : uint8 +{ + DXIL = 0, + Count = 1, + Invalid = Count +}; + +enum class ShaderStage : uint8 +{ + Vertex = 0, + Fragment = 1, + Compute = 2, + Count = 3, + Invalid = Count +}; + +struct ShaderInfo +{ + ByteBuffer ByteCodeBuffer; + Juliet::String EntryPoint; + Juliet::String Name; + ShaderStage Stage; +}; + +struct HLSLShaderInfo : ShaderInfo +{ + // TODO Defines and includes +}; + +ByteBuffer CompileDXIL(HLSLShaderInfo& info); \ No newline at end of file diff --git a/JulietShaderCompiler/main.cpp b/JulietShaderCompiler/main.cpp index b6b4885..f12f954 100644 --- a/JulietShaderCompiler/main.cpp +++ b/JulietShaderCompiler/main.cpp @@ -1,56 +1,229 @@ +#include #include #include #include #include - -// Must be before everything else. -// Cannot include DX12Includes because we redefine everything. -// TODO : Separate lib -// TODO : Only when not shipping -// Check if we just load the dll and not link against? -#include - -#pragma comment(lib, "dxcompiler.lib") +#include +#include using namespace Juliet; -void Compile() +void PrintHelp() { - IDxcCompiler3* compiler = nullptr; - DxcCreateInstance(&CLSID_DxcCompiler, IID_IDxcCompiler3, reinterpret_cast(&compiler)); - - IDxcUtils* utils = nullptr; - DxcCreateInstance(&CLSID_DxcUtils, &IID_IDxcUtils, reinterpret_cast(&utils)); - - if (compiler == nullptr) - { - Juliet::Log(LogLevel::Error, LogCategory::Graphics, "Cannot create DXCompiler instance"); - } - - if (utils == nullptr) - { - Log(LogLevel::Error, LogCategory::Graphics, "Cannot create IDxcUtils instance"); - compiler->lpVtbl->Release(compiler); - } - - IDxcIncludeHandler* includeHandler; - utils->lpVtbl->CreateDefaultIncludeHandler(utils, &includeHandler); - if (includeHandler == nullptr) - { - compiler->lpVtbl->Release(compiler); - utils->lpVtbl->Release(utils); - } + LogMessage(LogCategory::Tool, "Usage: JulietShaderCompiler.exe [options]"); + LogMessage(LogCategory::Tool, "Required Parameters:"); + static constexpr int kColumnWdith = 32; + LogMessage(LogCategory::Tool, " %-*s %s", kColumnWdith, "-o | --output ", "Output file."); + // LogMessage(LogCategory::Tool, "\n"); + // LogMessage(LogCategory::Tool, "Required Parameters that can be inferred from filename:"); + // LogMessage(LogCategory::Tool, " %-*s %s", kColumnWdith, "-s | --src ", "Source Language. Values: [HLSL*] | * Default"); + // LogMessage(LogCategory::Tool, " %-*s %s", kColumnWdith, "-d | --dest ", "Destination Format. Values: [DXIL*] | * Default"); } int main(int argc, char* argv[]) { - // auto* stream = IOFromFile(ConstString("XF"), ConstString("w")); + String filename = {}; + ByteBuffer fileData = {}; + + String outputFilename = {}; + + String entryPoint = WrapString("main"); + + bool sourceLangDetected = false; + auto srcLanguage = SourceLanguage::Invalid; + + bool dstShaderFormatDetected = false; + auto dstShaderFormat = ShaderFormat::Invalid; + + bool shaderStageDetected = false; + auto shaderStage = ShaderStage::Invalid; + + IOStream* outStream = nullptr; + + auto deferred = Defer( + [&]() + { + if (IsValid(fileData)) + { + Free(fileData); + } + if (outStream) + { + IOClose(outStream); + } + // Pause here to not close the console window immediately on stop + system("PAUSE"); + }); + + for (int idx = 1; idx < argc; ++idx) + { + String arg = WrapString(argv[idx]); + if (CStr(arg)[0] == '-') + { + if (StringCompareCaseInsensitive(arg, ConstString("-h")) == 0 || + StringCompareCaseInsensitive(arg, ConstString("--help")) == 0) + { + PrintHelp(); + return 1; + } + + if (StringCompareCaseInsensitive(arg, ConstString("-o")) == 0 || + StringCompareCaseInsensitive(arg, ConstString("--output")) == 0) + { + if (idx + 1 >= argc) + { + LogError(LogCategory::Tool, "%s requires an argument", argv); + PrintHelp(); + return 1; + } + idx += 1; + outputFilename = WrapString(argv[idx]); + } + } + else if (IsValid(filename) == false) + { + filename = arg; + } + else + { + LogError(LogCategory::Tool, "%s: Unknown parameter: %s", argv[0], CStr(arg)); + PrintHelp(); + return 1; + } + } + + if (!IsValid(filename)) + { + LogError(LogCategory::Tool, "%s: missing input filename path", argv[0]); + PrintHelp(); + return 1; + } + if (!IsValid(outputFilename)) + { + LogError(LogCategory::Tool, "%s: missing output path", argv[0]); + PrintHelp(); + return 1; + } + + fileData = LoadFile(filename); + if (!IsValid(fileData)) + { + LogError(LogCategory::Tool, "Invalid file: %s", CStr(filename)); + return 1; + } + + // Find the source language from filename + { + if (!sourceLangDetected) + { + if (IsValid(FindString(filename, WrapString("hlsl")))) + { + srcLanguage = SourceLanguage::HLSL; + } + static_assert(ToUnderlying(SourceLanguage::Count) == 1, "SourceLanguage must be one of: HLSL"); + } + + if (srcLanguage == SourceLanguage::Invalid) + { + LogError(LogCategory::Tool, + "%s: Invalid source language. Please either have a valid file extension or use -s/--src", argv[0]); + PrintHelp(); + return 1; + } + } + + // Find the destination format from output file name + { + if (!dstShaderFormatDetected) + { + if (IsValid(FindString(outputFilename, WrapString("dxil")))) + { + dstShaderFormat = ShaderFormat::DXIL; + } + static_assert(ToUnderlying(ShaderFormat::Count) == 1, "ShaderFormat must be one of: DXIL"); + + if (dstShaderFormat == ShaderFormat::Invalid) + { + LogError(LogCategory::Tool, + "%s: Invalid shader format. Please either have a valid file extension or use -d/--dest", argv[0]); + PrintHelp(); + return 1; + } + } + } + + // Find shader stage from filename + { + if (!shaderStageDetected) + { + // TODO Need a case insensitive FindString + if (IsValid(FindString(filename, WrapString("vert")))) + { + shaderStage = ShaderStage::Vertex; + } + else if (IsValid(FindString(filename, WrapString("frag")))) + { + shaderStage = ShaderStage::Fragment; + } + else if (IsValid(FindString(filename, WrapString("comp")))) + { + shaderStage = ShaderStage::Compute; + } + static_assert(ToUnderlying(ShaderFormat::Count) == 1, "ShaderStage must be one of: vert, frag, comp"); + + if (shaderStage == ShaderStage::Invalid) + { + LogError(LogCategory::Tool, "%s: Invalid shader stage. Please include it in the filename", argv[0]); + PrintHelp(); + return 1; + } + } + } + + outStream = IOFromFile(outputFilename, WrapString("w")); + if (!outStream) + { + LogError(LogCategory::Tool, "%s: Failed to open %s for writing", argv[0], CStr(outputFilename)); + return 1; + } + + switch (srcLanguage) + { + case SourceLanguage::HLSL: + { + HLSLShaderInfo hlslInfo = {}; + hlslInfo.ByteCodeBuffer = fileData; + hlslInfo.EntryPoint = entryPoint; + hlslInfo.Name = filename; + hlslInfo.Stage = shaderStage; + + switch (dstShaderFormat) + { + case ShaderFormat::DXIL: + { + ByteBuffer compiledBuffer = CompileDXIL(hlslInfo); + if (!IsValid(compiledBuffer)) + { + LogError(LogCategory::Tool, "Failed to compile DXBC from HLSL. Error: %s", "Unknown"); + } + else + { + IOWrite(outStream, compiledBuffer); + Free(compiledBuffer); + } + break; + } + case ShaderFormat::Invalid: Assert(false && "Shader format is invalid"); return 1; + } + + break; + } + case SourceLanguage::Invalid: Assert(false && "Source Language is invalid"); return 1; + } + // // IOPrintf(stream, ":)"); // IOPrintf(stream, "%d%s", 1234, "Hello World!"); - size_t bytesRead = 0; - auto* data = LoadFile(ConstString("XF"),bytesRead ); - return 0; }