#include "hecl/Backend/GLSL.hpp" #include "hecl/Runtime.hpp" #include #include #include #include static logvisor::Module Log("hecl::Backend::GLSL"); namespace hecl::Backend { std::string GLSL::EmitTexGenSource2(TexGenSrc src, int uvIdx) const { switch (src) { case TexGenSrc::Position: return "vtf.mvPos.xy"; case TexGenSrc::Normal: return "vtf.mvNorm.xy"; case TexGenSrc::UV: return hecl::Format("uvIn[%u]", uvIdx); default: break; } return std::string(); } std::string GLSL::EmitTexGenSource4(TexGenSrc src, int uvIdx) const { switch (src) { case TexGenSrc::Position: return "vec4(vtf.mvPos.xyz, 1.0)"; case TexGenSrc::Normal: return "vec4(vtf.mvNorm.xyz, 1.0)"; case TexGenSrc::UV: return hecl::Format("vec4(uvIn[%u], 0.0, 1.0)", uvIdx); default: break; } return std::string(); } std::string GLSL::GenerateVertInStruct(unsigned col, unsigned uv, unsigned w) const { std::string retval = "layout(location=0) in vec3 posIn;\n" "layout(location=1) in vec3 normIn;\n"; unsigned idx = 2; if (col) { retval += hecl::Format("layout(location=%u) in vec4 colIn[%u];\n", idx, col); idx += col; } if (uv) { retval += hecl::Format("layout(location=%u) in vec2 uvIn[%u];\n", idx, uv); idx += uv; } if (w) { retval += hecl::Format("layout(location=%u) in vec4 weightIn[%u];\n", idx, w); } return retval; } std::string GLSL::GenerateVertToFragStruct(size_t extTexCount, bool reflectionCoords) const { std::string retval = "struct VertToFrag\n" "{\n" " vec4 mvPos;\n" " vec4 mvNorm;\n"; if (m_tcgs.size()) retval += hecl::Format(" vec2 tcgs[%u];\n", unsigned(m_tcgs.size())); if (extTexCount) retval += hecl::Format(" vec2 extTcgs[%u];\n", unsigned(extTexCount)); if (reflectionCoords) retval += " vec2 reflectTcgs[2];\n" " float reflectAlpha;\n"; return retval + "};\n"; } std::string GLSL::GenerateVertUniformStruct(unsigned skinSlots, bool reflectionCoords) const { if (skinSlots == 0) skinSlots = 1; std::string retval = hecl::Format("UBINDING0 uniform HECLVertUniform\n" "{\n" " mat4 mv[%u];\n" " mat4 mvInv[%u];\n" " mat4 proj;\n" "};\n", skinSlots, skinSlots); retval += "struct HECLTCGMatrix\n" "{\n" " mat4 mtx;\n" " mat4 postMtx;\n" "};\n" "UBINDING1 uniform HECLTexMtxUniform\n" "{\n" " HECLTCGMatrix texMtxs[8];\n" "};\n"; if (reflectionCoords) retval += "UBINDING3 uniform HECLReflectMtx\n" "{\n" " mat4 indMtx;\n" " mat4 reflectMtx;\n" " float reflectAlpha;\n" "};\n" "\n"; return retval; } std::string GLSL::GenerateAlphaTest() const { return " if (colorOut.a < 0.01)\n" " {\n" " discard;\n" " }\n"; } std::string GLSL::GenerateReflectionExpr(ReflectionType type) const { switch (type) { case ReflectionType::None: default: return "vec3(0.0, 0.0, 0.0)"; case ReflectionType::Simple: return "texture(reflectionTex, vtf.reflectTcgs[1]).rgb * vtf.reflectAlpha"; case ReflectionType::Indirect: return "texture(reflectionTex, (texture(reflectionIndTex, vtf.reflectTcgs[0]).rg - " "vec2(0.5, 0.5)) * vec2(0.5, 0.5) + vtf.reflectTcgs[1]).rgb * vtf.reflectAlpha"; } } void GLSL::reset(const IR& ir, Diagnostics& diag) { /* Common programmable interpretation */ ProgrammableCommon::reset(ir, diag, "GLSL"); } std::string GLSL::makeVert(const char* glslVer, unsigned col, unsigned uv, unsigned w, unsigned s, size_t extTexCount, const TextureInfo* extTexs, ReflectionType reflectionType) const { extTexCount = std::min(int(extTexCount), BOO_GLSL_MAX_TEXTURE_COUNT - int(m_tcgs.size())); std::string retval = std::string(glslVer) + "\n" BOO_GLSL_BINDING_HEAD + GenerateVertInStruct(col, uv, w) + "\n" + GenerateVertToFragStruct(extTexCount, reflectionType != ReflectionType::None) + "\n" + GenerateVertUniformStruct(s, reflectionType != ReflectionType::None) + "SBINDING(0) out VertToFrag vtf;\n\n" "void main()\n{\n"; if (s) { /* skinned */ 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& objOut) { m_backend.reset(ir, diag); size_t cachedSz = 3; std::string vertSource = m_backend.makeVert("#version 330", tag.getColorCount(), tag.getUvCount(), tag.getWeightCount(), tag.getSkinSlotCount(), 0, nullptr, tag.getReflectionType()); cachedSz += vertSource.size() + 1; std::string fragSource = m_backend.makeFrag("#version 330", tag.getDepthWrite() && m_backend.m_blendDst == hecl::Backend::BlendFactor::InvSrcAlpha, tag.getReflectionType()); cachedSz += fragSource.size() + 1; if (m_backend.m_texMapEnd > 8) Log.report(logvisor::Fatal, "maximum of 8 texture maps supported"); objOut = static_cast(ctx). newShaderPipeline(vertSource.c_str(), fragSource.c_str(), m_backend.m_texMapEnd, STD_TEXNAMES, 2, STD_BLOCKNAMES, boo::BlendFactor(m_backend.m_blendSrc), boo::BlendFactor(m_backend.m_blendDst), tag.getPrimType(), tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None, tag.getDepthWrite(), true, false, tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None); if (!objOut) Log.report(logvisor::Fatal, "unable to build shader"); ShaderCachedData dataOut(tag, cachedSz); athena::io::MemoryWriter w(dataOut.m_data.get(), dataOut.m_sz); w.writeUByte(m_backend.m_texMapEnd); w.writeUByte(atUint8(m_backend.m_blendSrc)); w.writeUByte(atUint8(m_backend.m_blendDst)); w.writeString(vertSource); w.writeString(fragSource); return dataOut; } boo::ObjToken buildShaderFromCache(const ShaderCachedData& data, boo::IGraphicsDataFactory::Context& ctx) { const ShaderTag& tag = data.m_tag; athena::io::MemoryReader r(data.m_data.get(), data.m_sz, false, false); atUint8 texMapEnd = r.readUByte(); boo::BlendFactor blendSrc = boo::BlendFactor(r.readUByte()); boo::BlendFactor blendDst = boo::BlendFactor(r.readUByte()); std::string vertSource = r.readString(); std::string fragSource = r.readString(); if (r.hasError()) return nullptr; if (texMapEnd > 8) Log.report(logvisor::Fatal, "maximum of 8 texture maps supported"); auto ret = static_cast(ctx). newShaderPipeline(vertSource.c_str(), fragSource.c_str(), texMapEnd, STD_TEXNAMES, 2, STD_BLOCKNAMES, blendSrc, blendDst, tag.getPrimType(), tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None, tag.getDepthWrite(), true, false, tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None); if (!ret) Log.report(logvisor::Fatal, "unable to build shader"); return ret; } ShaderCachedData buildExtendedShaderFromIR(const ShaderTag& tag, const hecl::Frontend::IR& ir, hecl::Frontend::Diagnostics& diag, const std::vector& extensionSlots, boo::IGraphicsDataFactory::Context& ctx, FReturnExtensionShader returnFunc) { m_backend.reset(ir, diag); size_t cachedSz = 3; if (m_backend.m_texMapEnd > 8) Log.report(logvisor::Fatal, "maximum of 8 texture maps supported"); std::vector> sources; sources.reserve(extensionSlots.size()); for (const ShaderCacheExtensions::ExtensionSlot& slot : extensionSlots) { size_t bc = 2; const char** bn = STD_BLOCKNAMES; if (slot.blockCount) { bc = slot.blockCount; bn = slot.blockNames; } sources.emplace_back(m_backend.makeVert("#version 330", tag.getColorCount(), tag.getUvCount(), tag.getWeightCount(), tag.getSkinSlotCount(), slot.texCount, slot.texs, tag.getReflectionType()), m_backend.makeFrag("#version 330", tag.getDepthWrite() && m_backend.m_blendDst == hecl::Backend::BlendFactor::InvSrcAlpha, tag.getReflectionType(), slot.lighting, slot.post, slot.texCount, slot.texs)); cachedSz += sources.back().first.size() + 1; cachedSz += sources.back().second.size() + 1; boo::ZTest zTest; switch (slot.depthTest) { case hecl::Backend::ZTest::Original: default: zTest = tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None; break; case hecl::Backend::ZTest::None: zTest = boo::ZTest::None; break; case hecl::Backend::ZTest::LEqual: zTest = boo::ZTest::LEqual; break; case hecl::Backend::ZTest::Greater: zTest = boo::ZTest::Greater; break; case hecl::Backend::ZTest::Equal: zTest = boo::ZTest::Equal; break; } const char* ExtTexnames[8]; for (int i=0 ; i<8 ; ++i) ExtTexnames[i] = STD_TEXNAMES[i]; for (int i=0 ; i(ctx). newShaderPipeline(sources.back().first.c_str(), sources.back().second.c_str(), 8, ExtTexnames, bc, bn, boo::BlendFactor((slot.srcFactor == hecl::Backend::BlendFactor::Original) ? m_backend.m_blendSrc : slot.srcFactor), boo::BlendFactor((slot.dstFactor == hecl::Backend::BlendFactor::Original) ? m_backend.m_blendDst : slot.dstFactor), tag.getPrimType(), zTest, slot.noDepthWrite ? false : tag.getDepthWrite(), !slot.noColorWrite, !slot.noAlphaWrite, (slot.cullMode == hecl::Backend::CullMode::Original) ? (tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None) : boo::CullMode(slot.cullMode), !slot.noAlphaOverwrite); if (!ret) Log.report(logvisor::Fatal, "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_texMapEnd); w.writeUByte(atUint8(m_backend.m_blendSrc)); w.writeUByte(atUint8(m_backend.m_blendDst)); for (const std::pair& pair : sources) { w.writeString(pair.first); w.writeString(pair.second); } return dataOut; } bool buildExtendedShaderFromCache(const ShaderCachedData& data, const std::vector& extensionSlots, boo::IGraphicsDataFactory::Context& ctx, FReturnExtensionShader returnFunc) { const ShaderTag& tag = data.m_tag; athena::io::MemoryReader r(data.m_data.get(), data.m_sz, false, false); atUint8 texMapEnd = r.readUByte(); hecl::Backend::BlendFactor blendSrc = hecl::Backend::BlendFactor(r.readUByte()); hecl::Backend::BlendFactor blendDst = hecl::Backend::BlendFactor(r.readUByte()); if (r.hasError()) return false; if (texMapEnd > 8) Log.report(logvisor::Fatal, "maximum of 8 texture maps supported"); for (const ShaderCacheExtensions::ExtensionSlot& slot : extensionSlots) { size_t bc = 2; const char** bn = STD_BLOCKNAMES; if (slot.blockCount) { bc = slot.blockCount; bn = slot.blockNames; } std::string vertSource = r.readString(); std::string fragSource = r.readString(); if (r.hasError()) return false; boo::ZTest zTest; switch (slot.depthTest) { case hecl::Backend::ZTest::Original: default: zTest = tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None; break; case hecl::Backend::ZTest::None: zTest = boo::ZTest::None; break; case hecl::Backend::ZTest::LEqual: zTest = boo::ZTest::LEqual; break; case hecl::Backend::ZTest::Greater: zTest = boo::ZTest::Greater; break; case hecl::Backend::ZTest::Equal: zTest = boo::ZTest::Equal; break; } const char* ExtTexnames[8]; for (int i=0 ; i<8 ; ++i) ExtTexnames[i] = STD_TEXNAMES[i]; for (int i=0 ; i(ctx). newShaderPipeline(vertSource.c_str(), fragSource.c_str(), 8, ExtTexnames, bc, bn, boo::BlendFactor((slot.srcFactor == hecl::Backend::BlendFactor::Original) ? blendSrc : slot.srcFactor), boo::BlendFactor((slot.dstFactor == hecl::Backend::BlendFactor::Original) ? blendDst : slot.dstFactor), tag.getPrimType(), zTest, slot.noDepthWrite ? false : tag.getDepthWrite(), !slot.noColorWrite, !slot.noAlphaWrite, (slot.cullMode == hecl::Backend::CullMode::Original) ? (tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None) : boo::CullMode(slot.cullMode), !slot.noAlphaOverwrite); if (!ret) Log.report(logvisor::Fatal, "unable to build shader"); returnFunc(ret); } return true; } }; IShaderBackendFactory* _NewGLSLBackendFactory() { return new struct GLSLBackendFactory(); } #if BOO_HAS_VULKAN struct SPIRVBackendFactory : IShaderBackendFactory { Backend::GLSL m_backend; ShaderCachedData buildShaderFromIR(const ShaderTag& tag, const hecl::Frontend::IR& ir, hecl::Frontend::Diagnostics& diag, boo::IGraphicsDataFactory::Context& ctx, boo::ObjToken& objOut) { m_backend.reset(ir, diag); std::string vertSource = m_backend.makeVert("#version 330", tag.getColorCount(), tag.getUvCount(), tag.getWeightCount(), tag.getSkinSlotCount(), 0, nullptr, tag.getReflectionType()); std::string fragSource = m_backend.makeFrag("#version 330", tag.getDepthWrite() && m_backend.m_blendDst == hecl::Backend::BlendFactor::InvSrcAlpha, tag.getReflectionType()); std::vector vertBlob; std::vector fragBlob; std::vector pipelineBlob; objOut = static_cast(ctx). newShaderPipeline(vertSource.c_str(), fragSource.c_str(), &vertBlob, &fragBlob, &pipelineBlob, tag.newVertexFormat(ctx), boo::BlendFactor(m_backend.m_blendSrc), boo::BlendFactor(m_backend.m_blendDst), tag.getPrimType(), tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None, tag.getDepthWrite(), true, false, tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None); if (!objOut) Log.report(logvisor::Fatal, "unable to build shader"); atUint32 vertSz = vertBlob.size() * sizeof(unsigned int); atUint32 fragSz = fragBlob.size() * sizeof(unsigned int); atUint32 pipelineSz = pipelineBlob.size(); size_t cachedSz = 15 + vertSz + fragSz + pipelineSz; ShaderCachedData dataOut(tag, cachedSz); athena::io::MemoryWriter w(dataOut.m_data.get(), dataOut.m_sz); w.writeUByte(atUint8(m_backend.m_texMapEnd)); w.writeUByte(atUint8(m_backend.m_blendSrc)); w.writeUByte(atUint8(m_backend.m_blendDst)); if (vertBlob.size()) { w.writeUint32Big(vertSz); w.writeUBytes((atUint8*)vertBlob.data(), vertSz); } else w.writeUint32Big(0); if (fragBlob.size()) { w.writeUint32Big(fragSz); w.writeUBytes((atUint8*)fragBlob.data(), fragSz); } else w.writeUint32Big(0); if (pipelineBlob.size()) { w.writeUint32Big(pipelineSz); w.writeUBytes((atUint8*)pipelineBlob.data(), pipelineSz); } else w.writeUint32Big(0); return dataOut; } boo::ObjToken buildShaderFromCache(const ShaderCachedData& data, boo::IGraphicsDataFactory::Context& ctx) { const ShaderTag& tag = data.m_tag; athena::io::MemoryReader r(data.m_data.get(), data.m_sz, false, false); size_t texCount = size_t(r.readByte()); boo::BlendFactor blendSrc = boo::BlendFactor(r.readUByte()); boo::BlendFactor blendDst = boo::BlendFactor(r.readUByte()); atUint32 vertSz = r.readUint32Big(); std::vector vertBlob(vertSz / sizeof(unsigned int)); if (vertSz) r.readUBytesToBuf(vertBlob.data(), vertSz); atUint32 fragSz = r.readUint32Big(); std::vector fragBlob(fragSz / sizeof(unsigned int)); if (fragSz) r.readUBytesToBuf(fragBlob.data(), fragSz); atUint32 pipelineSz = r.readUint32Big(); std::vector pipelineBlob(pipelineSz); if (pipelineSz) r.readUBytesToBuf(pipelineBlob.data(), pipelineSz); if (r.hasError()) return nullptr; boo::ObjToken ret = static_cast(ctx). newShaderPipeline(nullptr, nullptr, &vertBlob, &fragBlob, &pipelineBlob, tag.newVertexFormat(ctx), blendSrc, blendDst, tag.getPrimType(), tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None, tag.getDepthWrite(), true, false, tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None); if (!ret) Log.report(logvisor::Fatal, "unable to build shader"); return ret; } ShaderCachedData buildExtendedShaderFromIR(const ShaderTag& tag, const hecl::Frontend::IR& ir, hecl::Frontend::Diagnostics& diag, const std::vector& extensionSlots, boo::IGraphicsDataFactory::Context& ctx, FReturnExtensionShader returnFunc) { m_backend.reset(ir, diag); struct Blobs { std::vector vert; std::vector frag; std::vector pipeline; }; std::vector pipeBlobs; pipeBlobs.reserve(extensionSlots.size()); size_t cachedSz = 3 + 12 * extensionSlots.size(); for (const ShaderCacheExtensions::ExtensionSlot& slot : extensionSlots) { std::string vertSource = m_backend.makeVert("#version 330", tag.getColorCount(), tag.getUvCount(), tag.getWeightCount(), tag.getSkinSlotCount(), slot.texCount, slot.texs, tag.getReflectionType()); std::string fragSource = m_backend.makeFrag("#version 330", tag.getDepthWrite() && m_backend.m_blendDst == hecl::Backend::BlendFactor::InvSrcAlpha, tag.getReflectionType(), slot.lighting, slot.post, slot.texCount, slot.texs); pipeBlobs.emplace_back(); Blobs& pipeBlob = pipeBlobs.back(); boo::ObjToken ret = static_cast(ctx). newShaderPipeline(vertSource.c_str(), fragSource.c_str(), &pipeBlob.vert, &pipeBlob.frag, &pipeBlob.pipeline, tag.newVertexFormat(ctx), boo::BlendFactor((slot.srcFactor == hecl::Backend::BlendFactor::Original) ? m_backend.m_blendSrc : slot.srcFactor), boo::BlendFactor((slot.dstFactor == hecl::Backend::BlendFactor::Original) ? m_backend.m_blendDst : slot.dstFactor), tag.getPrimType(), tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None, slot.noDepthWrite ? false : tag.getDepthWrite(), !slot.noColorWrite, !slot.noAlphaWrite, (slot.cullMode == hecl::Backend::CullMode::Original) ? (tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None) : boo::CullMode(slot.cullMode), !slot.noAlphaOverwrite); if (!ret) Log.report(logvisor::Fatal, "unable to build shader"); cachedSz += pipeBlob.vert.size() * sizeof(unsigned int); cachedSz += pipeBlob.frag.size() * sizeof(unsigned int); cachedSz += pipeBlob.pipeline.size(); returnFunc(ret); } ShaderCachedData dataOut(tag, cachedSz); athena::io::MemoryWriter w(dataOut.m_data.get(), dataOut.m_sz); w.writeUByte(atUint8(m_backend.m_texMapEnd)); w.writeUByte(atUint8(m_backend.m_blendSrc)); w.writeUByte(atUint8(m_backend.m_blendDst)); for (const Blobs& pipeBlob : pipeBlobs) { size_t vertBlobSz = pipeBlob.vert.size() * sizeof(unsigned int); size_t fragBlobSz = pipeBlob.frag.size() * sizeof(unsigned int); size_t pipeBlobSz = pipeBlob.pipeline.size(); if (vertBlobSz) { w.writeUint32Big(vertBlobSz); w.writeUBytes((atUint8*)pipeBlob.vert.data(), vertBlobSz); } else w.writeUint32Big(0); if (fragBlobSz) { w.writeUint32Big(fragBlobSz); w.writeUBytes((atUint8*)pipeBlob.frag.data(), fragBlobSz); } else w.writeUint32Big(0); if (pipeBlobSz) { w.writeUint32Big(pipeBlobSz); w.writeUBytes((atUint8*)pipeBlob.pipeline.data(), pipeBlobSz); } else w.writeUint32Big(0); } return dataOut; } bool buildExtendedShaderFromCache(const ShaderCachedData& data, const std::vector& extensionSlots, boo::IGraphicsDataFactory::Context& ctx, FReturnExtensionShader returnFunc) { const ShaderTag& tag = data.m_tag; athena::io::MemoryReader r(data.m_data.get(), data.m_sz); size_t texCount = size_t(r.readByte()); hecl::Backend::BlendFactor blendSrc = hecl::Backend::BlendFactor(r.readUByte()); hecl::Backend::BlendFactor blendDst = hecl::Backend::BlendFactor(r.readUByte()); if (r.hasError()) return false; for (const ShaderCacheExtensions::ExtensionSlot& slot : extensionSlots) { atUint32 vertSz = r.readUint32Big(); std::vector vertBlob(vertSz / sizeof(unsigned int)); if (vertSz) r.readUBytesToBuf(vertBlob.data(), vertSz); atUint32 fragSz = r.readUint32Big(); std::vector fragBlob(fragSz / sizeof(unsigned int)); if (fragSz) r.readUBytesToBuf(fragBlob.data(), fragSz); atUint32 pipelineSz = r.readUint32Big(); std::vector pipelineBlob(pipelineSz); if (pipelineSz) r.readUBytesToBuf(pipelineBlob.data(), pipelineSz); if (r.hasError()) return false; boo::ZTest zTest; switch (slot.depthTest) { case hecl::Backend::ZTest::Original: default: zTest = tag.getDepthTest() ? boo::ZTest::LEqual : boo::ZTest::None; break; case hecl::Backend::ZTest::None: zTest = boo::ZTest::None; break; case hecl::Backend::ZTest::LEqual: zTest = boo::ZTest::LEqual; break; case hecl::Backend::ZTest::Greater: zTest = boo::ZTest::Greater; break; case hecl::Backend::ZTest::Equal: zTest = boo::ZTest::Equal; break; } boo::ObjToken ret = static_cast(ctx). newShaderPipeline(nullptr, nullptr, &vertBlob, &fragBlob, &pipelineBlob, tag.newVertexFormat(ctx), boo::BlendFactor((slot.srcFactor == hecl::Backend::BlendFactor::Original) ? blendSrc : slot.srcFactor), boo::BlendFactor((slot.dstFactor == hecl::Backend::BlendFactor::Original) ? blendDst : slot.dstFactor), tag.getPrimType(), zTest, slot.noDepthWrite ? false : tag.getDepthWrite(), !slot.noColorWrite, !slot.noAlphaWrite, (slot.cullMode == hecl::Backend::CullMode::Original) ? (tag.getBackfaceCulling() ? boo::CullMode::Backface : boo::CullMode::None) : boo::CullMode(slot.cullMode), !slot.noAlphaOverwrite); if (!ret) Log.report(logvisor::Fatal, "unable to build shader"); returnFunc(ret); } return true; } }; IShaderBackendFactory* _NewSPIRVBackendFactory() { return new struct SPIRVBackendFactory(); } #endif }