diff --git a/hecl/extern/libBoo b/hecl/extern/libBoo index b97ad76c4..62fae6004 160000 --- a/hecl/extern/libBoo +++ b/hecl/extern/libBoo @@ -1 +1 @@ -Subproject commit b97ad76c45b4965f3dd9f499e5975738714f5fa8 +Subproject commit 62fae6004226348bdf5e3b0f5401d091128ec86e diff --git a/hecl/include/HECL/Backend/Metal.hpp b/hecl/include/HECL/Backend/Metal.hpp new file mode 100644 index 000000000..6d33b6131 --- /dev/null +++ b/hecl/include/HECL/Backend/Metal.hpp @@ -0,0 +1,47 @@ +#ifndef HECLBACKEND_METAL_HPP +#define HECLBACKEND_METAL_HPP +#if __APPLE__ + +#include "ProgrammableCommon.hpp" +#include + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 +#define HECL_HAS_METAL 1 + +namespace HECL +{ +namespace Backend +{ + +struct Metal : ProgrammableCommon +{ + void reset(const IR& ir, Diagnostics& diag); + std::string makeVert(unsigned col, unsigned uv, unsigned w, + unsigned skinSlots, unsigned texMtxs) const; + std::string makeFrag(const ShaderFunction& lighting=ShaderFunction()) const; + std::string makeFrag(const ShaderFunction& lighting, + const ShaderFunction& post) const; + +private: + std::string GenerateVertInStruct(unsigned col, unsigned uv, unsigned w) const; + std::string GenerateVertToFragStruct() const; + std::string GenerateVertUniformStruct(unsigned skinSlots, unsigned texMtxs) const; + + std::string EmitVec3(const atVec4f& vec) const + { + return HECL::Format("float3(%g,%g,%g)", vec.vec[0], vec.vec[1], vec.vec[2]); + } + + std::string EmitTexGenSource2(TexGenSrc src, int uvIdx) const; + std::string EmitTexGenSource4(TexGenSrc src, int uvIdx) const; +}; + +} +} + +#else +#define HECL_HAS_METAL 0 +#endif + +#endif // __APPLE__ +#endif // HECLBACKEND_METAL_HPP diff --git a/hecl/include/HECL/Runtime.hpp b/hecl/include/HECL/Runtime.hpp index e0601da8a..84d335fec 100644 --- a/hecl/include/HECL/Runtime.hpp +++ b/hecl/include/HECL/Runtime.hpp @@ -149,6 +149,7 @@ class IShaderBackendFactory { friend class ShaderCacheManager; protected: + boo::ITextureR* m_rtHint = nullptr; using FReturnExtensionShader = std::function; virtual ShaderCachedData buildShaderFromIR(const ShaderTag& tag, const HECL::Frontend::IR& ir, @@ -205,6 +206,10 @@ public: : ShaderCacheManager(storeMgr, gfxFactory, ShaderCacheExtensions()) {} void reload(); + /* Some platforms (like Metal) require information about the render target + * for encoding the pipeline state. This must be called before building shaders */ + void setRenderTargetHint(boo::ITextureR* tex) {m_factory->m_rtHint = tex;} + boo::IShaderPipeline* buildShader(const ShaderTag& tag, const std::string& source, const std::string& diagName); boo::IShaderPipeline* buildShader(const ShaderTag& tag, const HECL::Frontend::IR& ir, diff --git a/hecl/lib/Backend/CMakeLists.txt b/hecl/lib/Backend/CMakeLists.txt index 3938d4ec8..cde5a79d7 100644 --- a/hecl/lib/Backend/CMakeLists.txt +++ b/hecl/lib/Backend/CMakeLists.txt @@ -1,5 +1,12 @@ +if(WIN32) + set(PLAT_SRCS HLSL.cpp) +endif() +if(APPLE) + set(PLAT_SRCS Metal.cpp) +endif() + add_library(HECLBackend GX.cpp ProgrammableCommon.cpp GLSL.cpp - HLSL.cpp) + ${PLAT_SRCS}) diff --git a/hecl/lib/Backend/GLSL.cpp b/hecl/lib/Backend/GLSL.cpp index faf2708eb..0d593e66f 100644 --- a/hecl/lib/Backend/GLSL.cpp +++ b/hecl/lib/Backend/GLSL.cpp @@ -94,7 +94,7 @@ std::string GLSL::GenerateVertUniformStruct(unsigned skinSlots, unsigned texMtxs skinSlots, skinSlots); if (texMtxs) retval += HECL::Format(" mat4 texMtxs[%u];\n", texMtxs); - return retval + "} vu;\n"; + return retval + "};\n"; } void GLSL::reset(const IR& ir, Diagnostics& diag) @@ -119,20 +119,20 @@ std::string GLSL::makeVert(const char* glslVer, unsigned col, unsigned uv, unsig retval += " vec4 posAccum = vec4(0.0,0.0,0.0,0.0);\n" " vec4 normAccum = vec4(0.0,0.0,0.0,0.0);\n"; for (size_t i=0 ; i +#include +#include + +static LogVisor::LogModule Log("HECL::Backend::Metal"); + +namespace HECL +{ +namespace Backend +{ + +std::string Metal::EmitTexGenSource2(TexGenSrc src, int uvIdx) const +{ + switch (src) + { + case TG_POS: + return "v.posIn.xy\n"; + case TG_NRM: + return "v.normIn.xy\n"; + case TG_UV: + return HECL::Format("v.uvIn%u", uvIdx); + default: break; + } + return std::string(); +} + +std::string Metal::EmitTexGenSource4(TexGenSrc src, int uvIdx) const +{ + switch (src) + { + case TG_POS: + return "float4(v.posIn, 1.0)\n"; + case TG_NRM: + return "float4(v.normIn, 1.0)\n"; + case TG_UV: + return HECL::Format("float4(v.uvIn%u, 0.0, 1.0)", uvIdx); + default: break; + } + return std::string(); +} + +std::string Metal::GenerateVertInStruct(unsigned col, unsigned uv, unsigned w) const +{ + std::string retval = + "struct VertData\n" + "{\n" + " float3 posIn [[ attribute(0) ]];\n" + " float3 normIn [[ attribute(1) ]];\n"; + + unsigned idx = 2; + if (col) + { + for (unsigned i=0 ; i tex%u [[ texture(%u) ]]", i, i); + } + + std::string retval = "#include \nusing namespace metal;\n" + "constexpr sampler samp(address::repeat);\n" + + GenerateVertToFragStruct() + "\n" + + lightingSrc + "\n" + + "fragment float4 fmain(VertToFrag vtf [[ stage_in ]]" + texMapDecl + ")\n{\n"; + + + if (m_lighting) + { + if (lighting.m_entry) + retval += HECL::Format(" float4 lighting = %s();\n", lighting.m_entry); + else + retval += " float4 lighting = float4(1.0,1.0,1.0,1.0);\n"; + } + + unsigned sampIdx = 0; + for (const TexSampling& sampling : m_texSamplings) + retval += HECL::Format(" float4 sampling%u = tex%u.sample(samp, vtf.tcgs%u);\n", + sampIdx++, sampling.mapIdx, sampling.tcgIdx); + + if (m_alphaExpr.size()) + retval += " return float4(" + m_colorExpr + ", " + m_alphaExpr + ");\n"; + else + retval += " return float4(" + m_colorExpr + ", 1.0);\n"; + + return retval + "}\n"; +} + +std::string Metal::makeFrag(const ShaderFunction& lighting, + const ShaderFunction& post) const +{ + std::string lightingSrc; + if (lighting.m_source) + lightingSrc = lighting.m_source; + + std::string postSrc; + if (post.m_source) + postSrc = post.m_source; + + std::string postEntry; + if (post.m_entry) + postEntry = post.m_entry; + + std::string texMapDecl; + if (m_texMapEnd) + { + for (int i=0 ; i tex%u [[ texture(%u) ]],\n", i, i); + } + + std::string retval = "#include \nusing namespace metal;\n" + "constexpr sampler samp(address::repeat);\n" + + GenerateVertToFragStruct() + "\n" + + lightingSrc + "\n" + + "fragment float4 fmain(VertToFrag vtf [[ stage_in ]],\n" + texMapDecl + ")\n{\n"; + + if (m_lighting) + { + if (lighting.m_entry) + retval += HECL::Format(" float4 lighting = %s();\n", lighting.m_entry); + else + retval += " float4 lighting = float4(1.0,1.0,1.0,1.0);\n"; + } + + unsigned sampIdx = 0; + for (const TexSampling& sampling : m_texSamplings) + retval += HECL::Format(" float4 sampling%u = tex%u.sample(samp, vtf.tcgs%u);\n", + sampIdx++, sampling.mapIdx, sampling.tcgIdx); + + if (m_alphaExpr.size()) + retval += " return " + postEntry + "(float4(" + m_colorExpr + ", " + m_alphaExpr + "));\n"; + else + retval += " return " + postEntry + "(float4(" + m_colorExpr + ", 1.0));\n"; + + return retval + "}\n"; +} + +} +namespace Runtime +{ + +struct MetalBackendFactory : IShaderBackendFactory +{ + Backend::Metal m_backend; + boo::MetalDataFactory* m_gfxFactory; + + MetalBackendFactory(boo::IGraphicsDataFactory* gfxFactory) + : m_gfxFactory(dynamic_cast(gfxFactory)) {} + + ShaderCachedData buildShaderFromIR(const ShaderTag& tag, + const HECL::Frontend::IR& ir, + HECL::Frontend::Diagnostics& diag, + boo::IShaderPipeline** objOut) + { + if (!m_rtHint) + Log.report(LogVisor::FatalError, + "ShaderCacheManager::setRenderTargetHint must be called before making metal shaders"); + + m_backend.reset(ir, diag); + size_t cachedSz = 2; + + std::string vertSource = + m_backend.makeVert(tag.getColorCount(), tag.getUvCount(), tag.getWeightCount(), + tag.getSkinSlotCount(), tag.getTexMtxCount()); + cachedSz += vertSource.size() + 1; + + std::string fragSource = m_backend.makeFrag(); + cachedSz += fragSource.size() + 1; + *objOut = + m_gfxFactory->newShaderPipeline(vertSource.c_str(), fragSource.c_str(), + tag.newVertexFormat(m_gfxFactory), m_rtHint, + m_backend.m_blendSrc, m_backend.m_blendDst, + tag.getDepthTest(), tag.getDepthWrite(), + tag.getBackfaceCulling()); + if (!*objOut) + Log.report(LogVisor::FatalError, "unable to build shader"); + + ShaderCachedData dataOut(tag, cachedSz); + Athena::io::MemoryWriter w(dataOut.m_data.get(), dataOut.m_sz); + w.writeUByte(m_backend.m_blendSrc); + w.writeUByte(m_backend.m_blendDst); + w.writeString(vertSource); + w.writeString(fragSource); + + return dataOut; + } + + boo::IShaderPipeline* buildShaderFromCache(const ShaderCachedData& data) + { + if (!m_rtHint) + Log.report(LogVisor::FatalError, + "ShaderCacheManager::setRenderTargetHint must be called before making metal shaders"); + + const ShaderTag& tag = data.m_tag; + Athena::io::MemoryReader r(data.m_data.get(), data.m_sz); + boo::BlendFactor blendSrc = boo::BlendFactor(r.readUByte()); + boo::BlendFactor blendDst = boo::BlendFactor(r.readUByte()); + std::string vertSource = r.readString(); + std::string fragSource = r.readString(); + boo::IShaderPipeline* ret = + m_gfxFactory->newShaderPipeline(vertSource.c_str(), fragSource.c_str(), + tag.newVertexFormat(m_gfxFactory), m_rtHint, + blendSrc, blendDst, + tag.getDepthTest(), tag.getDepthWrite(), + tag.getBackfaceCulling()); + if (!ret) + Log.report(LogVisor::FatalError, "unable to build shader"); + return ret; + } + + ShaderCachedData buildExtendedShaderFromIR(const ShaderTag& tag, + const HECL::Frontend::IR& ir, + HECL::Frontend::Diagnostics& diag, + const std::vector& extensionSlots, + FReturnExtensionShader returnFunc) + { + if (!m_rtHint) + Log.report(LogVisor::FatalError, + "ShaderCacheManager::setRenderTargetHint must be called before making metal shaders"); + + m_backend.reset(ir, diag); + size_t cachedSz = 2; + + std::string vertSource = + m_backend.makeVert(tag.getColorCount(), tag.getUvCount(), tag.getWeightCount(), + tag.getSkinSlotCount(), tag.getTexMtxCount()); + cachedSz += vertSource.size() + 1; + + std::vector fragSources; + fragSources.reserve(extensionSlots.size()); + for (const ShaderCacheExtensions::ExtensionSlot& slot : extensionSlots) + { + fragSources.push_back(m_backend.makeFrag(slot.lighting, slot.post)); + cachedSz += fragSources.back().size() + 1; + boo::IShaderPipeline* ret = + m_gfxFactory->newShaderPipeline(vertSource.c_str(), fragSources.back().c_str(), + tag.newVertexFormat(m_gfxFactory), m_rtHint, + m_backend.m_blendSrc, m_backend.m_blendDst, + tag.getDepthTest(), tag.getDepthWrite(), + tag.getBackfaceCulling()); + if (!ret) + Log.report(LogVisor::FatalError, "unable to build shader"); + returnFunc(ret); + } + + ShaderCachedData dataOut(tag, cachedSz); + Athena::io::MemoryWriter w(dataOut.m_data.get(), dataOut.m_sz); + w.writeUByte(m_backend.m_blendSrc); + w.writeUByte(m_backend.m_blendDst); + w.writeString(vertSource); + for (const std::string src : fragSources) + w.writeString(src); + + return dataOut; + } + + void buildExtendedShaderFromCache(const ShaderCachedData& data, + const std::vector& extensionSlots, + FReturnExtensionShader returnFunc) + { + if (!m_rtHint) + Log.report(LogVisor::FatalError, + "ShaderCacheManager::setRenderTargetHint must be called before making metal shaders"); + + const ShaderTag& tag = data.m_tag; + Athena::io::MemoryReader r(data.m_data.get(), data.m_sz); + boo::BlendFactor blendSrc = boo::BlendFactor(r.readUByte()); + boo::BlendFactor blendDst = boo::BlendFactor(r.readUByte()); + std::string vertSource = r.readString(); + for (const ShaderCacheExtensions::ExtensionSlot& slot : extensionSlots) + { + std::string fragSource = r.readString(); + boo::IShaderPipeline* ret = + m_gfxFactory->newShaderPipeline(vertSource.c_str(), fragSource.c_str(), + tag.newVertexFormat(m_gfxFactory), m_rtHint, + blendSrc, blendDst, + tag.getDepthTest(), tag.getDepthWrite(), + tag.getBackfaceCulling()); + if (!ret) + Log.report(LogVisor::FatalError, "unable to build shader"); + returnFunc(ret); + } + } +}; + +IShaderBackendFactory* _NewMetalBackendFactory(boo::IGraphicsDataFactory* gfxFactory) +{ + return new struct MetalBackendFactory(gfxFactory); +} + +} +} + +#endif diff --git a/hecl/lib/CMakeLists.txt b/hecl/lib/CMakeLists.txt index 6b80f9802..14076944c 100644 --- a/hecl/lib/CMakeLists.txt +++ b/hecl/lib/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(HECLCommon ../include/HECL/Backend/ProgrammableCommon.hpp ../include/HECL/Backend/GLSL.hpp ../include/HECL/Backend/HLSL.hpp + ../include/HECL/Backend/Metal.hpp ../include/HECL/Frontend.hpp ../include/HECL/Database.hpp ../include/HECL/Runtime.hpp diff --git a/hecl/lib/Runtime/ShaderCacheManager.cpp b/hecl/lib/Runtime/ShaderCacheManager.cpp index 386a1db72..395dd2d7e 100644 --- a/hecl/lib/Runtime/ShaderCacheManager.cpp +++ b/hecl/lib/Runtime/ShaderCacheManager.cpp @@ -6,13 +6,20 @@ #include #include "HECL/Backend/GLSL.hpp" +#if __APPLE__ +#include "HECL/Backend/Metal.hpp" +#endif namespace HECL { namespace Runtime { IShaderBackendFactory* _NewGLSLBackendFactory(boo::IGraphicsDataFactory* gfxFactory); +#if _WIN32 IShaderBackendFactory* _NewHLSLBackendFactory(boo::IGraphicsDataFactory* gfxFactory); +#elif __APPLE__ +IShaderBackendFactory* _NewMetalBackendFactory(boo::IGraphicsDataFactory* gfxFactory); +#endif static LogVisor::LogModule Log("ShaderCacheManager"); static uint64_t IDX_MAGIC = SBig(uint64_t(0xDEADFEEDC001D00D)); @@ -110,10 +117,16 @@ ShaderCacheManager::ShaderCacheManager(const FileStoreManager& storeMgr, case boo::IGraphicsDataFactory::PlatformOGL: m_factory.reset(_NewGLSLBackendFactory(gfxFactory)); break; +#if _WIN32 case boo::IGraphicsDataFactory::PlatformD3D11: case boo::IGraphicsDataFactory::PlatformD3D12: m_factory.reset(_NewHLSLBackendFactory(gfxFactory)); break; +#elif __APPLE__ && HECL_HAS_METAL + case boo::IGraphicsDataFactory::PlatformMetal: + m_factory.reset(_NewMetalBackendFactory(gfxFactory)); + break; +#endif default: Log.report(LogVisor::FatalError, _S("unsupported backend %s"), gfxFactory->platformName()); } diff --git a/hecl/test/main.cpp b/hecl/test/main.cpp index c9881bf8e..93b7ded36 100644 --- a/hecl/test/main.cpp +++ b/hecl/test/main.cpp @@ -80,6 +80,7 @@ struct HECLApplicationCallback : boo::IApplicationCallback /* HECL managers */ HECL::Runtime::FileStoreManager fileMgr(app->getUniqueName()); HECL::Runtime::ShaderCacheManager shaderMgr(fileMgr, gfxF); + shaderMgr.setRenderTargetHint(renderTex); /* Compile HECL shader */ static std::string testShader = "HECLOpaque(Texture(0, UV(0)))";