// HARFANG(R) Copyright (C) 2021 Emmanuel Julien, NWNC HARFANG. Released under GPL/LGPL/Commercial Licence, see licence.txt for details. #include "engine/render_pipeline.h" #include "engine/assets_rw_interface.h" #include "engine/file_format.h" #include "engine/meta.h" #include "foundation/file.h" #include "foundation/file_rw_interface.h" #include "foundation/format.h" #include "foundation/kv_store.h" #include "foundation/log.h" #include "foundation/matrix3.h" #include "foundation/matrix4.h" #include "foundation/matrix44.h" #include "foundation/path_tools.h" #include "foundation/projection.h" #include "foundation/time.h" #include "platform/window_system.h" #include #include #include #include #include #include #include using json = nlohmann::json; namespace bgfx { NLOHMANN_JSON_SERIALIZE_ENUM(UniformType::Enum, { {UniformType::Enum::Sampler, "sampler"}, {UniformType::Enum::End, "end"}, {UniformType::Enum::Vec4, "vec4"}, {UniformType::Enum::Mat3, "mat3"}, {UniformType::Enum::Mat4, "mat4"}, }); } // namespace bgfx namespace hg { static bool bgfx_is_up = false; static bgfx::UniformHandle u_previous_model = BGFX_INVALID_HANDLE; bgfxMatrix4 to_bgfx(const Mat44 &m) { return {m.m[0][0], m.m[1][0], m.m[2][0], m.m[3][0], m.m[0][1], m.m[1][1], m.m[2][1], m.m[3][1], m.m[0][2], m.m[1][2], m.m[2][2], m.m[3][2], m.m[0][3], m.m[1][3], m.m[2][3], m.m[3][3]}; } bgfxMatrix4 to_bgfx(const Mat4 &m) { return { m.m[0][0], m.m[1][0], m.m[2][0], 0.f, m.m[0][1], m.m[1][1], m.m[2][1], 0.f, m.m[0][2], m.m[1][2], m.m[2][2], 0.f, m.m[0][3], m.m[1][3], m.m[2][3], 1.f}; } bgfxMatrix3 to_bgfx(const Mat3 &m) { return {m.m[0][0], m.m[1][0], m.m[2][0], m.m[0][1], m.m[1][1], m.m[2][1], m.m[0][2], m.m[1][2], m.m[2][2]}; } // ViewState ComputeOrthographicViewState(const Mat4 &world, float size, float znear, float zfar, const Vec2 &aspect_ratio, const Vec2 &offset) { const bgfx::Caps *caps = bgfx::getCaps(); const auto view = InverseFast(world); const auto proj = ComputeOrthographicProjectionMatrix(znear, zfar, size, aspect_ratio, offset); const auto frustum = MakeFrustum(proj, world); return {frustum, proj, view}; } ViewState ComputePerspectiveViewState(const Mat4 &world, float fov, float znear, float zfar, const Vec2 &aspect_ratio, const Vec2 &offset) { const bgfx::Caps *caps = bgfx::getCaps(); const auto view = InverseFast(world); const auto proj = ComputePerspectiveProjectionMatrix(znear, zfar, FovToZoomFactor(fov), aspect_ratio, offset); const auto frustum = MakeFrustum(proj, world); return {frustum, proj, view}; } // Mat4 ComputeBillboardMat4(const Vec3 &pos, const ViewState &view_state, const Vec3 &scale) { return ComputeBillboardMat4(pos, Transpose(GetRotationMatrix(view_state.view)), scale); } // uint32_t ComputeSortKey(float view_depth) { view_depth += 100.f; // sort up to 100 meters behind view origin if (view_depth < 0.f) view_depth = 0.f; return uint32_t(view_depth * 1000.f); // 1mm precision } uint32_t ComputeSortKeyFromWorld(const Vec3 &T, const Mat4 &view) { return ComputeSortKey((T * view).z); } uint32_t ComputeSortKeyFromWorld(const Vec3 &T, const Mat4 &view, const Mat4 &model) { return ComputeSortKey((T * (view * model)).z); } // UniformSetValue::UniformSetValue(const UniformSetValue &v) { *this = v; } UniformSetValue &UniformSetValue::operator=(const UniformSetValue &v) { if (bgfx::isValid(uniform)) bgfx::destroy(uniform); if (bgfx::isValid(v.uniform)) { bgfx::UniformInfo info; bgfx::getUniformInfo(v.uniform, info); uniform = bgfx::createUniform(info.name, info.type, info.num); value = v.value; count = v.count; } else { uniform = BGFX_INVALID_HANDLE; value.clear(); count = 0; } return *this; } UniformSetValue::~UniformSetValue() { if (IsRenderUp() && bgfx::isValid(uniform)) bgfx::destroy(uniform); } // UniformSetTexture::UniformSetTexture(const UniformSetTexture &v) { *this = v; } UniformSetTexture &UniformSetTexture::operator=(const UniformSetTexture &v) { if (bgfx::isValid(uniform)) bgfx::destroy(uniform); if (bgfx::isValid(v.uniform)) { bgfx::UniformInfo info; bgfx::getUniformInfo(v.uniform, info); uniform = bgfx::createUniform(info.name, info.type, info.num); texture = v.texture; stage = v.stage; } else { uniform = BGFX_INVALID_HANDLE; texture = {}; stage = 0; } return *this; } UniformSetTexture::~UniformSetTexture() { if (IsRenderUp() && bgfx::isValid(uniform)) bgfx::destroy(uniform); } // void SetMaterialProgram(Material &mat, PipelineProgramRef prg) { mat.program = prg; } void SetMaterialValue(Material &mat, const char *name, float val) { const auto &i = mat.values.find(name); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Vec4; v.uniform = bgfx::createUniform(name, v.type); v.value = {val, 0, 0, 0}; v.count = 1; } else { i->second.value = {val, 0, 0, 0}; } } void SetMaterialValue(Material &mat, const char *name, const Vec2 &val) { const auto &i = mat.values.find(name); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Vec4; v.uniform = bgfx::createUniform(name, v.type); v.value = {val.x, val.y, 0, 0}; v.count = 1; } else { i->second.value = {val.x, val.y, 0, 0}; } } void SetMaterialValue(Material &mat, const char *name, const Vec3 &val) { const auto &i = mat.values.find(name); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Vec4; v.uniform = bgfx::createUniform(name, v.type); v.value = {val.x, val.y, val.z, 0}; v.count = 1; } else { i->second.value = {val.x, val.y, val.z, 0}; } } void SetMaterialValue(Material &mat, const char *name, const Vec4 &val) { const auto &i = mat.values.find(name); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Vec4; v.uniform = bgfx::createUniform(name, v.type); v.value = {val.x, val.y, val.z, val.w}; v.count = 1; } else { i->second.value = {val.x, val.y, val.z, val.w}; } } void SetMaterialValue(Material &mat, const char *name, const Mat3 &m) { const auto &i = mat.values.find(name); const auto m_ = to_bgfx(m); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Mat3; v.uniform = bgfx::createUniform(name, v.type); std::copy(std::begin(m_), std::end(m_), std::back_inserter(v.value)); v.count = 1; } else { i->second.value.clear(); std::copy(std::begin(m_), std::end(m_), std::back_inserter(i->second.value)); } } void SetMaterialValue(Material &mat, const char *name, const Mat4 &m) { const auto &i = mat.values.find(name); const auto m_ = to_bgfx(m); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Mat4; v.uniform = bgfx::createUniform(name, v.type); std::copy(std::begin(m_), std::end(m_), std::back_inserter(v.value)); v.count = 1; } else { i->second.value.clear(); std::copy(std::begin(m_), std::end(m_), std::back_inserter(i->second.value)); } } void SetMaterialValue(Material &mat, const char *name, const Mat44 &m) { const auto &i = mat.values.find(name); const auto m_ = to_bgfx(m); if (i == std::end(mat.values)) { auto &v = mat.values[name]; v.type = bgfx::UniformType::Mat4; v.uniform = bgfx::createUniform(name, v.type); std::copy(std::begin(m_), std::end(m_), std::back_inserter(v.value)); v.count = 1; } else { i->second.value.clear(); std::copy(std::begin(m_), std::end(m_), std::back_inserter(i->second.value)); } } void SetMaterialTexture(Material &mat, const char *name, TextureRef tex, uint8_t stage) { const auto &i = mat.textures.find(name); if (i == std::end(mat.textures)) { auto &v = mat.textures[name]; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Sampler); v.texture = tex; v.channel = stage; } else { auto &v = i->second; v.texture = tex; v.channel = stage; } } bool SetMaterialTextureRef(Material &mat, const char *name, TextureRef tex) { const auto &i = mat.textures.find(name); if (i == std::end(mat.textures)) return false; i->second.texture = tex; return true; } TextureRef GetMaterialTexture(Material &mat, const char *name) { if (mat.textures.find(name) != std::end(mat.textures)) return mat.textures[name].texture; return InvalidTextureRef; } std::vector GetMaterialTextures(Material &mat) { std::vector names; for (auto const &texture : mat.textures) names.push_back(texture.first); return names; } std::vector GetMaterialValues(Material &mat) { std::vector names; for (auto const &value : mat.values) names.push_back(value.first); return names; } // void GetMaterialWriteRGBA(const Material &m, bool &write_r, bool &write_g, bool &write_b, bool &write_a) { write_r = m.state.state & BGFX_STATE_WRITE_R; write_g = m.state.state & BGFX_STATE_WRITE_G; write_b = m.state.state & BGFX_STATE_WRITE_B; write_a = m.state.state & BGFX_STATE_WRITE_A; } bool GetMaterialWriteZ(const Material &m) { return m.state.state & BGFX_STATE_WRITE_Z; } // void SetMaterialWriteRGBA(Material &m, bool write_r, bool write_g, bool write_b, bool write_a) { m.state.state &= ~(BGFX_STATE_WRITE_R | BGFX_STATE_WRITE_G | BGFX_STATE_WRITE_B | BGFX_STATE_WRITE_A); m.state.state |= (write_r ? BGFX_STATE_WRITE_R : 0) | (write_g ? BGFX_STATE_WRITE_G : 0) | (write_b ? BGFX_STATE_WRITE_B : 0) | (write_a ? BGFX_STATE_WRITE_A : 0); } void SetMaterialWriteZ(Material &m, bool enable) { if (enable) m.state.state |= BGFX_STATE_WRITE_Z; else m.state.state &= ~BGFX_STATE_WRITE_Z; } // FaceCulling GetMaterialFaceCulling(const Material &mat) { const auto cull = mat.state.state & BGFX_STATE_CULL_MASK; if (cull == BGFX_STATE_CULL_CW) return FC_Clockwise; if (cull == BGFX_STATE_CULL_CCW) return FC_CounterClockwise; return FC_Disabled; } void SetMaterialFaceCulling(Material &mat, FaceCulling mode) { mat.state.state &= ~BGFX_STATE_CULL_MASK; if (mode == FC_Clockwise) mat.state.state |= BGFX_STATE_CULL_CW; else if (mode == FC_CounterClockwise) mat.state.state |= BGFX_STATE_CULL_CCW; } DepthTest GetMaterialDepthTest(const Material &mat) { const auto depth_test = mat.state.state & BGFX_STATE_DEPTH_TEST_MASK; if (depth_test == BGFX_STATE_DEPTH_TEST_LESS) return DT_Less; if (depth_test == BGFX_STATE_DEPTH_TEST_LEQUAL) return DT_LessEqual; if (depth_test == BGFX_STATE_DEPTH_TEST_EQUAL) return DT_Equal; if (depth_test == BGFX_STATE_DEPTH_TEST_GEQUAL) return DT_GreaterEqual; if (depth_test == BGFX_STATE_DEPTH_TEST_GREATER) return DT_Greater; if (depth_test == BGFX_STATE_DEPTH_TEST_NOTEQUAL) return DT_NotEqual; if (depth_test == BGFX_STATE_DEPTH_TEST_NEVER) return DT_Never; if (depth_test == BGFX_STATE_DEPTH_TEST_ALWAYS) return DT_Always; return DT_Disabled; } void SetMaterialDepthTest(Material &mat, DepthTest test) { mat.state.state &= ~BGFX_STATE_DEPTH_TEST_MASK; if (test == DT_Less) mat.state.state |= BGFX_STATE_DEPTH_TEST_LESS; else if (test == DT_LessEqual) mat.state.state |= BGFX_STATE_DEPTH_TEST_LEQUAL; else if (test == DT_Equal) mat.state.state |= BGFX_STATE_DEPTH_TEST_EQUAL; else if (test == DT_GreaterEqual) mat.state.state |= BGFX_STATE_DEPTH_TEST_GEQUAL; else if (test == DT_Greater) mat.state.state |= BGFX_STATE_DEPTH_TEST_GREATER; else if (test == DT_NotEqual) mat.state.state |= BGFX_STATE_DEPTH_TEST_NOTEQUAL; else if (test == DT_Never) mat.state.state |= BGFX_STATE_DEPTH_TEST_NEVER; else if (test == DT_Always) mat.state.state |= BGFX_STATE_DEPTH_TEST_ALWAYS; } BlendMode GetMaterialBlendMode(const Material &mat) { const auto blend_state = mat.state.state & (BGFX_STATE_BLEND_MASK | BGFX_STATE_BLEND_EQUATION_MASK); if (blend_state == BGFX_STATE_BLEND_ADD) return BM_Additive; if (blend_state == BGFX_STATE_BLEND_ALPHA) return BM_Alpha; if (blend_state == BGFX_STATE_BLEND_DARKEN) return BM_Darken; if (blend_state == BGFX_STATE_BLEND_LIGHTEN) return BM_Lighten; if (blend_state == BGFX_STATE_BLEND_MULTIPLY) return BM_Multiply; if (blend_state == BGFX_STATE_BLEND_NORMAL) return BM_Opaque; if (blend_state == BGFX_STATE_BLEND_SCREEN) return BM_Screen; if (blend_state == BGFX_STATE_BLEND_LINEAR_BURN) return BM_LinearBurn; return BM_Opaque; } void SetMaterialBlendMode(Material &mat, BlendMode mode) { mat.state.state &= ~(BGFX_STATE_BLEND_MASK | BGFX_STATE_BLEND_EQUATION_MASK); if (mode == BM_Additive) mat.state.state |= BGFX_STATE_BLEND_ADD; else if (mode == BM_Alpha) mat.state.state |= BGFX_STATE_BLEND_ALPHA; else if (mode == BM_Darken) mat.state.state |= BGFX_STATE_BLEND_DARKEN; else if (mode == BM_Lighten) mat.state.state |= BGFX_STATE_BLEND_LIGHTEN; else if (mode == BM_Multiply) mat.state.state |= BGFX_STATE_BLEND_MULTIPLY; // else if (mode == BM_Opaque) // mat.state.state |= BGFX_STATE_BLEND_NORMAL; else if (mode == BM_Screen) mat.state.state |= BGFX_STATE_BLEND_SCREEN; else if (mode == BM_LinearBurn) mat.state.state |= BGFX_STATE_BLEND_LINEAR_BURN; } bool GetMaterialSkinning(const Material &m) { return m.flags & MF_EnableSkinning; } void SetMaterialSkinning(Material &m, bool enable) { if (enable) m.flags |= MF_EnableSkinning; else m.flags &= ~MF_EnableSkinning; } // RenderState ComputeRenderState(BlendMode blend, DepthTest test, FaceCulling culling, bool write_z, bool write_r, bool write_g, bool write_b, bool write_a) { RenderState state{BGFX_STATE_MSAA, 0}; if (write_r) state.state |= BGFX_STATE_WRITE_R; if (write_g) state.state |= BGFX_STATE_WRITE_G; if (write_b) state.state |= BGFX_STATE_WRITE_B; if (write_a) state.state |= BGFX_STATE_WRITE_A; if (write_z) state.state |= BGFX_STATE_WRITE_Z; if (culling == FC_Clockwise) state.state |= BGFX_STATE_CULL_CW; else if (culling == FC_CounterClockwise) state.state |= BGFX_STATE_CULL_CCW; if (test == DT_Less) state.state |= BGFX_STATE_DEPTH_TEST_LESS; else if (test == DT_LessEqual) state.state |= BGFX_STATE_DEPTH_TEST_LEQUAL; else if (test == DT_Equal) state.state |= BGFX_STATE_DEPTH_TEST_EQUAL; else if (test == DT_GreaterEqual) state.state |= BGFX_STATE_DEPTH_TEST_GEQUAL; else if (test == DT_Greater) state.state |= BGFX_STATE_DEPTH_TEST_GREATER; else if (test == DT_NotEqual) state.state |= BGFX_STATE_DEPTH_TEST_NOTEQUAL; else if (test == DT_Never) state.state |= BGFX_STATE_DEPTH_TEST_NEVER; else if (test == DT_Always) state.state |= BGFX_STATE_DEPTH_TEST_ALWAYS; if (blend == BM_Additive) state.state |= BGFX_STATE_BLEND_ADD; else if (blend == BM_Alpha) state.state |= BGFX_STATE_BLEND_ALPHA; else if (blend == BM_Darken) state.state |= BGFX_STATE_BLEND_DARKEN; else if (blend == BM_Lighten) state.state |= BGFX_STATE_BLEND_LIGHTEN; else if (blend == BM_Multiply) state.state |= BGFX_STATE_BLEND_MULTIPLY; // else if (mode == BM_Opaque) // state.state |= BGFX_STATE_BLEND_NORMAL; else if (blend == BM_Screen) state.state |= BGFX_STATE_BLEND_SCREEN; else if (blend == BM_LinearBurn) state.state |= BGFX_STATE_BLEND_LINEAR_BURN; return state; } RenderState ComputeRenderState(BlendMode blend, bool write_z, bool write_r, bool write_g, bool write_b, bool write_a) { return ComputeRenderState(blend, DT_Less, FC_Clockwise, write_z, write_r, write_g, write_b, write_a); } // static bgfx::ShaderHandle LoadShader(const Reader &ir, const Handle &h, const char *name) { const auto data = LoadData(ir, h); if (!data.GetSize()) return BGFX_INVALID_HANDLE; const auto mem = bgfx::copy(data.GetData(), uint32_t(data.GetSize())); auto hnd = bgfx::createShader(mem); if (bgfx::isValid(hnd)) bgfx::setName(hnd, name); return hnd; } // struct ProgramComparator { bool operator()(const bgfx::ProgramHandle &a, const bgfx::ProgramHandle &b) const { return a.idx < b.idx; } }; static std::map, ProgramComparator> program_shaders; std::vector GetProgramShaders(bgfx::ProgramHandle prg_h) { return program_shaders[prg_h]; } // bgfx::ProgramHandle LoadProgram(const Reader &ir, const ReadProvider &ip, const char *vs_name, const char *fs_name) { ScopedReadHandle vs_h(ip, vs_name), fs_h(ip, fs_name); if (!ir.is_valid(vs_h)) { error(format("Vertex shader '%1' not found").arg(vs_name)); return BGFX_INVALID_HANDLE; } if (!ir.is_valid(fs_h)) { error(format("Fragment shader '%1' not found").arg(fs_name)); return BGFX_INVALID_HANDLE; } const auto vs = LoadShader(ir, vs_h, vs_name); if (!bgfx::isValid(vs)) { error(format("Failed to load vertex shader '%1'").arg(vs_name)); return BGFX_INVALID_HANDLE; } const auto fs = LoadShader(ir, fs_h, fs_name); if (!bgfx::isValid(fs)) { error(format("Failed to load fragment shader '%1'").arg(vs_name)); return BGFX_INVALID_HANDLE; } const auto prg_h = bgfx::createProgram(vs, fs, true); if (!bgfx::isValid(prg_h)) { error(format("Failed to create program from shader '%1' and '%2'").arg(vs_name).arg(fs_name)); return BGFX_INVALID_HANDLE; } program_shaders[prg_h] = {vs, fs}; // TODO make this optional? (this is only required to retrieved uniforms for editing ATM) return prg_h; } bgfx::ProgramHandle LoadProgram(const Reader &ir, const ReadProvider &ip, const char *name) { const std::string _name(name); return LoadProgram(ir, ip, (_name + ".vsb").c_str(), (_name + ".fsb").c_str()); } bgfx::ProgramHandle LoadComputeProgram(const Reader &ir, const ReadProvider &ip, const char *cs_name) { ScopedReadHandle cs_h(ip, cs_name); if (!ir.is_valid(cs_h)) { error(format("Compute shader '%1' not found").arg(cs_name)); return BGFX_INVALID_HANDLE; } const auto cs = LoadShader(ir, cs_h, nullptr); if (!bgfx::isValid(cs)) { error(format("Failed to load compute shader '%1'").arg(cs_name)); return BGFX_INVALID_HANDLE; } const auto prg_h = bgfx::createProgram(cs, BGFX_INVALID_HANDLE, true); if (!bgfx::isValid(prg_h)) { error(format("Failed to create program from shader '%1'").arg(cs_name)); return BGFX_INVALID_HANDLE; } program_shaders[prg_h] = {cs, BGFX_INVALID_HANDLE}; // TODO make this optional? (this is only required to retrieved uniforms for editing ATM) return prg_h; } // bgfx::ProgramHandle LoadProgramFromFile(const char *vs, const char *fs) { return LoadProgram(g_file_reader, g_file_read_provider, vs, fs); } bgfx::ProgramHandle LoadProgramFromAssets(const char *vs, const char *fs) { return LoadProgram(g_assets_reader, g_assets_read_provider, vs, fs); } bgfx::ProgramHandle LoadProgramFromFile(const char *path) { return LoadProgram(g_file_reader, g_file_read_provider, path); } bgfx::ProgramHandle LoadProgramFromAssets(const char *name) { return LoadProgram(g_assets_reader, g_assets_read_provider, name); } bgfx::ProgramHandle LoadComputeProgramFromFile(const char *cs_path) { return LoadComputeProgram(g_file_reader, g_file_read_provider, cs_path); } bgfx::ProgramHandle LoadComputeProgramFromAssets(const char *cs_name) { return LoadComputeProgram(g_assets_reader, g_assets_read_provider, cs_name); } // std::vector LoadPipelineProgramFeatures(const Reader &ir, const ReadProvider &ip, const char *name, bool &success) { success = false; const auto json_text = LoadString(ir, ScopedReadHandle(ip, name)); if (json_text.empty()) { if (!ends_with(name, ".hps")) warn(format("Request to load pipeline program features from a path not ending with '.hps' ('%1')").arg(name)); return {}; } json js; try { js = json::parse(json_text); } catch (...) { debug(format("Failed to parse json '%1'").arg(name)); return {}; } std::vector features; const auto i = js.find("features"); if (i != std::end(js)) { features.reserve(i->size()); for (const auto &j : *i) { if (!j.is_string()) continue; const auto feat = j.get(); if (feat == "OptionalBaseColorOpacityMap") features.push_back(OptionalBaseColorOpacityMap); else if (feat == "OptionalOcclusionRoughnessMetalnessMap") features.push_back(OptionalOcclusionRoughnessMetalnessMap); else if (feat == "OptionalDiffuseMap") features.push_back(OptionalDiffuseMap); else if (feat == "OptionalSpecularMap") features.push_back(OptionalSpecularMap); else if (feat == "OptionalLightMap") features.push_back(OptionalLightMap); else if (feat == "OptionalSelfMap") features.push_back(OptionalSelfMap); else if (feat == "OptionalOpacityMap") features.push_back(OptionalOpacityMap); else if (feat == "OptionalAmbientMap") features.push_back(OptionalAmbientMap); else if (feat == "OptionalReflectionMap") features.push_back(OptionalReflectionMap); else if (feat == "OptionalNormalMap") features.push_back(OptionalNormalMap); else if (feat == "NormalMapWorldOrTangent") features.push_back(NormalMapWorldOrTangent); else if (feat == "DiffuseUV01") features.push_back(DiffuseUV01); else if (feat == "SpecularUV01") features.push_back(SpecularUV01); else if (feat == "OptionalSkinning") features.push_back(OptionalSkinning); } } success = true; return features; } std::vector LoadPipelineProgramFeaturesFromFile(const char *path, bool &success) { try { return LoadPipelineProgramFeatures(g_file_reader, g_file_read_provider, path, success); } catch (...) { debug(format("Failed to read program features from file '%1'").arg(path)); } return {}; } std::vector LoadPipelineProgramFeaturesFromAssets(const char *name, bool &success) { try { return LoadPipelineProgramFeatures(g_assets_reader, g_assets_read_provider, name, success); } catch (...) { debug(format("Failed to read program features from asset '%1'").arg(name)); } return {}; } // bool LoadPipelineProgramUniforms(const Reader &ir, const ReadProvider &ip, const char *name, std::vector &texs, std::vector &vecs, PipelineResources &resources) { const auto json_text = LoadString(ir, ScopedReadHandle(ip, name, true)); if (json_text.empty()) return false; const auto js = json::parse(json_text); const auto i = js.find("uniforms"); if (i == std::end(js)) return true; // [EJ] if there's no uniforms declared look no further for (const auto &j : *i) { const auto uname = j["name"].get(); const auto utype = j["type"].get(); if (utype == "Vec4" || utype == "Color") { Vec4Uniform u; u.handle = bgfx::createUniform(uname.c_str(), bgfx::UniformType::Vec4); u.is_color = utype == "Color"; const auto v = j.find("value"); if (v != std::end(j)) if (v->is_array()) { int k = 0; for (auto f : *v) { u.value[k] = f.get(); if (++k == 4) break; } } vecs.push_back(u); } else if (utype == "Sampler") { TextureUniform u; u.handle = bgfx::createUniform(uname.c_str(), bgfx::UniformType::Sampler); auto c = j.find("channel"); if (c != std::end(j)) { u.channel = c->get(); const auto v = j.find("value"); if (v != std::end(j)) if (v->is_string()) { const auto path = v->get(); const auto flags = LoadTextureFlags(ir, ip, path); u.tex_ref = LoadTexture(ir, ip, path.c_str(), flags, resources); } texs.push_back(u); } else { error(format("Ignoring invalid uniform '%1' sampler definition in '%2': no channel specified").arg(uname).arg(name)); } } } return true; } bool LoadPipelineProgramUniformsFromFile(const char *path, std::vector &texs, std::vector &vecs, PipelineResources &resources) { return LoadPipelineProgramUniforms(g_file_reader, g_file_read_provider, path, texs, vecs, resources); } bool LoadPipelineProgramUniformsFromAssets(const char *name, std::vector &texs, std::vector &vecs, PipelineResources &resources) { return LoadPipelineProgramUniforms(g_assets_reader, g_assets_read_provider, name, texs, vecs, resources); } // PipelineProgram LoadPipelineProgram(const Reader &ir, const ReadProvider &ip, const char *name, PipelineResources &resources, const PipelineInfo &pipeline) { PipelineProgram prg; bool success; prg.features = LoadPipelineProgramFeatures(ir, ip, name, success); if (!success) error(format("Failed to load pipeline program features '%1'").arg(name)); else prg.programs.resize(GetPipelineProgramVariantCount(prg.features) * pipeline.configs.size()); if (!LoadPipelineProgramUniforms(ir, ip, name, prg.texture_uniforms, prg.vec4_uniforms, resources)) error(format("Failed to load pipeline program uniforms '%1'").arg(name)); prg.name = CutFileExtension(name); prg.pipeline = pipeline; prg.ip = ip; prg.ir = ir; return prg; } PipelineProgram LoadPipelineProgramFromFile(const char *path, PipelineResources &resources, const PipelineInfo &pipeline) { return LoadPipelineProgram(g_file_reader, g_file_read_provider, path, resources, pipeline); } PipelineProgram LoadPipelineProgramFromAssets(const char *name, PipelineResources &resources, const PipelineInfo &pipeline) { return LoadPipelineProgram(g_assets_reader, g_assets_read_provider, name, resources, pipeline); } // int GetPipelineProgramFeatureStateCount(PipelineProgramFeature feature) { return 2; } int GetPipelineProgramVariantCount(const std::vector &feats) { int count = 1; for (const auto feat : feats) count *= GetPipelineProgramFeatureStateCount(feat); return count; } int GetPipelineProgramVariantIndex(const std::vector &feats, const std::vector &states) { __ASSERT__(feats.size() == states.size()); int k = 1, idx = 0; for (size_t i = 0; i < feats.size(); ++i) { idx += states[i] * k; k *= GetPipelineProgramFeatureStateCount(feats[i]); } return idx; } std::string GetPipelineProgramVariantName(const char *name, const std::vector &feats, const std::vector &states) { __ASSERT__(feats.size() == states.size()); std::vector variant_tags; variant_tags.reserve(feats.size() + 2); variant_tags.push_back(name); const auto idx = GetPipelineProgramVariantIndex(feats, states); variant_tags.push_back(std::string("var-") + std::to_string(idx)); return join(std::begin(variant_tags), std::end(variant_tags), "_"); } // std::vector GetMaterialPipelineProgramFeatureStates(const Material &mat, const std::vector &features) { std::set tex_names; for (const auto &i : mat.textures) if (i.second.texture != InvalidTextureRef) tex_names.insert(i.first); const auto has_texture = [&](const char *name) -> bool { return tex_names.find(name) != std::end(tex_names); }; std::vector states; states.reserve(features.size()); for (const auto &feat : features) { if (feat == OptionalBaseColorOpacityMap) states.push_back(has_texture("uBaseOpacityMap") ? 1 : 0); else if (feat == OptionalOcclusionRoughnessMetalnessMap) states.push_back(has_texture("uOcclusionRoughnessMetalnessMap") ? 1 : 0); else if (feat == OptionalDiffuseMap) states.push_back(has_texture("uDiffuseMap") ? 1 : 0); else if (feat == OptionalSpecularMap) states.push_back(has_texture("uSpecularMap") ? 1 : 0); else if (feat == OptionalLightMap) states.push_back(has_texture("uLightMap") ? 1 : 0); else if (feat == OptionalSelfMap) states.push_back(has_texture("uSelfMap") ? 1 : 0); else if (feat == OptionalOpacityMap) states.push_back(has_texture("uOpacityMap") ? 1 : 0); else if (feat == OptionalAmbientMap) states.push_back(has_texture("uAmbientMap") ? 1 : 0); else if (feat == OptionalReflectionMap) states.push_back(has_texture("uReflectionMap") ? 1 : 0); else if (feat == OptionalNormalMap) states.push_back(has_texture("uNormalMap") ? 1 : 0); else if (feat == OptionalSkinning) states.push_back(mat.flags & MF_EnableSkinning ? 1 : 0); } return states; } // static void CreateMissingMaterialSampler( Material &mat, const std::string &name, uint8_t stage, const std::string &tex, PipelineResources &resources, const Reader &ir, const ReadProvider &ip) { if (mat.values.find(name) == std::end(mat.values)) { debug(format("Creating missing material sampler uniform %1").arg(name)); TextureRef ref{}; if (!tex.empty()) { debug(format("Loading missing material sampler uniform texture %1").arg(tex)); ref = LoadTexture(ir, ip, tex.c_str(), 0, resources); } SetMaterialTexture(mat, name.c_str(), ref, stage); } } static void CreateMissingMaterialVec4(Material &mat, const std::string &name, const Vec4 &val) { if (mat.values.find(name) == std::end(mat.values)) { debug(format("Creating missing material vec4 uniform %1").arg(name)); SetMaterialValue(mat, name.c_str(), val); } } void CreateMissingMaterialProgramValues(Material &mat, PipelineResources &resources, const Reader &ir, const ReadProvider &ip) { if (resources.programs.IsValidRef(mat.program)) { const auto &prg = resources.programs.Get_unsafe_(mat.program.ref.idx); std::set tex_names; for (const auto &i : mat.textures) tex_names.insert(i.first); const auto has_texture = [&](const char *name) -> bool { return tex_names.find(name) != std::end(tex_names); }; for (const auto &feat : prg.features) { if (feat == OptionalBaseColorOpacityMap) { if (!has_texture("uBaseOpacityMap")) CreateMissingMaterialVec4(mat, "uBaseOpacityColor", {0.75f, 0.75f, 0.75f, 1.f}); } else if (feat == OptionalOcclusionRoughnessMetalnessMap) { if (!has_texture("uOcclusionRoughnessMetalnessMap")) CreateMissingMaterialVec4(mat, "uOcclusionRoughnessMetalnessColor", {1.f, 0.5f, 0.1f, 1.f}); } else if (feat == OptionalDiffuseMap) { if (!has_texture("uDiffuseMap")) CreateMissingMaterialVec4(mat, "uDiffuseColor", {0.75f, 0.75f, 0.75f, 0.1f}); } else if (feat == OptionalSpecularMap) { if (!has_texture("uSpecularMap")) CreateMissingMaterialVec4(mat, "uSpecularColor", {0.75f, 0.75f, 0.75f, 0.25f}); } else if (feat == OptionalSelfMap) { if (!has_texture("uSelfMap")) CreateMissingMaterialVec4(mat, "uSelfColor", {0.f, 0.f, 0.f, 1.f}); } } // the following optional features do not fall back to a material value // // OptionalLightMap // OptionalOpacityMap // OptionalAmbientMap // OptionalReflectionMap // OptionalNormalMap // // NormalMapWorldOrTangent // DiffuseUV01 // SpecularUV01 } } void CreateMissingMaterialProgramValuesFromFile(Material &mat, PipelineResources &resources) { CreateMissingMaterialProgramValues(mat, resources, g_file_reader, g_file_read_provider); } void CreateMissingMaterialProgramValuesFromAssets(Material &mat, PipelineResources &resources) { CreateMissingMaterialProgramValues(mat, resources, g_assets_reader, g_assets_read_provider); } // void UpdateMaterialPipelineProgramVariant(Material &mat, const PipelineResources &resources) { if (resources.programs.IsValidRef(mat.program)) { const auto &prg = resources.programs.Get_unsafe_(mat.program.ref.idx); const auto states = GetMaterialPipelineProgramFeatureStates(mat, prg.features); mat.variant_idx = GetPipelineProgramVariantIndex(prg.features, states); } } // static bx::DefaultAllocator g_allocator; // json LoadResourceMeta(const Reader &ir, const ReadProvider &ip, const std::string &name) { const auto meta_path = name + ".meta"; return LoadJson(ir, ScopedReadHandle(ip, meta_path.c_str(), true)); } json LoadResourceMetaFromFile(const std::string &path) { return LoadResourceMeta(g_file_reader, g_file_read_provider, path); } json LoadResourceMetaFromAssets(const std::string &name) { return LoadResourceMeta(g_assets_reader, g_assets_read_provider, name); } bool SaveResourceMetaToFile(const std::string &path, const json &meta) { return hg::SaveJsonToFile(meta, (path + ".meta").c_str()); } // TextureMeta LoadTextureMeta(const Reader &ir, const ReadProvider &ip, const std::string &name) { const auto js = LoadResourceMeta(ir, ip, name); TextureMeta meta; { std::string filter; GetMetaValue(js, "min-filter", filter); if (filter == "Nearest") meta.flags |= BGFX_SAMPLER_MIN_POINT; else if (filter == "Linear") ; else // if (filter == "Anisotropic") meta.flags |= BGFX_SAMPLER_MIN_ANISOTROPIC; GetMetaValue(js, "mag-filter", filter); if (filter == "Nearest") meta.flags |= BGFX_SAMPLER_MAG_POINT; else if (filter == "Linear") ; else // if (filter == "Anisotropic") meta.flags |= BGFX_SAMPLER_MAG_ANISOTROPIC; } { std::string wrap; GetMetaValue(js, "wrap-U", wrap); if (wrap == "Clamp") meta.flags |= BGFX_SAMPLER_U_CLAMP; else if (wrap == "Border") meta.flags |= BGFX_SAMPLER_U_BORDER; else if (wrap == "Mirror") meta.flags |= BGFX_SAMPLER_U_MIRROR; GetMetaValue(js, "wrap-V", wrap); if (wrap == "Clamp") meta.flags |= BGFX_SAMPLER_V_CLAMP; else if (wrap == "Border") meta.flags |= BGFX_SAMPLER_V_BORDER; else if (wrap == "Mirror") meta.flags |= BGFX_SAMPLER_V_MIRROR; } { bool sRGB = false; GetMetaValue(js, "sRGB", sRGB); if (sRGB) meta.flags |= BGFX_TEXTURE_SRGB; else meta.flags &= ~BGFX_TEXTURE_SRGB; } return meta; } TextureMeta LoadTextureMetaFromFile(const std::string &path) { return LoadTextureMeta(g_file_reader, g_file_read_provider, path); } TextureMeta LoadTextureMetaFromAssets(const std::string &name) { return LoadTextureMeta(g_assets_reader, g_assets_read_provider, name); } // uint64_t LoadTextureFlags(const Reader &ir, const ReadProvider &ip, const std::string &name) { return LoadTextureMeta(ir, ip, name).flags; } uint64_t LoadTextureFlagsFromFile(const std::string &path) { return LoadTextureFlags(g_file_reader, g_file_read_provider, path); } uint64_t LoadTextureFlagsFromAssets(const std::string &name) { return LoadTextureFlags(g_assets_reader, g_assets_read_provider, name); } // Texture CreateTexture(int width, int height, const char *name, uint64_t flags, bgfx::TextureFormat::Enum texture_format) { log(format("Creating texture '%1' (empty %2x%3)").arg(name).arg(width).arg(height).c_str()); bgfx::TextureHandle handle = BGFX_INVALID_HANDLE; if (bgfx::isTextureValid(0, false, 1, texture_format, flags)) handle = bgfx::createTexture2D(width, height, false, 1, texture_format, flags); if (bgfx::isValid(handle)) bgfx::setName(handle, name); else error(format("Failed to create texture '%1', format:%2 flags:%3").arg(name).arg(texture_format).arg(flags).c_str()); return MakeTexture(handle, flags); } Texture CreateTextureFromPicture(const Picture &pic, const char *name, uint64_t flags, bgfx::TextureFormat::Enum texture_format) { log(format("Creating texture '%1' from picture").arg(name).c_str()); bgfx::TextureHandle handle = BGFX_INVALID_HANDLE; const auto *mem = bgfx::makeRef(pic.GetData(), pic.GetWidth() * pic.GetHeight() * size_of(pic.GetFormat())); if (bgfx::isTextureValid(0, false, 1, texture_format, flags)) handle = bgfx::createTexture2D(pic.GetWidth(), pic.GetHeight(), false, 1, texture_format, flags, mem); if (bgfx::isValid(handle)) bgfx::setName(handle, name); else error(format("Failed to create texture '%1', format:%2 flags:%3").arg(name).arg(texture_format).arg(flags).c_str()); return MakeTexture(handle, flags); } void UpdateTextureFromPicture(Texture &tex, const Picture &pic) { const auto *mem = bgfx::makeRef(pic.GetData(), pic.GetWidth() * pic.GetHeight() * size_of(pic.GetFormat())); bgfx::updateTexture2D(tex.handle, 0, 0, 0, 0, pic.GetWidth(), pic.GetHeight(), mem); } // Texture LoadTexture(const Reader &ir, const ReadProvider &ip, const char *name, uint64_t flags, bgfx::TextureInfo *info, bimg::Orientation::Enum *orientation) { log(format("Loading texture '%1'").arg(name).c_str()); bgfx::TextureHandle handle = BGFX_INVALID_HANDLE; const auto data = LoadData(ir, ScopedReadHandle(ip, name)); if (data.GetSize() > 0) { if (auto container = bimg::imageParse(&g_allocator, data.GetData(), uint32_t(data.GetSize()), bimg::TextureFormat::Count)) { const auto *mem = bgfx::makeRef( container->m_data, container->m_size, [](void *ptr, void *user) { BX_ALIGNED_FREE(&g_allocator, user, 16); }, container); if (container->m_cubeMap) { handle = bgfx::createTextureCube( uint16_t(container->m_width), 1 < container->m_numMips, container->m_numLayers, bgfx::TextureFormat::Enum(container->m_format), flags, mem); } else if (1 < container->m_depth) { handle = bgfx::createTexture3D(uint16_t(container->m_width), uint16_t(container->m_height), uint16_t(container->m_depth), 1 < container->m_numMips, bgfx::TextureFormat::Enum(container->m_format), flags, mem); } else if (bgfx::isTextureValid(0, false, container->m_numLayers, bgfx::TextureFormat::Enum(container->m_format), flags)) { handle = bgfx::createTexture2D(uint16_t(container->m_width), uint16_t(container->m_height), 1 < container->m_numMips, container->m_numLayers, bgfx::TextureFormat::Enum(container->m_format), flags, mem); } if (info) bgfx::calcTextureSize(*info, uint16_t(container->m_width), uint16_t(container->m_height), uint16_t(container->m_depth), container->m_cubeMap, 1 < container->m_numMips, container->m_numLayers, bgfx::TextureFormat::Enum(container->m_format)); } if (!bgfx::isValid(handle)) { error(format("Failed to load texture '%1', unsupported format").arg(name).c_str()); static const uint32_t dummy = 0xff00ffff; handle = bgfx::createTexture2D(1, 1, false, 1, bgfx::TextureFormat::RGBA8, BGFX_SAMPLER_NONE, bgfx::copy(&dummy, 4)); } if (bgfx::isValid(handle)) bgfx::setName(handle, name); } else { error(format("Failed to load texture '%1', could not load data").arg(name).c_str()); } return MakeTexture(handle, flags); } Texture LoadTextureFromFile(const char *name, uint64_t flags, bgfx::TextureInfo *info, bimg::Orientation::Enum *orientation) { return LoadTexture(g_file_reader, g_file_read_provider, name, flags, info, orientation); } Texture LoadTextureFromAssets(const char *name, uint64_t flags, bgfx::TextureInfo *info, bimg::Orientation::Enum *orientation) { return LoadTexture(g_assets_reader, g_assets_read_provider, name, flags, info, orientation); } // NLOHMANN_JSON_SERIALIZE_ENUM(FaceCulling, {{FC_Disabled, "disabled"}, {FC_Clockwise, "cw"}, {FC_CounterClockwise, "ccw"}}); NLOHMANN_JSON_SERIALIZE_ENUM(DepthTest, {{DT_Less, "less"}, {DT_LessEqual, "leq"}, {DT_Equal, "eq"}, {DT_GreaterEqual, "geq"}, {DT_Greater, "greater"}, {DT_NotEqual, "neq"}, {DT_Never, "never"}, {DT_Always, "always"}, {DT_Disabled, "disabled"}}); NLOHMANN_JSON_SERIALIZE_ENUM(BlendMode, {{BM_Additive, "add"}, {BM_Alpha, "alpha"}, {BM_Darken, "darken"}, {BM_Lighten, "lighten"}, {BM_Multiply, "multiply"}, {BM_Opaque, "opaque"}, {BM_Screen, "screen"}, {BM_LinearBurn, "linearburn"}, {BM_Undefined, "undefined"}}); bool SaveMaterial(const Material &mat, json &js, const PipelineResources &resources) { js["program"] = resources.programs.GetName(mat.program); for (const auto &i : mat.values) { auto &values = js["values"]; values.push_back({ {"name", i.first}, {"type", i.second.type}, {"value", i.second.value}, }); if (i.second.count != 1) values["count"] = i.second.count; } for (const auto &i : mat.textures) { json tex = {{"name", i.first}, {"stage", i.second.channel}}; const auto &texture = i.second.texture; if (resources.textures.IsValidRef(texture)) tex["path"] = resources.textures.GetName(texture); js["textures"].push_back(tex); } js["face_culling"] = GetMaterialFaceCulling(mat); js["depth_test"] = GetMaterialDepthTest(mat); js["blend_mode"] = GetMaterialBlendMode(mat); bool write_r, write_g, write_b, write_a; GetMaterialWriteRGBA(mat, write_r, write_g, write_b, write_a); js["write_r"] = write_r; js["write_g"] = write_g; js["write_b"] = write_b; js["write_a"] = write_a; bool write_z = GetMaterialWriteZ(mat); js["write_z"] = write_z; json flags = json::array(); if (mat.flags & MF_EnableSkinning) flags.push_back("EnableSkinning"); js["flags"] = flags; return true; } bool SaveMaterial(const Material &mat, const Writer &iw, const Handle &h, const PipelineResources &resources) { Write(iw, h, resources.programs.GetName(mat.program)); Write(iw, h, uint16_t(mat.values.size())); for (const auto &i : mat.values) { Write(iw, h, i.first); Write(iw, h, i.second.type); Write(iw, h, i.second.count); Write(iw, h, uint16_t(i.second.value.size())); iw.write(h, i.second.value.data(), sizeof(float) * i.second.value.size()); } Write(iw, h, uint16_t(mat.textures.size())); for (const auto &i : mat.textures) { Write(iw, h, i.first); Write(iw, h, i.second.channel); if (resources.textures.IsValidRef(i.second.texture)) { Write(iw, h, uint8_t(1)); Write(iw, h, resources.textures.GetName(i.second.texture)); Write(iw, h, uint32_t(/*resources.textures.Get(i.second.texture).flags*/ 0xffffffff)); // TODO remove obsolete flag } else { Write(iw, h, uint8_t(0)); } } Write(iw, h, mat.state.state); Write(iw, h, mat.state.rgba); Write(iw, h, mat.flags); return true; } bool SaveMaterialToFile(const char *path, const Material &m, const PipelineResources &resources) { return SaveMaterial(m, g_file_writer, ScopedWriteHandle(g_file_write_provider, path), resources); } template T GetOptional(const json &js, const char *key, T dflt) { auto i = js.find(key); if (i == std::end(js)) return dflt; return i->get(); } Material LoadMaterial(const json &js, const Reader &deps_ir, const ReadProvider &deps_ip, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { Material mat; const std::string prg_name = js.at("program"); if (do_not_load_resources) mat.program = resources.programs.Add(prg_name.c_str(), {}); else mat.program = LoadPipelineProgramRef(deps_ir, deps_ip, prg_name.c_str(), resources, pipeline); const auto i_values = js.find("values"); if (i_values != std::end(js)) for (const auto &val : *i_values) { const auto name = val.at("name").get(); auto &v = mat.values[name]; v.type = val.at("type"); v.count = val.find("count") != std::end(val) ? val.at("count").get() : uint16_t(1); const auto &vval = val.at("value"); v.value.clear(); v.value.reserve(vval.size()); for (const auto &vv : vval) v.value.push_back(vv); v.uniform = BGFX_INVALID_HANDLE; if (IsRenderUp()) v.uniform = bgfx::createUniform(name.c_str(), v.type, v.count); } const auto i_textures = js.find("textures"); if (i_textures != std::end(js)) for (const auto &tex : *i_textures) { const auto name = tex.at("name").get(); auto &t = mat.textures[name]; t.channel = tex.at("stage"); std::string path; const auto i_path = tex.find("path"); if (i_path != std::end(tex)) { path = i_path->get(); } else { const auto i_texture = tex.find("texture"); // legacy texture {path, flag} support if (i_texture != std::end(tex)) path = i_texture->at("name").get(); } if (!path.empty()) { if (do_not_load_resources) { t.texture = resources.textures.Add(path.c_str(), {}); } else { auto ref = resources.textures.Has(path.c_str()); if (ref != InvalidTextureRef) { t.texture = ref; } else { const auto meta = LoadTextureMeta(deps_ir, deps_ip, path); if (queue_texture_loads) t.texture = QueueLoadTexture(deps_ir, deps_ip, path.c_str(), meta.flags, resources); else t.texture = LoadTexture(deps_ir, deps_ip, path.c_str(), meta.flags, resources); } } } t.uniform = BGFX_INVALID_HANDLE; if (IsRenderUp()) t.uniform = bgfx::createUniform(name.c_str(), bgfx::UniformType::Sampler); } SetMaterialFaceCulling(mat, GetOptional(js, "face_culling", FC_Clockwise)); SetMaterialDepthTest(mat, GetOptional(js, "depth_test", DT_Less)); SetMaterialBlendMode(mat, GetOptional(js, "blend_mode", BM_Opaque)); const bool write_r = GetOptional(js, "write_r", true), write_g = GetOptional(js, "write_g", true), write_b = GetOptional(js, "write_b", true), write_a = GetOptional(js, "write_a", true); SetMaterialWriteRGBA(mat, write_r, write_g, write_b, write_a); const bool write_z = GetOptional(js, "write_z", true); SetMaterialWriteZ(mat, write_z); const auto i_flags = js.find("flags"); if (i_flags != std::end(js)) for (auto flag : *i_flags) { if (flag == "EnableSkinning") mat.flags |= MF_EnableSkinning; } UpdateMaterialPipelineProgramVariant(mat, resources); return mat; } Material LoadMaterial(const Reader &ir, const Handle &h, const Reader &deps_ir, const ReadProvider &deps_ip, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { Material mat; std::string name; Read(ir, h, name); if (do_not_load_resources) mat.program = resources.programs.Add(name.c_str(), {}); else mat.program = LoadPipelineProgramRef(deps_ir, deps_ip, name.c_str(), resources, pipeline); const auto value_count = Read(ir, h); for (size_t i = 0; i < value_count; ++i) { Read(ir, h, name); auto &v = mat.values[name]; Read(ir, h, v.type); Read(ir, h, v.count); uint16_t value_count; Read(ir, h, value_count); v.uniform = BGFX_INVALID_HANDLE; if (IsRenderUp()) v.uniform = bgfx::createUniform(name.c_str(), v.type); v.value.clear(); v.value.resize(value_count); for (auto &vv : v.value) ir.read(h, &vv, sizeof(float)); } const auto texture_count = Read(ir, h); for (size_t i = 0; i < texture_count; ++i) { Read(ir, h, name); auto &t = mat.textures[name]; Read(ir, h, t.channel); if (Read(ir, h) == 1) { std::string tex_name; Read(ir, h, tex_name); uint32_t dummy_flags; // probably will be removed, serves no purpose since proper meta file support Read(ir, h, dummy_flags); if (do_not_load_resources) { t.texture = resources.textures.Add(tex_name.c_str(), {}); } else { auto ref = resources.textures.Has(tex_name.c_str()); if (ref != InvalidTextureRef) { t.texture = ref; } else { const auto meta = LoadTextureMeta(deps_ir, deps_ip, tex_name); if (queue_texture_loads) t.texture = QueueLoadTexture(deps_ir, deps_ip, tex_name.c_str(), meta.flags, resources); else t.texture = LoadTexture(deps_ir, deps_ip, tex_name.c_str(), meta.flags, resources); } } } t.uniform = BGFX_INVALID_HANDLE; if (IsRenderUp()) t.uniform = bgfx::createUniform(name.c_str(), bgfx::UniformType::Sampler); } Read(ir, h, mat.state.state); Read(ir, h, mat.state.rgba); Read(ir, h, mat.flags); UpdateMaterialPipelineProgramVariant(mat, resources); return mat; } Material LoadMaterialFromFile( const char *path, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { return LoadMaterial(g_file_reader, ScopedReadHandle(g_file_read_provider, path), g_file_reader, g_file_read_provider, resources, pipeline, queue_texture_loads, do_not_load_resources); } Material LoadMaterialFromAssets( const char *path, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { return LoadMaterial(g_assets_reader, ScopedReadHandle(g_assets_read_provider, path), g_assets_reader, g_assets_read_provider, resources, pipeline, queue_texture_loads, do_not_load_resources); } // PipelineProgramRef LoadPipelineProgramRef( const Reader &ir, const ReadProvider &ip, const char *name, PipelineResources &resources, const PipelineInfo &pipeline) { auto ref = resources.programs.Has(name); if (ref == InvalidPipelineProgramRef) { auto prg = LoadPipelineProgram(ir, ip, name, resources, pipeline); ref = resources.programs.Add(name, std::move(prg)); } return ref; } PipelineProgramRef LoadPipelineProgramRefFromFile(const char *path, PipelineResources &resources, const PipelineInfo &pipeline) { return LoadPipelineProgramRef(g_file_reader, g_file_read_provider, path, resources, pipeline); } PipelineProgramRef LoadPipelineProgramRefFromAssets(const char *name, PipelineResources &resources, const PipelineInfo &pipeline) { return LoadPipelineProgramRef(g_assets_reader, g_assets_read_provider, name, resources, pipeline); } // bgfx::VertexLayout VertexLayoutPosFloatNormFloat() { bgfx::VertexLayout vtx_layout; vtx_layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float).end(); return vtx_layout; } bgfx::VertexLayout VertexLayoutPosFloatNormUInt8() { bgfx::VertexLayout vtx_layout; vtx_layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Uint8, true, true).end(); return vtx_layout; } bgfx::VertexLayout VertexLayoutPosFloatColorFloat() { bgfx::VertexLayout vtx_layout; vtx_layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Float).end(); return vtx_layout; } bgfx::VertexLayout VertexLayoutPosFloatColorUInt8() { bgfx::VertexLayout vtx_layout; vtx_layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true, false).end(); return vtx_layout; } bgfx::VertexLayout VertexLayoutPosFloatTexCoord0UInt8() { bgfx::VertexLayout vtx_layout; vtx_layout.begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Uint8, true, false) .skip(2) .end(); return vtx_layout; } bgfx::VertexLayout VertexLayoutPosFloatNormUInt8TexCoord0UInt8() { bgfx::VertexLayout vtx_layout; vtx_layout.begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true, false) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Uint8, true, false) .skip(2) .end(); return vtx_layout; } // Model LoadModel(const Reader &ir, const Handle &h, const char *name, ModelInfo *info) { const auto t = time_now(); if (!ir.is_valid(h)) { error(format("Cannot load model '%1', invalid file handle").arg(name)); return {}; } if (Read(ir, h) != HarfangMagic) { error(format("Cannot load model '%1', invalid magic marker").arg(name)); return {}; } if (Read(ir, h) != ModelMarker) { error(format("Cannot load model '%1', invalid file marker").arg(name)); return {}; } const auto version = Read(ir, h); if (version > 2) { error(format("Cannot load model '%1', unsupported version %2").arg(name).arg(version)); return {}; } Model model; bgfx::VertexLayout vs_decl; ir.read(h, &vs_decl, sizeof(bgfx::VertexLayout)); // read vertex declaration uint32_t tri_count{}; while (true) { uint8_t idx_type_size = 2; // legacy is 16 bit indices if (version > 1) { Read(ir, h, idx_type_size); // idx type size in bytes if (idx_type_size == 0) break; // EOLists __ASSERT_MSG__(idx_type_size == 2 || idx_type_size == 4, "BGFX only supports 16 or 32 bit index buffer"); } // index buffer auto size = Read(ir, h); if (version < 2) if (size == 0) break; // EOLists const auto idx_mem = bgfx::alloc(size); ir.read(h, idx_mem->data, idx_mem->size); tri_count += (size / idx_type_size) / 3; const auto idx_hnd = bgfx::createIndexBuffer(idx_mem, idx_type_size == 4 ? BGFX_BUFFER_INDEX32 : BGFX_BUFFER_NONE); bgfx::setName(idx_hnd, name); // vertex buffer size = Read(ir, h); const auto vtx_mem = bgfx::alloc(size); ir.read(h, vtx_mem->data, vtx_mem->size); const auto vtx_hnd = bgfx::createVertexBuffer(vtx_mem, vs_decl); bgfx::setName(vtx_hnd, name); // bones table size = Read(ir, h); std::vector bones_table; bones_table.resize(size); ir.read(h, bones_table.data(), bones_table.size() * sizeof(bones_table[0])); // model.lists.push_back({idx_hnd, vtx_hnd, std::move(bones_table)}); model.bounds.push_back(Read(ir, h)); model.mats.push_back(Read(ir, h)); } if (info) { info->vs_decl = vs_decl; info->tri_count = tri_count; } if (version > 0) { // version 1: add bind poses const auto bone_count = Read(ir, h); model.bind_pose.resize(bone_count); for (uint32_t j = 0; j < bone_count; ++j) Read(ir, h, model.bind_pose[j]); } log(format("Load model '%1' (%2 triangles, %3 lists), took %4 ms") .arg(name) .arg(tri_count) .arg(model.lists.size()) .arg(time_to_ms(time_now() - t)) .c_str()); return model; } Model LoadModelFromFile(const char *path, ModelInfo *info) { return LoadModel(g_file_reader, ScopedReadHandle(g_file_read_provider, path), path, info); } Model LoadModelFromAssets(const char *name, ModelInfo *info) { return LoadModel(g_assets_reader, ScopedReadHandle(g_assets_read_provider, name), name, info); } // size_t GetModelMaterialCount(const Model &model) { size_t count = 0; for (auto i : model.mats) if (i > count) count = i; return count + 1; } // UniformSetValue MakeUniformSetValue(const char *name, float val, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Vec4, count); v.value.resize(4 * count); v.count = count; for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = {val, 0.f, 0.f, 0.f}; return v; } UniformSetValue MakeUniformSetValue(const char *name, const Vec2 &val, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Vec4, count); v.value.resize(4 * count); v.count = count; for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = {val.x, val.y, 0.f, 0.f}; return v; } UniformSetValue MakeUniformSetValue(const char *name, const Vec3 &val, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Vec4, count); v.value.resize(4 * count); for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = {val.x, val.y, val.z, 1.f}; return v; } UniformSetValue MakeUniformSetValue(const char *name, const Vec4 &val, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Vec4, count); v.value.resize(4 * count); v.count = count; for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = val; return v; } UniformSetValue MakeUniformSetValue(const char *name, const Mat3 &mtx, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Mat4, count); v.value.resize(sizeof(bgfxMatrix3) / sizeof(float) * count); v.count = count; const auto m = to_bgfx(mtx); for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = m; return v; } UniformSetValue MakeUniformSetValue(const char *name, const Mat4 &mtx, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Mat4, count); v.value.resize(sizeof(bgfxMatrix4) / sizeof(float) * count); v.count = count; const auto m = to_bgfx(mtx); for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = m; return v; } UniformSetValue MakeUniformSetValue(const char *name, const Mat44 &mtx, uint16_t count) { UniformSetValue v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Mat4, count); v.value.resize(sizeof(bgfxMatrix4) / sizeof(float) * count); v.count = count; const auto m = to_bgfx(mtx); for (size_t i = 0; i < count; ++i) reinterpret_cast(v.value.data())[i] = m; return v; } UniformSetTexture MakeUniformSetTexture(const char *name, const Texture &texture, uint8_t stage) { UniformSetTexture v; v.uniform = bgfx::createUniform(name, bgfx::UniformType::Sampler); v.texture = texture; v.stage = stage; return v; } // OK void SetUniforms(const std::vector &values, const std::vector &textures) { for (auto &v : values) bgfx::setUniform(v.uniform, v.value.data(), v.count); for (auto &t : textures) bgfx::setTexture(t.stage, t.uniform, t.texture.handle, uint32_t(t.texture.flags)); // only retain the BGFX_SAMPLER_XXX bits of the texture flag } void DrawDisplayList(bgfx::ViewId view_id, bgfx::IndexBufferHandle idx, bgfx::VertexBufferHandle vtx, bgfx::ProgramHandle prg, const std::vector &values, const std::vector &textures, RenderState state, uint32_t depth) { SetUniforms(values, textures); bgfx::setIndexBuffer(idx); bgfx::setVertexBuffer(0, vtx); bgfx::setState(state.state, state.rgba); bgfx::submit(view_id, prg, depth); } static void RenderDisplayLists(bgfx::ViewId view_id, const std::vector &lists, bgfx::ProgramHandle prg, const std::vector &values, const std::vector &textures, const bgfxMatrix4 *mtxs, size_t mtx_count, RenderState state, uint32_t depth) { const auto i_mtx = bgfx::setTransform(mtxs, uint16_t(mtx_count)); const auto lists_size = lists.size(); for (size_t i = 0; i < lists_size; ++i) { bgfx::setTransform(i_mtx); DrawDisplayList(view_id, lists[i].index_buffer, lists[i].vertex_buffer, prg, values, textures, state, depth); } } // void DrawModel(bgfx::ViewId view_id, const Model &mdl, bgfx::ProgramHandle prg, const std::vector &values, const std::vector &textures, const Mat4 *mtxs, size_t mtx_count, RenderState state, uint32_t depth) { std::vector bgfx_mtxs(mtx_count); for (size_t i = 0; i < mtx_count; ++i) bgfx_mtxs[i] = to_bgfx(mtxs[i]); RenderDisplayLists(view_id, mdl.lists, prg, values, textures, bgfx_mtxs.data(), mtx_count, state, depth); } // void Destroy(PipelineProgram &pprg) { pprg.features.clear(); for (auto &u : pprg.texture_uniforms) bgfx::destroy(u.handle); pprg.texture_uniforms.clear(); for (auto &u : pprg.vec4_uniforms) bgfx::destroy(u.handle); pprg.vec4_uniforms.clear(); for (auto &prg : pprg.programs) if (bgfx::isValid(prg.handle)) bgfx::destroy(prg.handle); pprg.programs.clear(); } void Destroy(Texture &texture) { if (bgfx::isValid(texture.handle)) bgfx::destroy(texture.handle); texture.handle = BGFX_INVALID_HANDLE; } void Destroy(Model &model) { for (auto &l : model.lists) { if (bgfx::isValid(l.index_buffer)) bgfx::destroy(l.index_buffer); l.index_buffer = BGFX_INVALID_HANDLE; if (bgfx::isValid(l.vertex_buffer)) bgfx::destroy(l.vertex_buffer); l.vertex_buffer = BGFX_INVALID_HANDLE; } model.lists.clear(); } void Destroy(Material &material) { for (auto &i : material.textures) { if (bgfx::isValid(i.second.uniform)) bgfx::destroy(i.second.uniform); i.second.uniform = BGFX_INVALID_HANDLE; } for (auto &i : material.values) { if (bgfx::isValid(i.second.uniform)) bgfx::destroy(i.second.uniform); i.second.uniform = BGFX_INVALID_HANDLE; } } // void PipelineResources::DestroyAll() { if (bgfx_is_up) { programs.DestroyAll(); textures.DestroyAll(); materials.DestroyAll(); models.DestroyAll(); } texture_loads.clear(); texture_infos.clear(); } // static size_t ComputePipelineProgramVariantConfigProgramIndex(uint32_t variant_idx, size_t pipeline_config_count, uint8_t pipeline_config_idx) { return variant_idx * pipeline_config_count + pipeline_config_idx; } static bgfx::ProgramHandle RequestPipelineProgramVariantConfigProgram(const PipelineProgram &pprg, uint32_t variant_idx, uint8_t pipeline_config_idx) { const auto idx = ComputePipelineProgramVariantConfigProgramIndex(variant_idx, pprg.pipeline.configs.size(), pipeline_config_idx); if (idx < pprg.programs.size()) { auto &prg = const_cast(pprg.programs[idx]); // OUCH! if (!prg.loaded) { const std::string path = format("%1_var-%2_pipe-%3-cfg-%4").arg(pprg.name).arg(variant_idx).arg(pprg.pipeline.name).arg(pipeline_config_idx); log(format("On-request loading pipeline shader program '%1'").arg(path)); prg.handle = LoadProgram(pprg.ir, pprg.ip, path.c_str()); prg.loaded = true; // we tried at least... } return prg.handle; } return BGFX_INVALID_HANDLE; } // OK static void _RenderPipelineStageDisplayList(bgfx::ViewId view_id, const DisplayList &display_list, const Material &mat, uint8_t pipeline_config_idx, const PipelineResources &res, const std::vector &values, const std::vector &textures, uint32_t depth) { const auto &prg = res.programs.Get_unsafe_(mat.program.ref.idx); const auto prg_h = RequestPipelineProgramVariantConfigProgram(prg, mat.variant_idx, pipeline_config_idx); if (!bgfx::isValid(prg_h)) return; // material values/fallback to shader default std::vector uniforms_set; uniforms_set.reserve(32); for (const auto &i : mat.values) { bgfx::setUniform(i.second.uniform, i.second.value.data(), i.second.count); uniforms_set.push_back(i.second.uniform.idx); } for (const auto &u : prg.vec4_uniforms) if (std::find(std::begin(uniforms_set), std::end(uniforms_set), u.handle.idx) == std::end(uniforms_set)) bgfx::setUniform(u.handle, &u.value.x); // not set, set from shader default // material texture/fallback to shader default uniforms_set.clear(); for (const auto &i : mat.textures) { const auto &tex = res.textures.Get(i.second.texture); if (bgfx::isValid(tex.handle)) bgfx::setTexture(i.second.channel, i.second.uniform, tex.handle, uint32_t(tex.flags)); // only retain the BGFX_SAMPLER_XXX bits of the texture flag uniforms_set.push_back(i.second.uniform.idx); } for (const auto &u : prg.texture_uniforms) if (std::find(std::begin(uniforms_set), std::end(uniforms_set), u.handle.idx) == std::end(uniforms_set)) { const auto &tex = res.textures.Get(u.tex_ref); if (bgfx::isValid(tex.handle)) bgfx::setTexture(u.channel, u.handle, tex.handle, uint32_t(tex.flags)); // not set, set from shader default } DrawDisplayList(view_id, display_list.index_buffer, display_list.vertex_buffer, prg_h, values, textures, mat.state, depth); } static void _RenderDisplayLists(bgfx::ViewId view_id, const std::vector &lists, const std::vector &depths, const Material *mats, uint8_t pipeline_config_idx, const PipelineResources &res, const std::vector &values, const std::vector &textures, const bgfxMatrix4 *mtxs, size_t mtx_count) { const auto i_mtx = bgfx::setTransform(mtxs, uint16_t(mtx_count)); const auto lists_size = lists.size(); __ASSERT__(lists_size == depths.size()); for (size_t i = 0; i < lists_size; ++i) { bgfx::setTransform(i_mtx); _RenderPipelineStageDisplayList(view_id, lists[i], mats[i], pipeline_config_idx, res, values, textures, depths[i]); } } static void _RenderDisplayLists(bgfx::ViewId view_id, const std::vector &lists, const std::vector &depths, const Material &mat, uint8_t pipeline_config_idx, const PipelineResources &res, const std::vector &values, const std::vector &textures, const bgfxMatrix4 *mtxs, size_t mtx_count) { std::vector mats(lists.size(), mat); _RenderDisplayLists(view_id, lists, depths, mats.data(), pipeline_config_idx, res, values, textures, mtxs, mtx_count); } // void CullModelDisplayLists(const Frustum &frustum, std::vector &display_lists, const std::vector &mtxs, const PipelineResources &res) { const auto i = std::remove_if(std::begin(display_lists), std::end(display_lists), [&](const ModelDisplayList &display_list) { const auto &model = res.models.Get_unsafe_(display_list.mdl_idx); return TestVisibility(frustum, model.bounds[display_list.lst_idx] * mtxs[display_list.mtx_idx]) == V_Outside; }); display_lists.resize(std::distance(std::begin(display_lists), i)); } // static void _DrawModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, const std::vector *depths, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const std::vector *prv_mtxs, const PipelineResources &res) { bgfxMatrix4 _mtx; uint32_t mtx_idx = 0xffffffff, i_mtx = 0xffffffff; // last set_matrix const auto dl_size = display_lists.size(); __ASSERT__(depths == nullptr || dl_size == depths->size()); for (size_t i = 0; i < dl_size; ++i) { const auto &dl = display_lists[i]; if (mtx_idx == dl.mtx_idx) { bgfx::setTransform(i_mtx, 1); // reuse last cache index } else { mtx_idx = dl.mtx_idx; _mtx = to_bgfx(mtxs[mtx_idx]); i_mtx = bgfx::setTransform(&_mtx, 1); } if (prv_mtxs) { _mtx = to_bgfx((*prv_mtxs)[dl.mtx_idx]); bgfx::setUniform(u_previous_model, &_mtx, 1); } const auto &mdl = res.models.Get_unsafe_(dl.mdl_idx); __ASSERT__(dl.mat != nullptr); _RenderPipelineStageDisplayList(view_id, mdl.lists[dl.lst_idx], *dl.mat, pipeline_config_idx, res, values, textures, depths ? (*depths)[i] : 0); } } void DrawModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const PipelineResources &res) { _DrawModelDisplayLists(view_id, display_lists, nullptr, pipeline_config_idx, values, textures, mtxs, nullptr, res); } void DrawModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, const std::vector &depths, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const PipelineResources &res) { _DrawModelDisplayLists(view_id, display_lists, &depths, pipeline_config_idx, values, textures, mtxs, nullptr, res); } void DrawModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const std::vector &prv_mtxs, const PipelineResources &res) { _DrawModelDisplayLists(view_id, display_lists, nullptr, pipeline_config_idx, values, textures, mtxs, &prv_mtxs, res); } // static void _DrawSkinnedModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, const std::vector *depths, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const std::vector *prv_mtxs, const PipelineResources &res) { bgfxMatrix4 _mtx[max_skinned_model_matrix_count] = {0}; const auto dl_size = display_lists.size(); __ASSERT__(depths == nullptr || dl_size == depths->size()); for (size_t i = 0; i < dl_size; ++i) { const auto &dl = display_lists[i]; __ASSERT__(dl.bone_count <= max_skinned_model_matrix_count); const auto &mdl = res.models.Get_unsafe_(dl.mdl_idx); for (int j = 0; j < dl.bone_count; ++j) _mtx[j] = to_bgfx(mtxs[dl.mtx_idxs[j]] * mdl.bind_pose[dl.bones_idxs[j]]); bgfx::setTransform(_mtx, dl.bone_count); // TODO [EJ] implement matrix caching here if (prv_mtxs) { for (int j = 0; j < dl.bone_count; ++j) _mtx[j] = to_bgfx((*prv_mtxs)[dl.mtx_idxs[j]] * mdl.bind_pose[dl.bones_idxs[j]]); bgfx::setUniform(u_previous_model, _mtx, dl.bone_count); } __ASSERT__(dl.mat != nullptr); _RenderPipelineStageDisplayList(view_id, mdl.lists[dl.lst_idx], *dl.mat, pipeline_config_idx, res, values, textures, depths ? (*depths)[i] : 0); } } void DrawSkinnedModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const PipelineResources &res) { _DrawSkinnedModelDisplayLists(view_id, display_lists, nullptr, pipeline_config_idx, values, textures, mtxs, nullptr, res); } void DrawSkinnedModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, const std::vector &depths, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const PipelineResources &res) { _DrawSkinnedModelDisplayLists(view_id, display_lists, &depths, pipeline_config_idx, values, textures, mtxs, nullptr, res); } void DrawSkinnedModelDisplayLists(bgfx::ViewId view_id, const std::vector &display_lists, uint8_t pipeline_config_idx, const std::vector &values, const std::vector &textures, const std::vector &mtxs, const std::vector &prv_mtxs, const PipelineResources &res) { _DrawSkinnedModelDisplayLists(view_id, display_lists, nullptr, pipeline_config_idx, values, textures, mtxs, &prv_mtxs, res); } // ModelRef LoadModel(const Reader &ir, const ReadProvider &ip, const char *path, PipelineResources &resources) { auto ref = resources.models.Has(path); if (ref == InvalidModelRef) { auto mdl = LoadModel(ir, ScopedReadHandle(ip, path), path); ref = resources.models.Add(path, std::move(mdl)); } return ref; } ModelRef LoadModelFromFile(const char *path, PipelineResources &resources) { return LoadModel(g_file_reader, g_file_read_provider, path, resources); } ModelRef LoadModelFromAssets(const char *path, PipelineResources &resources) { return LoadModel(g_assets_reader, g_assets_read_provider, path, resources); } MaterialRef LoadMaterialRef(const Reader &ir, const Handle &h, const char *path, const Reader &deps_ir, const ReadProvider &deps_ip, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { auto ref = resources.materials.Has(path); if (ref == InvalidMaterialRef) { auto mat = LoadMaterial(ir, h, deps_ir, deps_ip, resources, pipeline, queue_texture_loads, do_not_load_resources); ref = resources.materials.Add(path, std::move(mat)); } return ref; } MaterialRef LoadMaterialRefFromFile( const char *path, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { return LoadMaterialRef(g_file_reader, ScopedReadHandle(g_file_read_provider, path), path, g_file_reader, g_file_read_provider, resources, pipeline, queue_texture_loads, do_not_load_resources); } MaterialRef LoadMaterialRefFromAssets( const char *path, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources) { return LoadMaterialRef(g_assets_reader, ScopedReadHandle(g_assets_read_provider, path), path, g_assets_reader, g_assets_read_provider, resources, pipeline, queue_texture_loads, do_not_load_resources); } // size_t ProcessTextureLoadQueue(PipelineResources &res) { while (!res.texture_loads.empty()) { const auto &t = res.texture_loads.front(); if (res.textures.IsValidRef(t.ref)) { auto &tex = res.textures.Get(t.ref); const auto name = res.textures.GetName(t.ref); debug(format("Queued texture load '%1'").arg(name)); bgfx::TextureInfo info; tex = LoadTexture(t.ir, t.ip, name.c_str(), tex.flags, &info); res.texture_infos[t.ref.ref] = info; } res.texture_loads.pop_front(); break; // limit to 1 texture load per frame } return res.texture_loads.size(); } TextureRef QueueLoadTexture(const Reader &ir, const ReadProvider &ip, const char *name, uint64_t flags, PipelineResources &resources) { auto ref = resources.textures.Has(name); if (ref != InvalidTextureRef) return ref; ref = resources.textures.Add(name, {flags, bgfx::kInvalidHandle}); resources.texture_loads.push_back({ir, ip, ref}); return ref; } TextureRef QueueLoadTextureFromFile(const char *path, uint64_t flags, PipelineResources &resources) { return QueueLoadTexture(g_file_reader, g_file_read_provider, path, flags, resources); } TextureRef QueueLoadTextureFromAssets(const char *name, uint64_t flags, PipelineResources &resources) { return QueueLoadTexture(g_assets_reader, g_assets_read_provider, name, flags, resources); } // TextureRef LoadTexture(const Reader &ir, const ReadProvider &ip, const char *name, uint64_t flags, PipelineResources &resources) { auto ref = resources.textures.Has(name); if (ref == InvalidTextureRef) { bgfx::TextureInfo info; const auto tex = LoadTexture(ir, ip, name, flags, &info); ref = resources.textures.Add(name, tex); resources.texture_infos[ref.ref] = info; } return ref; } TextureRef LoadTextureFromFile(const char *path, uint64_t flags, PipelineResources &resources) { return LoadTexture(g_file_reader, g_file_read_provider, path, flags, resources); } TextureRef LoadTextureFromAssets(const char *name, uint64_t flags, PipelineResources &resources) { return LoadTexture(g_assets_reader, g_assets_read_provider, name, flags, resources); } // uint32_t CaptureTexture(const PipelineResources &resources, const hg::TextureRef &t, Picture &pic) { const auto ref = resources.textures.Get(t); return bgfx::readTexture(ref.handle, pic.GetData()); } // void SetTransform(const Mat4 &mtx) { bgfx::setTransform(to_bgfx(mtx).data()); } // void Vertices::Clear() { data.clear(); } void Vertices::Reserve(size_t count) { data.reserve(decl.getSize(numeric_cast(count))); } void Vertices::Resize(size_t count) { data.resize(decl.getSize(numeric_cast(count))); } Vertices::Vertices(const bgfx::VertexLayout &decl_, size_t count) : decl(decl_) { Resize(count); } Vertices &Vertices::Begin(size_t i) { if (i >= GetCount()) { if (i >= GetCapacity()) { Reserve(i + 64); debug(format("Vertices Begin() called with index %1, resizing buffer to accommodate request").arg(i)); } Resize(i + 1); } vtx_attr_flag = 0; __ASSERT__(idx == -1); idx = numeric_cast(i); return *this; } Vertices &Vertices::SetPos(const Vec3 &pos) { __ASSERT__(idx != -1); bgfx::vertexPack(&pos.x, false, bgfx::Attrib::Position, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Position; return *this; } Vertices &Vertices::SetNormal(const Vec3 &normal) { __ASSERT__(idx != -1); bgfx::vertexPack(&normal.x, true, bgfx::Attrib::Normal, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Normal; return *this; } Vertices &Vertices::SetTangent(const Vec3 &tangent) { __ASSERT__(idx != -1); bgfx::vertexPack(&tangent.x, true, bgfx::Attrib::Tangent, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Tangent; return *this; } Vertices &Vertices::SetBinormal(const Vec3 &binormal) { __ASSERT__(idx != -1); bgfx::vertexPack(&binormal.x, true, bgfx::Attrib::Bitangent, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Bitangent; return *this; } Vertices &Vertices::SetTexCoord0(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord0, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord0; return *this; } Vertices &Vertices::SetTexCoord1(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord1, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord1; return *this; } Vertices &Vertices::SetTexCoord2(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord2, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord2; return *this; } Vertices &Vertices::SetTexCoord3(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord3, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord3; return *this; } Vertices &Vertices::SetTexCoord4(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord4, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord4; return *this; } Vertices &Vertices::SetTexCoord5(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord5, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord5; return *this; } Vertices &Vertices::SetTexCoord6(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord6, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord6; return *this; } Vertices &Vertices::SetTexCoord7(const Vec2 &uv) { __ASSERT__(idx != -1); bgfx::vertexPack(&uv.x, true, bgfx::Attrib::TexCoord7, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::TexCoord7; return *this; } Vertices &Vertices::SetColor0(const Color &color) { __ASSERT__(idx != -1); bgfx::vertexPack(&color.r, true, bgfx::Attrib::Color0, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Color0; return *this; } Vertices &Vertices::SetColor1(const Color &color) { __ASSERT__(idx != -1); bgfx::vertexPack(&color.r, true, bgfx::Attrib::Color1, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Color1; return *this; } Vertices &Vertices::SetColor2(const Color &color) { __ASSERT__(idx != -1); bgfx::vertexPack(&color.r, true, bgfx::Attrib::Color2, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Color2; return *this; } Vertices &Vertices::SetColor3(const Color &color) { __ASSERT__(idx != -1); bgfx::vertexPack(&color.r, true, bgfx::Attrib::Color3, decl, data.data(), idx); vtx_attr_flag |= 1 << bgfx::Attrib::Color3; return *this; } void Vertices::End(bool validate) { __ASSERT__(idx != -1); if (validate) { static const char *attr_name[bgfx::Attrib::Count] = {"Position", "Normal", "Tangent", "Bitangent", "Color0", "Color1", "Color2", "Color3", "Indices", "Weight", "TexCoord0", "TexCoord1", "TexCoord2", "TexCoord3", "TexCoord4", "TexCoord5", "TexCoord6", "TexCoord7"}; for (int attr = 0; attr < bgfx::Attrib::Count; ++attr) if (decl.has(bgfx::Attrib::Enum(attr)) && !(vtx_attr_flag & (1 << attr))) // attribute expected by the vertex layout but missing from vertex declaration warn(format("Incomplete vertex #%1 declaration: missing %2 attribute").arg(idx).arg(attr_name[attr])); } idx = -1; } // static void DrawPrimitives(bgfx::ViewId view_id, const Vertices &vtx, bgfx::ProgramHandle program, const std::vector &values, const std::vector &textures, const std::vector *idx, uint64_t state, uint32_t rgba, uint32_t depth) { const auto vtx_count = vtx.GetCount(); bgfx::TransientVertexBuffer vb; bgfx::allocTransientVertexBuffer(&vb, numeric_cast(vtx_count), vtx.GetDecl()); memcpy(vb.data, vtx.GetData(), vtx.GetSize()); bgfx::TransientIndexBuffer ib; if (idx) { bgfx::allocTransientIndexBuffer(&ib, uint32_t(idx->size())); std::copy(std::begin(*idx), std::end(*idx), reinterpret_cast(ib.data)); } else { bgfx::allocTransientIndexBuffer(&ib, numeric_cast(vtx_count)); for (size_t i = 0; i < vtx_count; ++i) reinterpret_cast(ib.data)[i] = uint16_t(i); } bgfx::setVertexBuffer(0, &vb); bgfx::setIndexBuffer(&ib); SetUniforms(values, textures); bgfx::setState(state, rgba); bgfx::submit(view_id, program, depth); } // void DrawLines(bgfx::ViewId view_id, const Vertices &vtx, bgfx::ProgramHandle program, const std::vector &values, const std::vector &textures, RenderState state, uint32_t depth) { DrawPrimitives(view_id, vtx, program, values, textures, nullptr, (state.state & ~BGFX_STATE_PT_MASK) | BGFX_STATE_PT_LINES, state.rgba, depth); } void DrawTriangles(bgfx::ViewId view_id, const Vertices &vtx, bgfx::ProgramHandle program, const std::vector &values, const std::vector &textures, RenderState state, uint32_t depth) { DrawPrimitives(view_id, vtx, program, values, textures, nullptr, state.state & ~BGFX_STATE_PT_MASK, state.rgba, depth); } void DrawLines(bgfx::ViewId view_id, const Vertices &vtx, bgfx::ProgramHandle program, RenderState state, uint32_t depth) { DrawLines(view_id, vtx, program, {}, {}, state, depth); } void DrawTriangles(bgfx::ViewId view_id, const Vertices &vtx, bgfx::ProgramHandle program, RenderState state, uint32_t depth) { DrawTriangles(view_id, vtx, program, {}, {}, state, depth); } // void DrawLines(bgfx::ViewId view_id, const Indices &idx, const Vertices &vtx, bgfx::ProgramHandle program, const std::vector &values, const std::vector &textures, RenderState state, uint32_t depth) { DrawPrimitives(view_id, vtx, program, values, textures, &idx, (state.state & ~BGFX_STATE_PT_MASK) | BGFX_STATE_PT_LINES, state.rgba, depth); } void DrawTriangles(bgfx::ViewId view_id, const Indices &idx, const Vertices &vtx, bgfx::ProgramHandle program, const std::vector &values, const std::vector &textures, RenderState state, uint32_t depth) { DrawPrimitives(view_id, vtx, program, values, textures, &idx, state.state & ~BGFX_STATE_PT_MASK, state.rgba, depth); } void DrawLines(bgfx::ViewId view_id, const Indices &idx, const Vertices &vtx, bgfx::ProgramHandle program, RenderState state, uint32_t depth) { DrawLines(view_id, idx, vtx, program, {}, {}, state, depth); } void DrawTriangles(bgfx::ViewId view_id, const Indices &idx, const Vertices &vtx, bgfx::ProgramHandle program, RenderState state, uint32_t depth) { DrawTriangles(view_id, vtx, program, {}, {}, state, depth); } // void DrawSprites(bgfx::ViewId view_id, const Mat3 &inv_view_R, bgfx::VertexLayout &decl, const std::vector &pos, const Vec2 &size, bgfx::ProgramHandle prg, const std::vector &values, const std::vector &textures, RenderState state, uint32_t depth) { const auto X = GetX(inv_view_R) * size.x, Y = GetY(inv_view_R) * size.y; const auto count = pos.size(); std::vector idx(count * 6, 0); for (size_t i = 0; i < count * 6; i += 6) { idx[i + 0] = 0; idx[i + 1] = 1; idx[i + 2] = 2; idx[i + 3] = 0; idx[i + 4] = 2; idx[i + 5] = 3; } Vertices vtx(decl, count * 4); for (size_t i = 0; i < count; ++i) { const auto &P = pos[i]; const size_t j = i * 4; vtx.Begin(j + 0).SetPos(P - X + Y).SetTexCoord0({0, 0}).End(); vtx.Begin(j + 1).SetPos(P + X + Y).SetTexCoord0({1, 0}).End(); vtx.Begin(j + 2).SetPos(P + X - Y).SetTexCoord0({1, 1}).End(); vtx.Begin(j + 3).SetPos(P - X - Y).SetTexCoord0({0, 1}).End(); } DrawTriangles(view_id, idx, vtx, prg, values, textures, state, depth); } void DrawSprites(bgfx::ViewId view_id, const Mat3 &inv_view_R, bgfx::VertexLayout &decl, const std::vector &pos, const Vec2 &size, bgfx::ProgramHandle prg, RenderState state, uint32_t depth) { DrawSprites(view_id, inv_view_R, decl, pos, size, prg, {}, {}, state, depth); } // void DestroyPipeline(Pipeline &pipeline) { pipeline.uniform_values.clear(); pipeline.uniform_textures.clear(); for (auto &i : pipeline.textures) bgfx::destroy(i.second); pipeline.textures.clear(); for (auto &i : pipeline.framebuffers) bgfx::destroy(i.second); pipeline.framebuffers.clear(); } // static void DumpRenderPipelineInfos() { log("Render pipeline memory footprint:"); log(""); log(" Structure | sizeof (bytes)"); log(" ------------------+---------------"); log(format(" PipelineInfo | %1").arg(sizeof(PipelineInfo)).c_str()); log(format(" PipelineProgram | %1").arg(sizeof(PipelineProgram)).c_str()); log(format(" Texture | %1").arg(sizeof(Texture)).c_str()); log(format(" UniformSetValue | %1").arg(sizeof(UniformSetValue)).c_str()); log(format(" UniformSetTexture | %1").arg(sizeof(UniformSetTexture)).c_str()); log(format(" ProgramHandle | %1").arg(sizeof(ProgramHandle)).c_str()); log(format(" Material | %1").arg(sizeof(Material)).c_str()); log(format(" DisplayList | %1").arg(sizeof(DisplayList)).c_str()); log(format(" ModelDisplayList | %1").arg(sizeof(ModelDisplayList)).c_str()); log(format(" Model | %1").arg(sizeof(Model)).c_str()); log(format(" ModelInfo | %1").arg(sizeof(ModelInfo)).c_str()); log(format(" ViewState | %1").arg(sizeof(ViewState)).c_str()); log(format(" Indices | %1").arg(sizeof(Indices)).c_str()); log(format(" Vertices | %1").arg(sizeof(Vertices)).c_str()); log(""); } // bool IsRenderUp() { return bgfx_is_up; } bool RenderInit(Window *window, bgfx::RendererType::Enum type, bgfx::CallbackI *callback) { bgfx::PlatformData pd; bx::memSet(&pd, 0, sizeof(pd)); pd.ndt = GetDisplay(); pd.nwh = GetWindowHandle(window); bgfx::setPlatformData(pd); bgfx::Init init; // bx::memSet(&init, 0, sizeof(bgfx::Init)); init.callback = callback; if (type != bgfx::RendererType::Noop) { init.type = type; init.platformData.nwh = GetWindowHandle(window); } #ifndef NDEBUG init.debug = true; #endif DumpRenderPipelineInfos(); int w, h; GetWindowClientSize(window, w, h); init.resolution.width = w; init.resolution.height = h; init.resolution.maxFrameLatency = 1; // 0 is default (3) for DX11 // bgfx::renderFrame(); // if (!bgfx::init(init)) return false; bgfx::reset(w, h, BGFX_RESET_FLIP_AFTER_RENDER | BGFX_RESET_FLUSH_AFTER_RENDER | BGFX_RESET_MAXANISOTROPY); u_previous_model = bgfx::createUniform("uPreviousModel", bgfx::UniformType::Mat4, 96); // WARNING keep in sync with BGFX_MAX_BONES_CONFIG in bgfx_shader.sh const bgfx::Caps *caps = bgfx::getCaps(); SetNDCInfos(caps->originBottomLeft, caps->homogeneousDepth); bgfx_is_up = true; return true; } bool RenderInit(Window *window, bgfx::CallbackI *callback) { return RenderInit(window, bgfx::RendererType::Noop, callback); } Window *RenderInit(const char *window_title, int width, int height, bgfx::RendererType::Enum type, uint32_t reset_flags, bgfx::TextureFormat::Enum format, uint32_t debug_flags, bgfx::CallbackI *callback) { auto win = window_title ? NewWindow(window_title, width, height) : NewWindow(width, height); if (!RenderInit(win, type, callback)) { DestroyWindow(win); return nullptr; } reset_flags |= BGFX_RESET_FLIP_AFTER_RENDER | BGFX_RESET_FLUSH_AFTER_RENDER | BGFX_RESET_MAXANISOTROPY; if (reset_flags) bgfx::reset(width, height, reset_flags, format); if (debug_flags) bgfx::setDebug(debug_flags); return win; } Window *RenderInit(int width, int height, bgfx::RendererType::Enum type, uint32_t reset_flags, bgfx::TextureFormat::Enum format, uint32_t debug_flags, bgfx::CallbackI *callback) { return RenderInit(nullptr, width, height, type, reset_flags, format, debug_flags, callback); } Window *RenderInit(int width, int height, uint32_t reset_flags, bgfx::TextureFormat::Enum format, uint32_t debug_flags, bgfx::CallbackI *callback) { return RenderInit(width, height, bgfx::RendererType::Noop, reset_flags, format, debug_flags, callback); } Window *RenderInit( const char *window_title, int width, int height, uint32_t reset_flags, bgfx::TextureFormat::Enum format, uint32_t debug_flags, bgfx::CallbackI *callback) { return RenderInit(window_title, width, height, bgfx::RendererType::Noop, reset_flags, format, debug_flags, callback); } void RenderShutdown() { bgfx::shutdown(); bgfx_is_up = false; } // bool RenderResetToWindow(Window *win, int &width, int &height, uint32_t reset_flags) { int new_width, new_height; if (!GetWindowClientSize(win, new_width, new_height)) return false; // query failed... if (new_width == width && new_height == height) return false; // nothing to be done width = new_width; height = new_height; bgfx::reset(width, height, reset_flags); return true; } // void SetView2D(bgfx::ViewId id, int x, int y, int res_x, int res_y, float znear, float zfar, uint16_t clear_flags, const Color &clear_color, float depth, uint8_t stencil, bool y_up) { const bgfx::Caps *caps = bgfx::getCaps(); bgfx::setViewClear(id, clear_flags, ColorToABGR32(clear_color), depth, stencil); bgfx::setViewRect(id, x, y, res_x, res_y); static const auto _view = to_bgfx(Mat4::Identity); const auto proj = Compute2DProjectionMatrix(znear, zfar, float(res_x), float(res_y), y_up); const auto _proj = to_bgfx(proj); bgfx::setViewTransform(id, _view.data(), _proj.data()); } void SetViewPerspective(bgfx::ViewId id, int x, int y, int res_x, int res_y, const Mat4 &world, float znear, float zfar, float zoom_factor, uint16_t clear_flags, const Color &clear_color, float depth, uint8_t stencil, const Vec2 &aspect_ratio) { const bgfx::Caps *caps = bgfx::getCaps(); bgfx::setViewClear(id, clear_flags, ColorToABGR32(clear_color), depth, stencil); bgfx::setViewRect(id, x, y, res_x, res_y); const auto _aspect_ratio = aspect_ratio == Vec2::Zero ? ComputeAspectRatioX(float(res_x), float(res_y)) : aspect_ratio; const auto view = to_bgfx(InverseFast(world)); const auto proj = to_bgfx(ComputePerspectiveProjectionMatrix(znear, zfar, zoom_factor, _aspect_ratio)); bgfx::setViewTransform(id, view.data(), proj.data()); } void SetViewOrthographic(bgfx::ViewId id, int x, int y, int res_x, int res_y, const Mat4 &world, float znear, float zfar, float size, uint16_t clear_flags, const Color &clear_color, float depth, uint8_t stencil, const Vec2 &aspect_ratio) { const bgfx::Caps *caps = bgfx::getCaps(); bgfx::setViewClear(id, clear_flags, ColorToABGR32(clear_color), depth, stencil); bgfx::setViewRect(id, x, y, res_x, res_y); const auto _aspect_ratio = aspect_ratio == Vec2::Zero ? ComputeAspectRatioX(float(res_x), float(res_y)) : aspect_ratio; const auto view = to_bgfx(InverseFast(world)); const auto proj = to_bgfx(ComputeOrthographicProjectionMatrix(znear, zfar, size, _aspect_ratio)); bgfx::setViewTransform(id, view.data(), proj.data()); } // static inline FrameBuffer CreateFrameBuffer(bgfx::TextureHandle color, bgfx::TextureHandle depth, const char *name, bool own_textures) { bgfx::TextureHandle texs[] = {color, depth}; if (own_textures) { bgfx::setName(color, format("FrameBuffer.color (%1)").arg(name).c_str()); bgfx::setName(depth, format("FrameBuffer.depth (%1)").arg(name).c_str()); } bgfx::FrameBufferHandle handle = bgfx::createFrameBuffer(2, texs, own_textures); bgfx::setName(handle, format("FrameBuffer (%1)").arg(name).c_str()); return {handle}; } FrameBuffer CreateFrameBuffer(const hg::Texture &color, const hg::Texture &depth, const char *name) { return CreateFrameBuffer(color.handle, depth.handle, name, false); } static inline uint64_t ComputeTextureRTMSAAFlag(int aa) { switch (aa) { case 2: return BGFX_TEXTURE_RT_MSAA_X2; case 4: return BGFX_TEXTURE_RT_MSAA_X4; case 8: return BGFX_TEXTURE_RT_MSAA_X8; case 16: return BGFX_TEXTURE_RT_MSAA_X16; default: return BGFX_TEXTURE_RT; } } FrameBuffer CreateFrameBuffer(bgfx::TextureFormat::Enum color_format, bgfx::TextureFormat::Enum depth_format, int aa, const char *name) { uint64_t flags = ComputeTextureRTMSAAFlag(aa); const auto color = bgfx::createTexture2D(bgfx::BackbufferRatio::Equal, false, 1, color_format, flags); const auto depth = bgfx::createTexture2D(bgfx::BackbufferRatio::Equal, false, 1, depth_format, flags | BGFX_TEXTURE_RT_WRITE_ONLY); return CreateFrameBuffer(color, depth, name, true); } FrameBuffer CreateFrameBuffer(int width, int height, bgfx::TextureFormat::Enum color_format, bgfx::TextureFormat::Enum depth_format, int aa, const char *name) { uint64_t flags = ComputeTextureRTMSAAFlag(aa); const auto color = bgfx::createTexture2D(width, height, false, 1, color_format, flags); const auto depth = bgfx::createTexture2D(width, height, false, 1, depth_format, flags | BGFX_TEXTURE_RT_WRITE_ONLY); return CreateFrameBuffer(color, depth, name, true); } Texture GetColorTexture(FrameBuffer &frameBuffer) { return MakeTexture(bgfx::getTexture(frameBuffer.handle, 0)); } Texture GetDepthTexture(FrameBuffer &frameBuffer) { return MakeTexture(bgfx::getTexture(frameBuffer.handle, 1)); } void DestroyFrameBuffer(FrameBuffer &frameBuffer) { bgfx::destroy(frameBuffer.handle); } // bool CreateFullscreenQuad(bgfx::TransientIndexBuffer &idx, bgfx::TransientVertexBuffer &vtx) { bgfx::VertexLayout layout; layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float).add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float).end(); if (bgfx::getAvailTransientIndexBuffer(6) != 6 || bgfx::getAvailTransientVertexBuffer(4, layout) != 4) return false; // bgfx::allocTransientIndexBuffer(&idx, 6); { uint16_t *data = reinterpret_cast(idx.data); *data++ = 0; *data++ = 1; *data++ = 2; *data++ = 0; *data++ = 2; *data++ = 3; } // const float x_min = 0.f, x_max = 1.f; const float y_min = 0.f, y_max = 1.f; const float u_min = 0.f, u_max = 1.f; float v_min = 0.f, v_max = 1.f; if (bgfx::getCaps()->originBottomLeft) std::swap(v_min, v_max); bgfx::allocTransientVertexBuffer(&vtx, 4, layout); { float *data = reinterpret_cast(vtx.data); *data++ = x_min; // x *data++ = y_min; // y *data++ = 0.f; // z *data++ = u_min; // u *data++ = v_min; // v *data++ = x_max; *data++ = y_min; *data++ = 0.f; *data++ = u_max; *data++ = v_min; *data++ = x_max; *data++ = y_max; *data++ = 0.f; *data++ = u_max; *data++ = v_max; *data++ = x_min; *data++ = y_max; *data++ = 0.f; *data++ = u_min; *data++ = v_max; } return true; } // Material CreateMaterial(PipelineProgramRef prg) { return {prg}; } Material CreateMaterial(PipelineProgramRef prg, const char *value_name, const Vec4 &value) { Material mat; mat.program = prg; SetMaterialValue(mat, value_name, value); return mat; } Material CreateMaterial(PipelineProgramRef prg, const char *value_name_0, const Vec4 &value_0, const char *value_name_1, const Vec4 &value_1) { Material mat; mat.program = prg; SetMaterialValue(mat, value_name_0, value_0); SetMaterialValue(mat, value_name_1, value_1); return mat; } // bimg::ImageContainer *LoadImage(const Reader &ir, const ReadProvider &ip, const char *name) { const auto data = hg::LoadData(ir, hg::ScopedReadHandle(ip, name)); if (data.GetSize() == 0) { return nullptr; } return bimg::imageParse(&g_allocator, data.GetData(), uint32_t(data.GetSize()), bimg::TextureFormat::Count); } bimg::ImageContainer *LoadImageFromFile(const char *name) { return LoadImage(g_file_reader, g_file_read_provider, name); } bimg::ImageContainer *LoadImageFromAssets(const char *name) { return LoadImage(g_assets_reader, g_assets_read_provider, name); } void UpdateTextureFromImage(Texture &tex, bimg::ImageContainer *img, bool auto_delete) { const bgfx::Memory *mem = bgfx::makeRef( img->m_data, img->m_size, auto_delete ? [](void *ptr, void *user) { bimg::imageFree((bimg::ImageContainer *)user); } : (bgfx::ReleaseFn)0, img); bgfx::updateTexture2D(tex.handle, 0, 0, 0, 0, img->m_width, img->m_height, mem); } } // namespace hg