ADD: Texture Cache
This commit is contained in:
@@ -32,6 +32,8 @@ add_executable (vulkan_engine
|
|||||||
core/vk_pipeline_manager.cpp
|
core/vk_pipeline_manager.cpp
|
||||||
core/frame_resources.h
|
core/frame_resources.h
|
||||||
core/frame_resources.cpp
|
core/frame_resources.cpp
|
||||||
|
core/texture_cache.h
|
||||||
|
core/texture_cache.cpp
|
||||||
core/config.h
|
core/config.h
|
||||||
core/vk_engine.h
|
core/vk_engine.h
|
||||||
core/vk_engine.cpp
|
core/vk_engine.cpp
|
||||||
|
|||||||
@@ -30,11 +30,12 @@ struct SDL_Window;
|
|||||||
class AssetManager;
|
class AssetManager;
|
||||||
class RenderGraph;
|
class RenderGraph;
|
||||||
class RayTracingManager;
|
class RayTracingManager;
|
||||||
|
class TextureCache;
|
||||||
|
|
||||||
struct ShadowSettings
|
struct ShadowSettings
|
||||||
{
|
{
|
||||||
// 0 = Clipmap only, 1 = Clipmap + RT assist, 2 = RT only
|
// 0 = Clipmap only, 1 = Clipmap + RT assist, 2 = RT only
|
||||||
uint32_t mode = 0;
|
uint32_t mode = 2;
|
||||||
bool hybridRayQueryEnabled = false; // derived convenience: (mode != 0)
|
bool hybridRayQueryEnabled = false; // derived convenience: (mode != 0)
|
||||||
uint32_t hybridRayCascadesMask = 0b1110; // bit i => cascade i uses ray query assist (default: 1..3)
|
uint32_t hybridRayCascadesMask = 0b1110; // bit i => cascade i uses ray query assist (default: 1..3)
|
||||||
float hybridRayNoLThreshold = 0.25f; // trigger when N·L below this (mode==1)
|
float hybridRayNoLThreshold = 0.25f; // trigger when N·L below this (mode==1)
|
||||||
@@ -56,6 +57,7 @@ public:
|
|||||||
|
|
||||||
// Per-frame and subsystem pointers for modules to use without VulkanEngine
|
// Per-frame and subsystem pointers for modules to use without VulkanEngine
|
||||||
FrameResources* currentFrame = nullptr; // set by engine each frame
|
FrameResources* currentFrame = nullptr; // set by engine each frame
|
||||||
|
uint32_t frameIndex = 0; // incremented by engine each frame
|
||||||
EngineStats* stats = nullptr; // points to engine stats
|
EngineStats* stats = nullptr; // points to engine stats
|
||||||
ComputeManager* compute = nullptr; // compute subsystem
|
ComputeManager* compute = nullptr; // compute subsystem
|
||||||
PipelineManager* pipelines = nullptr; // graphics pipeline manager
|
PipelineManager* pipelines = nullptr; // graphics pipeline manager
|
||||||
@@ -90,4 +92,7 @@ public:
|
|||||||
// Convenience alias (singular) requested
|
// Convenience alias (singular) requested
|
||||||
AssetManager* getAsset() const { return assets; }
|
AssetManager* getAsset() const { return assets; }
|
||||||
RenderGraph* getRenderGraph() const { return renderGraph; }
|
RenderGraph* getRenderGraph() const { return renderGraph; }
|
||||||
|
|
||||||
|
// Streaming subsystems (engine-owned)
|
||||||
|
TextureCache* textures = nullptr; // texture streaming + cache
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,6 +51,44 @@
|
|||||||
#include "core/vk_pipeline_manager.h"
|
#include "core/vk_pipeline_manager.h"
|
||||||
#include "core/config.h"
|
#include "core/config.h"
|
||||||
|
|
||||||
|
// Query a conservative streaming texture budget based on VMA-reported
|
||||||
|
// device-local heap budgets. Uses ~35% of total device-local budget.
|
||||||
|
static size_t query_texture_budget_bytes(DeviceManager* dev)
|
||||||
|
{
|
||||||
|
if (!dev) return 512ull * 1024ull * 1024ull; // fallback
|
||||||
|
VmaAllocator alloc = dev->allocator();
|
||||||
|
if (!alloc) return 512ull * 1024ull * 1024ull;
|
||||||
|
|
||||||
|
const VkPhysicalDeviceMemoryProperties* memProps = nullptr;
|
||||||
|
vmaGetMemoryProperties(alloc, &memProps);
|
||||||
|
if (!memProps) return 512ull * 1024ull * 1024ull;
|
||||||
|
|
||||||
|
VmaBudget budgets[VK_MAX_MEMORY_HEAPS] = {};
|
||||||
|
vmaGetHeapBudgets(alloc, budgets);
|
||||||
|
|
||||||
|
unsigned long long totalBudget = 0;
|
||||||
|
unsigned long long totalUsage = 0;
|
||||||
|
for (uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
|
||||||
|
{
|
||||||
|
if (memProps->memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
|
||||||
|
{
|
||||||
|
totalBudget += budgets[i].budget;
|
||||||
|
totalUsage += budgets[i].usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalBudget == 0) return 512ull * 1024ull * 1024ull;
|
||||||
|
|
||||||
|
// Reserve ~65% of VRAM for attachments, swapchain, meshes, AS, etc.
|
||||||
|
unsigned long long cap = static_cast<unsigned long long>(double(totalBudget) * 0.35);
|
||||||
|
|
||||||
|
// If usage is already near the cap, still allow current textures to live; eviction will trim.
|
||||||
|
// Clamp to at least 128 MB, at most totalBudget.
|
||||||
|
unsigned long long minCap = 128ull * 1024ull * 1024ull;
|
||||||
|
if (cap < minCap) cap = minCap;
|
||||||
|
if (cap > totalBudget) cap = totalBudget;
|
||||||
|
return static_cast<size_t>(cap);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// ImGui helpers: keep UI code tidy and grouped in small functions.
|
// ImGui helpers: keep UI code tidy and grouped in small functions.
|
||||||
// These render inside a single consolidated Debug window using tab items.
|
// These render inside a single consolidated Debug window using tab items.
|
||||||
@@ -416,6 +454,11 @@ void VulkanEngine::init()
|
|||||||
_assetManager->init(this);
|
_assetManager->init(this);
|
||||||
_context->assets = _assetManager.get();
|
_context->assets = _assetManager.get();
|
||||||
|
|
||||||
|
// Create texture cache (engine-owned, accessible via EngineContext)
|
||||||
|
_textureCache = std::make_unique<TextureCache>();
|
||||||
|
_textureCache->init(_context.get());
|
||||||
|
_context->textures = _textureCache.get();
|
||||||
|
|
||||||
// Optional ray tracing manager if supported and extensions enabled
|
// Optional ray tracing manager if supported and extensions enabled
|
||||||
if (_deviceManager->supportsRayQuery() && _deviceManager->supportsAccelerationStructure())
|
if (_deviceManager->supportsRayQuery() && _deviceManager->supportsAccelerationStructure())
|
||||||
{
|
{
|
||||||
@@ -568,6 +611,8 @@ void VulkanEngine::cleanup()
|
|||||||
print_vma_stats(_deviceManager.get(), "after MainDQ flush");
|
print_vma_stats(_deviceManager.get(), "after MainDQ flush");
|
||||||
dump_vma_json(_deviceManager.get(), "after_MainDQ");
|
dump_vma_json(_deviceManager.get(), "after_MainDQ");
|
||||||
|
|
||||||
|
if (_textureCache) { _textureCache->cleanup(); }
|
||||||
|
|
||||||
_renderPassManager->cleanup();
|
_renderPassManager->cleanup();
|
||||||
print_vma_stats(_deviceManager.get(), "after RenderPassManager");
|
print_vma_stats(_deviceManager.get(), "after RenderPassManager");
|
||||||
dump_vma_json(_deviceManager.get(), "after_RenderPassManager");
|
dump_vma_json(_deviceManager.get(), "after_RenderPassManager");
|
||||||
@@ -673,8 +718,12 @@ void VulkanEngine::draw()
|
|||||||
|
|
||||||
// publish per-frame pointers and draw extent to context for passes
|
// publish per-frame pointers and draw extent to context for passes
|
||||||
_context->currentFrame = &get_current_frame();
|
_context->currentFrame = &get_current_frame();
|
||||||
|
_context->frameIndex = static_cast<uint32_t>(_frameNumber);
|
||||||
_context->drawExtent = _drawExtent;
|
_context->drawExtent = _drawExtent;
|
||||||
|
|
||||||
|
// Inform VMA of current frame for improved internal stats/aging (optional).
|
||||||
|
vmaSetCurrentFrameIndex(_deviceManager->allocator(), _context->frameIndex);
|
||||||
|
|
||||||
// Optional: check for shader changes and hot-reload pipelines
|
// Optional: check for shader changes and hot-reload pipelines
|
||||||
if (_pipelineManager)
|
if (_pipelineManager)
|
||||||
{
|
{
|
||||||
@@ -702,6 +751,14 @@ void VulkanEngine::draw()
|
|||||||
hShadowCascades[i] = _renderGraph->create_depth_image(name.c_str(), shadowExtent, VK_FORMAT_D32_SFLOAT);
|
hShadowCascades[i] = _renderGraph->create_depth_image(name.c_str(), shadowExtent, VK_FORMAT_D32_SFLOAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prior to building passes, pump texture loads for this frame.
|
||||||
|
if (_textureCache)
|
||||||
|
{
|
||||||
|
size_t budget = query_texture_budget_bytes(_deviceManager.get());
|
||||||
|
_textureCache->evictToBudget(budget);
|
||||||
|
_textureCache->pumpLoads(*_resourceManager, get_current_frame());
|
||||||
|
}
|
||||||
|
|
||||||
_resourceManager->register_upload_pass(*_renderGraph, get_current_frame());
|
_resourceManager->register_upload_pass(*_renderGraph, get_current_frame());
|
||||||
|
|
||||||
ImGuiPass *imguiPass = nullptr;
|
ImGuiPass *imguiPass = nullptr;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "core/asset_manager.h"
|
#include "core/asset_manager.h"
|
||||||
#include "render/rg_graph.h"
|
#include "render/rg_graph.h"
|
||||||
#include "core/vk_raytracing.h"
|
#include "core/vk_raytracing.h"
|
||||||
|
#include "core/texture_cache.h"
|
||||||
|
|
||||||
// Number of frames-in-flight. Affects per-frame command buffers, fences,
|
// Number of frames-in-flight. Affects per-frame command buffers, fences,
|
||||||
// semaphores, and transient descriptor pools in FrameResources.
|
// semaphores, and transient descriptor pools in FrameResources.
|
||||||
@@ -67,6 +68,7 @@ public:
|
|||||||
std::unique_ptr<AssetManager> _assetManager;
|
std::unique_ptr<AssetManager> _assetManager;
|
||||||
std::unique_ptr<RenderGraph> _renderGraph;
|
std::unique_ptr<RenderGraph> _renderGraph;
|
||||||
std::unique_ptr<RayTracingManager> _rayManager;
|
std::unique_ptr<RayTracingManager> _rayManager;
|
||||||
|
std::unique_ptr<TextureCache> _textureCache;
|
||||||
|
|
||||||
struct SDL_Window *_window{nullptr};
|
struct SDL_Window *_window{nullptr};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "frame_resources.h"
|
#include "frame_resources.h"
|
||||||
|
#include "texture_cache.h"
|
||||||
#include "vk_descriptor_manager.h"
|
#include "vk_descriptor_manager.h"
|
||||||
#include "vk_device.h"
|
#include "vk_device.h"
|
||||||
#include "core/engine_context.h"
|
#include "core/engine_context.h"
|
||||||
@@ -240,6 +241,10 @@ void GeometryPass::draw_geometry(VkCommandBuffer cmd,
|
|||||||
|
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
||||||
&r.material->materialSet, 0, nullptr);
|
&r.material->materialSet, 0, nullptr);
|
||||||
|
if (ctxLocal->textures)
|
||||||
|
{
|
||||||
|
ctxLocal->textures->markSetUsed(r.material->materialSet, ctxLocal->frameIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (r.indexBuffer != lastIndexBuffer)
|
if (r.indexBuffer != lastIndexBuffer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "texture_cache.h"
|
||||||
#include "vk_scene.h"
|
#include "vk_scene.h"
|
||||||
#include "vk_swapchain.h"
|
#include "vk_swapchain.h"
|
||||||
#include "core/engine_context.h"
|
#include "core/engine_context.h"
|
||||||
@@ -133,6 +135,10 @@ void TransparentPass::draw_transparent(VkCommandBuffer cmd,
|
|||||||
}
|
}
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r.material->pipeline->layout, 1, 1,
|
||||||
&r.material->materialSet, 0, nullptr);
|
&r.material->materialSet, 0, nullptr);
|
||||||
|
if (ctxLocal->textures)
|
||||||
|
{
|
||||||
|
ctxLocal->textures->markSetUsed(r.material->materialSet, ctxLocal->frameIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (r.indexBuffer != lastIndexBuffer)
|
if (r.indexBuffer != lastIndexBuffer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "stb_image.h"
|
#include "stb_image.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "vk_loader.h"
|
#include "vk_loader.h"
|
||||||
|
#include "core/texture_cache.h"
|
||||||
|
|
||||||
#include "core/vk_engine.h"
|
#include "core/vk_engine.h"
|
||||||
#include "render/vk_materials.h"
|
#include "render/vk_materials.h"
|
||||||
@@ -260,40 +261,58 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
// temporal arrays for all the objects to use while creating the GLTF data
|
// temporal arrays for all the objects to use while creating the GLTF data
|
||||||
std::vector<std::shared_ptr<MeshAsset> > meshes;
|
std::vector<std::shared_ptr<MeshAsset> > meshes;
|
||||||
std::vector<std::shared_ptr<Node> > nodes;
|
std::vector<std::shared_ptr<Node> > nodes;
|
||||||
std::vector<AllocatedImage> images;
|
|
||||||
std::vector<std::shared_ptr<GLTFMaterial> > materials;
|
std::vector<std::shared_ptr<GLTFMaterial> > materials;
|
||||||
//< load_arrays
|
//< load_arrays
|
||||||
|
|
||||||
// load all textures
|
// Note: glTF images are now loaded on-demand via TextureCache.
|
||||||
for (size_t i = 0; i < gltf.images.size(); ++i)
|
auto buildTextureKey = [&](size_t imgIndex, bool srgb) -> TextureCache::TextureKey
|
||||||
{
|
{
|
||||||
fastgltf::Image &image = gltf.images[i];
|
TextureCache::TextureKey key{};
|
||||||
// Default-load GLTF images as linear; baseColor is reloaded as sRGB when bound
|
key.srgb = srgb;
|
||||||
std::optional<AllocatedImage> img = load_image(engine, gltf, image, false);
|
key.mipmapped = true;
|
||||||
|
if (imgIndex >= gltf.images.size())
|
||||||
if (img.has_value())
|
|
||||||
{
|
{
|
||||||
images.push_back(*img);
|
key.hash = 0; // invalid
|
||||||
// Use a unique, stable key so every allocation is tracked and later freed.
|
return key;
|
||||||
std::string key = image.name.empty() ? (std::string("gltf.image.") + std::to_string(i))
|
}
|
||||||
: std::string(image.name.c_str());
|
fastgltf::Image &image = gltf.images[imgIndex];
|
||||||
// Avoid accidental collisions from duplicate names
|
std::visit(fastgltf::visitor{
|
||||||
int suffix = 1;
|
[&](fastgltf::sources::URI &filePath)
|
||||||
while (file.images.find(key) != file.images.end())
|
|
||||||
{
|
{
|
||||||
key = (image.name.empty() ? std::string("gltf.image.") + std::to_string(i)
|
const std::string path(filePath.uri.path().begin(), filePath.uri.path().end());
|
||||||
: std::string(image.name.c_str())) + std::string("#") + std::to_string(suffix++);
|
key.kind = TextureCache::TextureKey::SourceKind::FilePath;
|
||||||
}
|
key.path = path;
|
||||||
file.images[key] = *img;
|
std::string id = std::string("GLTF:") + path + (srgb ? "#sRGB" : "#UNORM");
|
||||||
}
|
key.hash = texcache::fnv1a64(id);
|
||||||
else
|
},
|
||||||
|
[&](fastgltf::sources::Vector &vector)
|
||||||
{
|
{
|
||||||
// we failed to load, so lets give the slot a default white texture to not
|
key.kind = TextureCache::TextureKey::SourceKind::Bytes;
|
||||||
// completely break loading
|
key.bytes.assign(vector.bytes.begin(), vector.bytes.end());
|
||||||
images.push_back(engine->_errorCheckerboardImage);
|
uint64_t h = texcache::fnv1a64(key.bytes.data(), key.bytes.size());
|
||||||
std::cout << "gltf failed to load texture index " << i << " (name='" << image.name << "')" << std::endl;
|
key.hash = h ^ (srgb ? 0x9E3779B97F4A7C15ull : 0ull);
|
||||||
}
|
},
|
||||||
|
[&](fastgltf::sources::BufferView &view)
|
||||||
|
{
|
||||||
|
auto &bufferView = gltf.bufferViews[view.bufferViewIndex];
|
||||||
|
auto &buffer = gltf.buffers[bufferView.bufferIndex];
|
||||||
|
std::visit(fastgltf::visitor{
|
||||||
|
[](auto &arg) {},
|
||||||
|
[&](fastgltf::sources::Vector &vec)
|
||||||
|
{
|
||||||
|
size_t off = bufferView.byteOffset;
|
||||||
|
size_t len = bufferView.byteLength;
|
||||||
|
key.kind = TextureCache::TextureKey::SourceKind::Bytes;
|
||||||
|
key.bytes.assign(vec.bytes.begin() + off, vec.bytes.begin() + off + len);
|
||||||
|
uint64_t h = texcache::fnv1a64(key.bytes.data(), key.bytes.size());
|
||||||
|
key.hash = h ^ (srgb ? 0x9E3779B97F4A7C15ull : 0ull);
|
||||||
}
|
}
|
||||||
|
}, buffer.data);
|
||||||
|
},
|
||||||
|
[](auto &other) {}
|
||||||
|
}, image.data);
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
//> load_buffer
|
//> load_buffer
|
||||||
// create buffer to hold the material data
|
// create buffer to hold the material data
|
||||||
@@ -343,90 +362,79 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
|
|||||||
// set the uniform buffer for the material data
|
// set the uniform buffer for the material data
|
||||||
materialResources.dataBuffer = file.materialDataBuffer.buffer;
|
materialResources.dataBuffer = file.materialDataBuffer.buffer;
|
||||||
materialResources.dataBufferOffset = data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants);
|
materialResources.dataBufferOffset = data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants);
|
||||||
// grab textures from gltf file
|
// Dynamic texture bindings via TextureCache (fallbacks are already set)
|
||||||
if (mat.pbrData.baseColorTexture.has_value())
|
TextureCache *cache = engine->_context->textures;
|
||||||
|
TextureCache::TextureHandle hColor = TextureCache::InvalidHandle;
|
||||||
|
TextureCache::TextureHandle hMRO = TextureCache::InvalidHandle;
|
||||||
|
TextureCache::TextureHandle hNorm = TextureCache::InvalidHandle;
|
||||||
|
|
||||||
|
if (cache && mat.pbrData.baseColorTexture.has_value())
|
||||||
{
|
{
|
||||||
const auto &tex = gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex];
|
const auto &tex = gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex];
|
||||||
size_t imgIndex = tex.imageIndex.value();
|
const size_t imgIndex = tex.imageIndex.value();
|
||||||
// Sampler is optional in glTF; fall back to default if missing
|
const bool hasSampler = tex.samplerIndex.has_value();
|
||||||
bool hasSampler = tex.samplerIndex.has_value();
|
const VkSampler sampler = hasSampler ? file.samplers[tex.samplerIndex.value()] : engine->_samplerManager->defaultLinear();
|
||||||
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
|
auto key = buildTextureKey(imgIndex, true);
|
||||||
|
if (key.hash != 0)
|
||||||
// Reload albedo as sRGB, independent of the global image cache
|
|
||||||
if (imgIndex < gltf.images.size())
|
|
||||||
{
|
{
|
||||||
auto albedoImg = load_image(engine, gltf, gltf.images[imgIndex], true);
|
hColor = cache->request(key, sampler);
|
||||||
if (albedoImg.has_value())
|
materialResources.colorSampler = sampler;
|
||||||
{
|
|
||||||
materialResources.colorImage = *albedoImg;
|
|
||||||
// Track for cleanup using a unique key
|
|
||||||
std::string key = std::string("albedo_") + mat.name.c_str() + "_" + std::to_string(imgIndex);
|
|
||||||
file.images[key] = *albedoImg;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
materialResources.colorImage = images[imgIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
materialResources.colorImage = engine->_errorCheckerboardImage;
|
|
||||||
}
|
|
||||||
materialResources.colorSampler = hasSampler ? file.samplers[sampler]
|
|
||||||
: engine->_samplerManager->defaultLinear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metallic-Roughness texture
|
if (cache && mat.pbrData.metallicRoughnessTexture.has_value())
|
||||||
if (mat.pbrData.metallicRoughnessTexture.has_value())
|
|
||||||
{
|
{
|
||||||
const auto &tex = gltf.textures[mat.pbrData.metallicRoughnessTexture.value().textureIndex];
|
const auto &tex = gltf.textures[mat.pbrData.metallicRoughnessTexture.value().textureIndex];
|
||||||
size_t imgIndex = tex.imageIndex.value();
|
const size_t imgIndex = tex.imageIndex.value();
|
||||||
bool hasSampler = tex.samplerIndex.has_value();
|
const bool hasSampler = tex.samplerIndex.has_value();
|
||||||
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
|
const VkSampler sampler = hasSampler ? file.samplers[tex.samplerIndex.value()] : engine->_samplerManager->defaultLinear();
|
||||||
if (imgIndex < images.size())
|
auto key = buildTextureKey(imgIndex, false);
|
||||||
|
if (key.hash != 0)
|
||||||
{
|
{
|
||||||
materialResources.metalRoughImage = images[imgIndex];
|
hMRO = cache->request(key, sampler);
|
||||||
materialResources.metalRoughSampler = hasSampler ? file.samplers[sampler]
|
materialResources.metalRoughSampler = sampler;
|
||||||
: engine->_samplerManager->defaultLinear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal map (tangent-space)
|
if (cache && mat.normalTexture.has_value())
|
||||||
if (mat.normalTexture.has_value())
|
|
||||||
{
|
{
|
||||||
const auto &tex = gltf.textures[mat.normalTexture.value().textureIndex];
|
const auto &tex = gltf.textures[mat.normalTexture.value().textureIndex];
|
||||||
size_t imgIndex = tex.imageIndex.value();
|
const size_t imgIndex = tex.imageIndex.value();
|
||||||
bool hasSampler = tex.samplerIndex.has_value();
|
const bool hasSampler = tex.samplerIndex.has_value();
|
||||||
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
|
const VkSampler sampler = hasSampler ? file.samplers[tex.samplerIndex.value()] : engine->_samplerManager->defaultLinear();
|
||||||
|
auto key = buildTextureKey(imgIndex, false);
|
||||||
if (imgIndex < gltf.images.size())
|
if (key.hash != 0)
|
||||||
{
|
{
|
||||||
auto normalImg = load_image(engine, gltf, gltf.images[imgIndex], false);
|
hNorm = cache->request(key, sampler);
|
||||||
if (normalImg.has_value())
|
materialResources.normalSampler = sampler;
|
||||||
{
|
|
||||||
materialResources.normalImage = *normalImg;
|
|
||||||
std::string key = std::string("normal_") + mat.name.c_str() + "_" + std::to_string(imgIndex);
|
|
||||||
file.images[key] = *normalImg;
|
|
||||||
}
|
}
|
||||||
else
|
// Store normal scale if provided
|
||||||
{
|
|
||||||
materialResources.normalImage = images[imgIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
materialResources.normalImage = engine->_flatNormalImage;
|
|
||||||
}
|
|
||||||
materialResources.normalSampler = hasSampler ? file.samplers[sampler]
|
|
||||||
: engine->_samplerManager->defaultLinear();
|
|
||||||
|
|
||||||
// Store normal scale into material constants extra[0].x if available
|
|
||||||
sceneMaterialConstants[data_index].extra[0].x = mat.normalTexture->scale;
|
sceneMaterialConstants[data_index].extra[0].x = mat.normalTexture->scale;
|
||||||
}
|
}
|
||||||
// build material
|
// build material
|
||||||
newMat->data = engine->metalRoughMaterial.write_material(engine->_deviceManager->device(), passType, materialResources,
|
newMat->data = engine->metalRoughMaterial.write_material(engine->_deviceManager->device(), passType, materialResources,
|
||||||
file.descriptorPool);
|
file.descriptorPool);
|
||||||
|
|
||||||
|
// Register descriptor patches for dynamic textures
|
||||||
|
if (cache)
|
||||||
|
{
|
||||||
|
if (hColor != TextureCache::InvalidHandle)
|
||||||
|
{
|
||||||
|
cache->watchBinding(hColor, newMat->data.materialSet, 1u, materialResources.colorSampler,
|
||||||
|
engine->_whiteImage.imageView);
|
||||||
|
}
|
||||||
|
if (hMRO != TextureCache::InvalidHandle)
|
||||||
|
{
|
||||||
|
cache->watchBinding(hMRO, newMat->data.materialSet, 2u, materialResources.metalRoughSampler,
|
||||||
|
engine->_whiteImage.imageView);
|
||||||
|
}
|
||||||
|
if (hNorm != TextureCache::InvalidHandle)
|
||||||
|
{
|
||||||
|
cache->watchBinding(hNorm, newMat->data.materialSet, 3u, materialResources.normalSampler,
|
||||||
|
engine->_flatNormalImage.imageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data_index++;
|
data_index++;
|
||||||
}
|
}
|
||||||
//< load_material
|
//< load_material
|
||||||
|
|||||||
Reference in New Issue
Block a user