1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-05-16 05:17:45 +00:00

Begin loading BG objects from the LGB

It "works" but it's too slow to actually all of them and
takes way too much memory.
This commit is contained in:
Joshua Goins 2025-05-14 21:06:41 -04:00
parent 58425c1ae0
commit 75a094134a
16 changed files with 182 additions and 104 deletions

View file

@ -12,6 +12,7 @@ class AppState : public QObject
public:
explicit AppState(QObject *parent = nullptr);
QString basePath;
std::vector<std::pair<QString, physis_LayerGroup>> lgbFiles;
Q_SIGNALS:

View file

@ -27,6 +27,7 @@ public Q_SLOTS:
private:
MDLPart *mdlPart = nullptr;
GameData *data;
FileCache &cache;
GameData *m_data;
FileCache &m_cache;
AppState *m_appState;
};

View file

@ -74,14 +74,8 @@ void MainWindow::setupActions()
void MainWindow::openMap(const QString &basePath)
{
QString base2Path = basePath.left(basePath.lastIndexOf(QStringLiteral("/level/")));
QString bgPath = QStringLiteral("bg/%1/bgplate/").arg(base2Path);
std::string bgPathStd = bgPath.toStdString() + "terrain.tera";
auto tera_buffer = physis_gamedata_extract_file(data, bgPathStd.c_str());
auto tera = physis_parse_tera(tera_buffer);
mapView->addTerrain(bgPath, tera);
m_appState->basePath = basePath;
setWindowTitle(basePath);

View file

@ -6,13 +6,15 @@
#include <QThreadPool>
#include <QVBoxLayout>
#include "appstate.h"
#include "filecache.h"
#include "objectpass.h"
MapView::MapView(GameData *data, FileCache &cache, AppState *appState, QWidget *parent)
: QWidget(parent)
, data(data)
, cache(cache)
, m_data(data)
, m_cache(cache)
, m_appState(appState)
{
mdlPart = new MDLPart(data, cache);
mdlPart->enableFreemode();
@ -24,6 +26,71 @@ MapView::MapView(GameData *data, FileCache &cache, AppState *appState, QWidget *
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(mdlPart);
setLayout(layout);
connect(appState, &AppState::mapLoaded, this, [this] {
mdlPart->clear();
QString base2Path = m_appState->basePath.left(m_appState->basePath.lastIndexOf(QStringLiteral("/level/")));
QString bgPath = QStringLiteral("bg/%1/bgplate/").arg(base2Path);
std::string bgPathStd = bgPath.toStdString() + "terrain.tera";
auto tera_buffer = physis_gamedata_extract_file(m_data, bgPathStd.c_str());
auto tera = physis_parse_tera(tera_buffer);
addTerrain(bgPath, tera);
// add bg models
for (const auto &[name, lgb] : m_appState->lgbFiles) {
// only load the bg models for now
if (name != QStringLiteral("bg")) {
continue;
}
for (int i = 0; i < lgb.num_chunks; i++) {
const auto chunk = lgb.chunks[i];
for (int j = 0; j < chunk.num_layers; j++) {
const auto layer = chunk.layers[j];
for (int z = 0; z < layer.num_objects; z++) {
const auto object = layer.objects[z];
switch (object.data.tag) {
case physis_LayerEntry::Tag::BG: {
std::string assetPath = object.data.bg._0.asset_path;
if (!assetPath.empty()) {
auto plateMdlFile = physis_gamedata_extract_file(m_data, assetPath.c_str());
if (plateMdlFile.size == 0) {
continue;
}
auto plateMdl = physis_mdl_parse(plateMdlFile);
if (plateMdl.p_ptr != nullptr) {
std::vector<physis_Material> materials;
for (uint32_t j = 0; j < plateMdl.num_material_names; j++) {
const char *material_name = plateMdl.material_names[j];
auto mat = physis_material_parse(m_cache.lookupFile(QLatin1String(material_name)));
materials.push_back(mat);
}
mdlPart->addModel(
plateMdl,
false,
glm::vec3(object.transform.translation[0], object.transform.translation[1], object.transform.translation[2]),
QString::fromStdString(assetPath),
materials,
0);
numLoadedBgModels++;
}
}
} break;
}
}
}
}
}
});
}
MDLPart &MapView::part() const
@ -33,20 +100,18 @@ MDLPart &MapView::part() const
void MapView::addTerrain(QString basePath, physis_Terrain terrain)
{
mdlPart->clear();
for (int i = 0; i < terrain.num_plates; i++) {
QString mdlPath = QStringLiteral("%1%2").arg(basePath, QString::fromStdString(terrain.plates[i].filename));
std::string mdlPathStd = mdlPath.toStdString();
auto plateMdlFile = physis_gamedata_extract_file(data, mdlPathStd.c_str());
auto plateMdlFile = physis_gamedata_extract_file(m_data, mdlPathStd.c_str());
auto plateMdl = physis_mdl_parse(plateMdlFile);
if (plateMdl.p_ptr != nullptr) {
std::vector<physis_Material> materials;
for (uint32_t j = 0; j < plateMdl.num_material_names; j++) {
const char *material_name = plateMdl.material_names[j];
auto mat = physis_material_parse(cache.lookupFile(QLatin1String(material_name)));
auto mat = physis_material_parse(m_cache.lookupFile(QLatin1String(material_name)));
materials.push_back(mat);
}

2
extern/libphysis vendored

@ -1 +1 @@
Subproject commit f0b53912ef8ef9361aaede623ae0a38d1bd833bb
Subproject commit 5946c315e39f6909b71787bc344ba0d879479d73

View file

@ -50,25 +50,25 @@ MDLPart::MDLPart(GameData *data, FileCache &cache, QWidget *parent)
void MDLPart::exportModel(const QString &fileName)
{
auto &model = models[0];
::exportModel(model.name, model.model, *skeleton, boneData, fileName);
auto &model = vkWindow->models[0];
::exportModel(model.name, model.sourceObject->model, *skeleton, boneData, fileName);
}
DrawObject &MDLPart::getModel(const int index)
{
return models[index];
return *vkWindow->models[index].sourceObject;
}
void MDLPart::reloadModel(const int index)
{
renderer->reloadDrawObject(models[index], 0);
renderer->reloadDrawObject(*vkWindow->models[index].sourceObject, 0);
Q_EMIT modelChanged();
}
void MDLPart::clear()
{
models.clear();
vkWindow->models.clear();
Q_EMIT modelChanged();
}
@ -82,24 +82,27 @@ void MDLPart::addModel(physis_MDL mdl,
uint16_t fromBodyId,
uint16_t toBodyId)
{
qDebug() << "Adding model to MDLPart";
DrawObject *model;
if (vkWindow->sourceModels.contains(name)) {
model = vkWindow->sourceModels[name];
} else {
model = renderer->addDrawObject(mdl, lod);
model->from_body_id = fromBodyId;
model->to_body_id = toBodyId;
model->skinned = skinned;
auto model = renderer->addDrawObject(mdl, lod);
model.name = name;
model.from_body_id = fromBodyId;
model.to_body_id = toBodyId;
model.position = position;
model.skinned = skinned;
std::transform(materials.begin(), materials.end(), std::back_inserter(model->materials), [this](const physis_Material &mat) {
return createOrCacheMaterial(mat);
});
std::transform(materials.begin(), materials.end(), std::back_inserter(model.materials), [this](const physis_Material &mat) {
return createOrCacheMaterial(mat);
});
if (materials.empty()) {
model->materials.push_back(createOrCacheMaterial(physis_Material{}));
}
if (materials.empty()) {
model.materials.push_back(createOrCacheMaterial(physis_Material{}));
vkWindow->sourceModels[name] = model;
}
models.push_back(model);
vkWindow->models.push_back(DrawObjectInstance{name, model, position});
Q_EMIT modelChanged();
}
@ -125,8 +128,6 @@ void MDLPart::clearSkeleton()
void MDLPart::reloadRenderer()
{
reloadBoneData();
vkWindow->models = models;
}
void MDLPart::enableFreemode()
@ -166,31 +167,32 @@ void MDLPart::reloadBoneData()
// update data
calculateBone(*skeleton, *skeleton->root_bone, nullptr);
for (auto &model : models) {
// TODO: this applies to all instances
for (auto &[_, model] : vkWindow->sourceModels) {
// we want to map the actual affected bones to bone ids
std::map<int, int> boneMapping;
for (uint32_t i = 0; i < model.model.num_affected_bones; i++) {
for (uint32_t i = 0; i < model->model.num_affected_bones; i++) {
for (uint32_t k = 0; k < skeleton->num_bones; k++) {
if (std::string_view{skeleton->bones[k].name} == std::string_view{model.model.affected_bone_names[i]}) {
if (std::string_view{skeleton->bones[k].name} == std::string_view{model->model.affected_bone_names[i]}) {
boneMapping[i] = k;
}
}
}
std::vector<glm::mat4> deformBones(model.model.num_affected_bones);
for (uint32_t i = 0; i < model.model.num_affected_bones; i++) {
std::vector<glm::mat4> deformBones(model->model.num_affected_bones);
for (uint32_t i = 0; i < model->model.num_affected_bones; i++) {
deformBones[i] = glm::mat4(1.0f);
}
// get deform matrices
if (enableRacialDeform) {
auto deform = physis_pbd_get_deform_matrix(pbd, model.from_body_id, model.to_body_id);
auto deform = physis_pbd_get_deform_matrix(pbd, model->from_body_id, model->to_body_id);
if (deform.num_bones != 0) {
for (int i = 0; i < deform.num_bones; i++) {
auto deformBone = deform.bones[i];
for (uint32_t k = 0; k < model.model.num_affected_bones; k++) {
if (std::string_view{model.model.affected_bone_names[k]} == std::string_view{deformBone.name}) {
for (uint32_t k = 0; k < model->model.num_affected_bones; k++) {
if (std::string_view{model->model.affected_bone_names[k]} == std::string_view{deformBone.name}) {
deformBones[k] =
glm::rowMajor4(glm::vec4{deformBone.deform[0], deformBone.deform[1], deformBone.deform[2], deformBone.deform[3]},
glm::vec4{deformBone.deform[4], deformBone.deform[5], deformBone.deform[6], deformBone.deform[7]},
@ -202,9 +204,9 @@ void MDLPart::reloadBoneData()
}
}
for (uint32_t i = 0; i < model.model.num_affected_bones; i++) {
for (uint32_t i = 0; i < model->model.num_affected_bones; i++) {
const int originalBoneId = boneMapping[i];
model.boneData[i] = deformBones[i] * boneData[originalBoneId].localTransform * boneData[originalBoneId].inversePose;
model->boneData[i] = deformBones[i] * boneData[originalBoneId].localTransform * boneData[originalBoneId].inversePose;
}
}
}
@ -397,12 +399,14 @@ void MDLPart::calculateBone(physis_Skeleton &skeleton, physis_Bone &bone, const
void MDLPart::removeModel(const physis_MDL &mdl)
{
models.erase(std::remove_if(models.begin(),
models.end(),
// TODO: restore this functionality
qWarning() << "MDLPart::removeModel needs to be reimplemented!";
/*vkWindow->models.erase(std::remove_if(vkWindow->models.begin(),
vkWindow->models.end(),
[mdl](const DrawObject &other) {
return mdl.p_ptr == other.model.p_ptr;
}),
models.end());
vkWindow->models.end());*/
Q_EMIT modelChanged();
}
@ -420,7 +424,7 @@ bool MDLPart::wireframe() const
int MDLPart::numModels() const
{
return models.size();
return vkWindow->models.size();
}
RenderManager *MDLPart::manager() const

View file

@ -102,10 +102,9 @@ private:
GameData *data = nullptr;
FileCache &cache;
std::vector<DrawObject> models;
std::unordered_map<uint64_t, RenderMaterial> renderMaterialCache;
RenderManager *renderer = nullptr;
VulkanWindow *vkWindow = nullptr;
bool firstTimeSkeletonDataCalculated = false;
};
};

View file

@ -20,7 +20,8 @@ public:
void render();
std::vector<DrawObject> models;
std::vector<DrawObjectInstance> models;
std::unordered_map<QString, DrawObject *> sourceModels;
bool freeMode = false;
private:
@ -29,4 +30,4 @@ private:
QVulkanInstance *m_instance;
MDLPart *part;
bool pressed_keys[4] = {};
};
};

View file

@ -14,7 +14,7 @@
#include <vulkan/vulkan.h>
class Renderer;
struct DrawObject;
struct DrawObjectInstance;
struct Camera;
struct Texture;
struct Scene;
@ -31,10 +31,10 @@ public:
virtual void resize() = 0;
/// Render a frame into @p commandBuffer. @p currentFrame is the same value as SwapChain::currentFrame for convenience.
virtual void render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObject> &models) = 0;
virtual void render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObjectInstance> &models) = 0;
/// The final composite texture that is drawn into with render()
virtual Texture &getCompositeTexture() = 0;
virtual Device &device() = 0;
};
};

View file

@ -34,13 +34,10 @@ struct RenderMaterial {
};
struct DrawObject {
QString name;
physis_MDL model;
std::vector<RenderPart> parts;
std::array<glm::mat4, 768> boneData; // JOINT_MATRIX_SIZE_DAWNTRAIL
std::vector<RenderMaterial> materials;
glm::vec3 position;
bool skinned = false;
uint16_t from_body_id = 101;
@ -48,3 +45,9 @@ struct DrawObject {
Buffer boneInfoBuffer;
};
struct DrawObjectInstance {
QString name;
DrawObject *sourceObject = nullptr;
glm::vec3 position;
};

View file

@ -31,7 +31,7 @@ public:
void resize() override;
void render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObject> &models) override;
void render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObjectInstance> &models) override;
Texture &getCompositeTexture() override;

View file

@ -33,11 +33,11 @@ public:
void destroySwapchain();
DrawObject addDrawObject(const physis_MDL &model, int lod);
DrawObject *addDrawObject(const physis_MDL &model, int lod);
void reloadDrawObject(DrawObject &model, uint32_t lod);
Texture addGameTexture(VkFormat format, physis_Texture gameTexture);
void render(const std::vector<DrawObject> &models);
void render(const std::vector<DrawObjectInstance> &models);
VkRenderPass presentationRenderPass() const;
@ -74,4 +74,4 @@ private:
BaseRenderer *m_renderer = nullptr;
GameData *m_data = nullptr;
std::vector<RendererPass *> m_passes;
};
};

View file

@ -30,7 +30,7 @@ public:
void resize() override;
void render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObject> &models) override;
void render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObjectInstance> &models) override;
Texture &getCompositeTexture() override;
@ -70,4 +70,4 @@ private:
Texture m_compositeTexture;
Device &m_device;
};
};

View file

@ -255,7 +255,7 @@ GameRenderer::GameRenderer(Device &device, GameData *data)
createImageResources();
}
void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObject> &models)
void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObjectInstance> &models)
{
Q_UNUSED(scene)
@ -299,29 +299,29 @@ void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &
const int jointMatrixSize = m_dawntrailMode ? JOINT_MATRIX_SIZE_DAWNTRAIL : JOINT_MATRIX_SIZE_ARR;
const size_t bufferSize = sizeof(glm::mat3x4) * jointMatrixSize;
void *mapped_data = nullptr;
vkMapMemory(m_device.device, model.boneInfoBuffer.memory, 0, bufferSize, 0, &mapped_data);
vkMapMemory(m_device.device, model.sourceObject->boneInfoBuffer.memory, 0, bufferSize, 0, &mapped_data);
std::vector<glm::mat3x4> newBoneData(model.boneData.size());
std::vector<glm::mat3x4> newBoneData(model.sourceObject->boneData.size());
for (int i = 0; i < jointMatrixSize; i++) {
newBoneData[i] = glm::transpose(model.boneData[i]);
newBoneData[i] = glm::transpose(model.sourceObject->boneData[i]);
}
memcpy(mapped_data, newBoneData.data(), bufferSize);
VkMappedMemoryRange range = {};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.memory = model.boneInfoBuffer.memory;
range.memory = model.sourceObject->boneInfoBuffer.memory;
range.size = bufferSize;
vkFlushMappedMemoryRanges(m_device.device, 1, &range);
vkUnmapMemory(m_device.device, model.boneInfoBuffer.memory);
vkUnmapMemory(m_device.device, model.sourceObject->boneInfoBuffer.memory);
}
for (const auto &part : model.parts) {
RenderMaterial renderMaterial = model.materials[part.materialIndex];
for (const auto &part : model.sourceObject->parts) {
RenderMaterial renderMaterial = model.sourceObject->materials[part.materialIndex];
if (static_cast<size_t>(part.materialIndex + 1) > model.materials.size()) {
renderMaterial = model.materials[0]; // TODO: better fallback
if (static_cast<size_t>(part.materialIndex + 1) > model.sourceObject->materials.size()) {
renderMaterial = model.sourceObject->materials[0]; // TODO: better fallback
}
if (renderMaterial.shaderPackage.p_ptr == nullptr) {
@ -341,7 +341,7 @@ void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &
bool found = false;
if (id == physis_shpk_crc("TransformView")) {
if (model.skinned) {
if (model.sourceObject->skinned) {
sceneKeys.push_back(physis_shpk_crc("TransformViewSkin"));
} else {
sceneKeys.push_back(physis_shpk_crc("TransformViewRigid"));
@ -400,9 +400,14 @@ void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &
physis_Shader vertexShader = renderMaterial.shaderPackage.vertex_shaders[vertexShaderIndice];
physis_Shader pixelShader = renderMaterial.shaderPackage.pixel_shaders[pixelShaderIndice];
auto &pipeline =
bindPipeline(commandBuffer, pass, vertexShader, pixelShader, renderMaterial.mat.shpk_name, &model.model, &part.originalPart);
bindDescriptorSets(commandBuffer, pipeline, &model, &renderMaterial, pass);
auto &pipeline = bindPipeline(commandBuffer,
pass,
vertexShader,
pixelShader,
renderMaterial.mat.shpk_name,
&model.sourceObject->model,
&part.originalPart);
bindDescriptorSets(commandBuffer, pipeline, model.sourceObject, &renderMaterial, pass);
VkDeviceSize offsets[] = {0};
for (int j = 0; j < part.originalPart.num_streams; j++) {
@ -541,11 +546,11 @@ void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &
beginPass(commandBuffer, pass);
for (auto &model : models) {
for (const auto &part : model.parts) {
RenderMaterial renderMaterial = model.materials[part.materialIndex];
for (const auto &part : model.sourceObject->parts) {
RenderMaterial renderMaterial = model.sourceObject->materials[part.materialIndex];
if (static_cast<size_t>(part.materialIndex + 1) > model.materials.size()) {
renderMaterial = model.materials[0]; // TODO: better fallback
if (static_cast<size_t>(part.materialIndex + 1) > model.sourceObject->materials.size()) {
renderMaterial = model.sourceObject->materials[0]; // TODO: better fallback
}
if (renderMaterial.shaderPackage.p_ptr == nullptr) {
@ -564,7 +569,7 @@ void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &
bool found = false;
if (id == physis_shpk_crc("TransformView")) {
if (model.skinned) {
if (model.sourceObject->skinned) {
sceneKeys.push_back(physis_shpk_crc("TransformViewSkin"));
} else {
sceneKeys.push_back(physis_shpk_crc("TransformViewRigid"));
@ -623,9 +628,14 @@ void GameRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &
physis_Shader vertexShader = renderMaterial.shaderPackage.vertex_shaders[vertexShaderIndice];
physis_Shader pixelShader = renderMaterial.shaderPackage.pixel_shaders[pixelShaderIndice];
auto &pipeline =
bindPipeline(commandBuffer, pass, vertexShader, pixelShader, renderMaterial.mat.shpk_name, &model.model, &part.originalPart);
bindDescriptorSets(commandBuffer, pipeline, &model, &renderMaterial, pass);
auto &pipeline = bindPipeline(commandBuffer,
pass,
vertexShader,
pixelShader,
renderMaterial.mat.shpk_name,
&model.sourceObject->model,
&part.originalPart);
bindDescriptorSets(commandBuffer, pipeline, model.sourceObject, &renderMaterial, pass);
VkDeviceSize offsets[] = {0};
for (int j = 0; j < part.originalPart.num_streams; j++) {

View file

@ -397,7 +397,7 @@ void RenderManager::destroySwapchain()
}
}
void RenderManager::render(const std::vector<DrawObject> &models)
void RenderManager::render(const std::vector<DrawObjectInstance> &models)
{
vkWaitForFences(m_device->device,
1,
@ -509,12 +509,12 @@ VkRenderPass RenderManager::presentationRenderPass() const
return m_renderPass;
}
DrawObject RenderManager::addDrawObject(const physis_MDL &model, int lod)
DrawObject *RenderManager::addDrawObject(const physis_MDL &model, int lod)
{
DrawObject DrawObject;
DrawObject.model = model;
auto DrawObject = new ::DrawObject();
DrawObject->model = model;
reloadDrawObject(DrawObject, lod);
reloadDrawObject(*DrawObject, lod);
return DrawObject;
}

View file

@ -49,7 +49,7 @@ void SimpleRenderer::resize()
m_device.nameObject(VK_OBJECT_TYPE_FRAMEBUFFER, reinterpret_cast<uint64_t>(m_framebuffer), "simple renderer framebuffer");
}
void SimpleRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObject> &models)
void SimpleRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene &scene, const std::vector<DrawObjectInstance> &models)
{
Q_UNUSED(scene)
@ -72,7 +72,7 @@ void SimpleRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
for (const auto &model : models) {
if (model.skinned) {
if (model.sourceObject->skinned) {
if (m_wireframe) {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_skinnedPipelineWireframe);
} else {
@ -90,33 +90,33 @@ void SimpleRenderer::render(VkCommandBuffer commandBuffer, Camera &camera, Scene
{
const size_t bufferSize = sizeof(glm::mat4) * 128;
void *mapped_data = nullptr;
vkMapMemory(m_device.device, model.boneInfoBuffer.memory, 0, bufferSize, 0, &mapped_data);
vkMapMemory(m_device.device, model.sourceObject->boneInfoBuffer.memory, 0, bufferSize, 0, &mapped_data);
memcpy(mapped_data, model.boneData.data(), bufferSize);
memcpy(mapped_data, model.sourceObject->boneData.data(), bufferSize);
VkMappedMemoryRange range = {};
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range.memory = model.boneInfoBuffer.memory;
range.memory = model.sourceObject->boneInfoBuffer.memory;
range.size = bufferSize;
vkFlushMappedMemoryRanges(m_device.device, 1, &range);
vkUnmapMemory(m_device.device, model.boneInfoBuffer.memory);
vkUnmapMemory(m_device.device, model.sourceObject->boneInfoBuffer.memory);
}
for (const auto &part : model.parts) {
for (const auto &part : model.sourceObject->parts) {
RenderMaterial defaultMaterial = {};
RenderMaterial *material = nullptr;
if (static_cast<size_t>(part.materialIndex) >= model.materials.size()) {
if (static_cast<size_t>(part.materialIndex) >= model.sourceObject->materials.size()) {
material = &defaultMaterial;
} else {
material = const_cast<RenderMaterial *>(&model.materials[part.materialIndex]);
material = const_cast<RenderMaterial *>(&model.sourceObject->materials[part.materialIndex]);
}
const auto h = hash(model, *material);
const auto h = hash(*model.sourceObject, *material);
if (!cachedDescriptors.count(h)) {
if (auto descriptor = createDescriptorFor(model, *material); descriptor != VK_NULL_HANDLE) {
if (auto descriptor = createDescriptorFor(*model.sourceObject, *material); descriptor != VK_NULL_HANDLE) {
cachedDescriptors[h] = descriptor;
} else {
continue;