Skip to content

Commit

Permalink
Implemented KHR_materials_variants.
Browse files Browse the repository at this point in the history
  • Loading branch information
stripe2933 committed Dec 27, 2024
1 parent db202f3 commit a776b00
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 14 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ target_sources(vk-gltf-viewer PRIVATE
interface/gltf/AssetProcessError.cppm
interface/gltf/AssetSceneGpuBuffers.cppm
interface/gltf/AssetSceneHierarchy.cppm
interface/gltf/MaterialVariantsMapping.cppm
interface/helpers/concepts.cppm
interface/helpers/fastgltf.cppm
interface/helpers/formatter/ByteSize.cppm
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Blazingly fast[^1] Vulkan glTF viewer.
- Binary format (`.glb`).
- Support glTF 2.0 extensions:
- [`KHR_materials_unlit`](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_unlit) for lighting independent material shading
- [`KHR_materials_variants`](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_variants)
- [`KHR_texture_basisu`](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_basisu) for BC7 GPU compression texture decoding
- [`EXT_mesh_gpu_instancing`](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing) for instancing multiple meshes with the same geometry
- Use 4x MSAA by default.
Expand Down
31 changes: 30 additions & 1 deletion impl/MainApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ void vk_gltf_viewer::MainApp::run() {
std::array<bool, FRAMES_IN_FLIGHT> shouldHandleSwapchainResize{};
std::array<bool, FRAMES_IN_FLIGHT> shouldRegenerateDrawCommands{};

// TODO: we need more general mechanism to upload the GPU buffer data in shared data. This is just a stopgap solution
// for current KHR_materials_variants implementation.
const vk::raii::CommandPool graphicsCommandPool { gpu.device, vk::CommandPoolCreateInfo { {}, gpu.queueFamilies.graphicsPresent } };
const auto [sharedDataUpdateCommandBuffer] = vku::allocateCommandBuffers<1>(*gpu.device, *graphicsCommandPool);
bool hasUpdateData = false;

std::vector<control::Task> tasks;
for (std::uint64_t frameIndex = 0; !glfwWindowShouldClose(window); frameIndex = (frameIndex + 1) % FRAMES_IN_FLIGHT) {
tasks.clear();
Expand All @@ -282,6 +288,9 @@ void vk_gltf_viewer::MainApp::run() {
if (auto &gltfAsset = appState.gltfAsset) {
imguiTaskCollector.assetInspector(gltfAsset->asset, gltf->directory);
imguiTaskCollector.materialEditor(gltfAsset->asset, assetTextureDescriptorSets);
if (!gltfAsset->asset.materialVariants.empty()) {
imguiTaskCollector.materialVariants(gltfAsset->asset);
}
imguiTaskCollector.sceneHierarchy(gltfAsset->asset, gltfAsset->getSceneIndex(), gltfAsset->nodeVisibilities, gltfAsset->hoveringNodeIndex, gltfAsset->selectedNodeIndices);
imguiTaskCollector.nodeInspector(gltfAsset->asset, gltfAsset->selectedNodeIndices);
}
Expand Down Expand Up @@ -513,6 +522,26 @@ void vk_gltf_viewer::MainApp::run() {
[&](control::task::InvalidateDrawCommandSeparation) {
shouldRegenerateDrawCommands.fill(true);
},
[&](const control::task::SelectMaterialVariants &task) {
assert(gltf && "Synchronization error: gltf is unset but material variants are selected.");

gpu.device.waitIdle();

graphicsCommandPool.reset();
sharedDataUpdateCommandBuffer.begin(vk::CommandBufferBeginInfo { vk::CommandBufferUsageFlagBits::eOneTimeSubmit });

for (const auto &[pPrimitive, materialIndex] : gltf->materialVariantsMapping.at(task.variantIndex)) {
pPrimitive->materialIndex.emplace(materialIndex);
hasUpdateData |= gltf->assetGpuBuffers.updatePrimitiveMaterial(*pPrimitive, materialIndex, sharedDataUpdateCommandBuffer);
}

sharedDataUpdateCommandBuffer.end();

if (hasUpdateData) {
gpu.queues.graphicsPresent.submit(vk::SubmitInfo { {}, {}, sharedDataUpdateCommandBuffer });
gpu.device.waitIdle();
}
}
}, task);
}

Expand Down Expand Up @@ -817,7 +846,7 @@ void vk_gltf_viewer::MainApp::loadGltf(const std::filesystem::path &path) {
};
}));
gpu.device.updateDescriptorSets({
sharedData.assetDescriptorSet.getWriteOne<0>({ gltf->assetGpuBuffers.primitiveBuffer, 0, vk::WholeSize }),
sharedData.assetDescriptorSet.getWriteOne<0>({ gltf->assetGpuBuffers.getPrimitiveBuffer(), 0, vk::WholeSize }),
sharedData.assetDescriptorSet.getWriteOne<1>({ gltf->assetGpuBuffers.materialBuffer, 0, vk::WholeSize }),
sharedData.assetDescriptorSet.getWrite<2>(imageInfos),
sharedData.sceneDescriptorSet.getWriteOne<0>({ gltf->sceneGpuBuffers.nodeBuffer, 0, vk::WholeSize }),
Expand Down
19 changes: 19 additions & 0 deletions impl/control/ImGuiTaskCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ void vk_gltf_viewer::control::ImGuiTaskCollector::assetSamplers(std::span<fastgl

// bottomSidebar
ImGui::DockBuilderDockWindow("Material Editor", bottomSidebar);
ImGui::DockBuilderDockWindow("Material Variants", bottomSidebar);

ImGui::DockBuilderFinish(dockSpaceOverViewport);

Expand Down Expand Up @@ -657,6 +658,24 @@ void vk_gltf_viewer::control::ImGuiTaskCollector::materialEditor(
materialEditorCalled = true;
}

void vk_gltf_viewer::control::ImGuiTaskCollector::materialVariants(const fastgltf::Asset &asset) {
assert(!asset.materialVariants.empty());

if (ImGui::Begin("Material Variants")) {
static int selectedMaterialVariantIndex = 0;
if (asset.materialVariants.size() <= selectedMaterialVariantIndex) {
selectedMaterialVariantIndex = 0;
}

for (const auto &[i, variantName] : asset.materialVariants | ranges::views::enumerate) {
if (ImGui::RadioButton(variantName.c_str(), &selectedMaterialVariantIndex, i)) {
tasks.emplace_back(std::in_place_type<task::SelectMaterialVariants>, i);
}
}
}
ImGui::End();
}

void vk_gltf_viewer::control::ImGuiTaskCollector::sceneHierarchy(
fastgltf::Asset &asset,
std::size_t sceneIndex,
Expand Down
37 changes: 28 additions & 9 deletions impl/gltf/AssetGpuBuffers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ import :helpers.ranges;
#define FWD(...) static_cast<decltype(__VA_ARGS__) &&>(__VA_ARGS__)
#define LIFT(...) [&](auto &&...xs) { return (__VA_ARGS__)(FWD(xs)...); }

bool vk_gltf_viewer::gltf::AssetGpuBuffers::updatePrimitiveMaterial(
const fastgltf::Primitive &primitive,
std::uint32_t materialIndex,
vk::CommandBuffer transferCommandBuffer
) {
const std::uint16_t orderedPrimitiveIndex = primitiveInfos.at(&primitive).index;
const std::uint32_t paddedMaterialIndex = padMaterialIndex(materialIndex);
return std::visit(multilambda {
[&](vku::MappedBuffer &primitiveBuffer) {
primitiveBuffer.asRange<GpuPrimitive>()[orderedPrimitiveIndex].materialIndex = paddedMaterialIndex;
return false;
},
[&](vk::Buffer primitiveBuffer) {
transferCommandBuffer.updateBuffer(
primitiveBuffer,
sizeof(GpuPrimitive) * orderedPrimitiveIndex + offsetof(GpuPrimitive, materialIndex),
sizeof(GpuPrimitive::materialIndex),
&paddedMaterialIndex);
return true;
}
}, primitiveBuffer);
}

std::vector<const fastgltf::Primitive*> vk_gltf_viewer::gltf::AssetGpuBuffers::createOrderedPrimitives() const {
return asset.meshes
| std::views::transform(&fastgltf::Mesh::primitives)
Expand Down Expand Up @@ -94,8 +117,8 @@ vku::AllocatedBuffer vk_gltf_viewer::gltf::AssetGpuBuffers::createMaterialBuffer
return dstBuffer;
}

vku::AllocatedBuffer vk_gltf_viewer::gltf::AssetGpuBuffers::createPrimitiveBuffer() {
vku::AllocatedBuffer stagingBuffer = vku::MappedBuffer {
std::variant<vku::AllocatedBuffer, vku::MappedBuffer> vk_gltf_viewer::gltf::AssetGpuBuffers::createPrimitiveBuffer() {
vku::MappedBuffer stagingBuffer {
gpu.allocator,
std::from_range, orderedPrimitives | std::views::transform([this](const fastgltf::Primitive *pPrimitive) {
const AssetPrimitiveInfo &primitiveInfo = primitiveInfos[pPrimitive];
Expand All @@ -114,15 +137,11 @@ vku::AllocatedBuffer vk_gltf_viewer::gltf::AssetGpuBuffers::createPrimitiveBuffe
.positionByteStride = primitiveInfo.positionInfo.byteStride,
.normalByteStride = normalInfo.byteStride,
.tangentByteStride = tangentInfo.byteStride,
.materialIndex
= primitiveInfo.materialIndex.transform([](std::size_t index) {
return 1U /* index 0 is reserved for the fallback material */ + static_cast<std::uint32_t>(index);
})
.value_or(0U),
.materialIndex = primitiveInfo.materialIndex.transform(padMaterialIndex).value_or(0U),
};
}),
gpu.isUmaDevice ? vk::BufferUsageFlagBits::eStorageBuffer : vk::BufferUsageFlagBits::eTransferSrc,
}.unmap();
};

if (gpu.isUmaDevice || vku::contains(gpu.allocator.getAllocationMemoryProperties(stagingBuffer.allocation), vk::MemoryPropertyFlagBits::eDeviceLocal)) {
return stagingBuffer;
Expand All @@ -134,7 +153,7 @@ vku::AllocatedBuffer vk_gltf_viewer::gltf::AssetGpuBuffers::createPrimitiveBuffe
vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferDst,
} };
stagingInfos.emplace_back(
std::move(stagingBuffer),
std::move(stagingBuffer).unmap(),
dstBuffer,
vk::BufferCopy{ 0, 0, dstBuffer.size });
return dstBuffer;
Expand Down
8 changes: 7 additions & 1 deletion interface/MainApp.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import :gltf.AssetGpuTextures;
import :gltf.AssetGpuFallbackTexture;
import :gltf.AssetSceneGpuBuffers;
import :gltf.AssetSceneHierarchy;
import :gltf.MaterialVariantsMapping;
import :vulkan.dsl.Asset;
import :vulkan.dsl.ImageBasedLighting;
import :vulkan.dsl.Scene;
Expand Down Expand Up @@ -46,6 +47,11 @@ namespace vk_gltf_viewer {
*/
fastgltf::Asset asset;

/**
* @brief Associative data structure for KHR_materials_variants.
*/
gltf::MaterialVariantsMapping materialVariantsMapping { asset };

private:
const vulkan::Gpu &gpu;

Expand Down Expand Up @@ -127,7 +133,7 @@ namespace vk_gltf_viewer {
// glTF resources.
// --------------------

fastgltf::Parser parser { fastgltf::Extensions::KHR_materials_unlit | fastgltf::Extensions::KHR_texture_basisu | fastgltf::Extensions::EXT_mesh_gpu_instancing };
fastgltf::Parser parser { fastgltf::Extensions::KHR_materials_unlit | fastgltf::Extensions::KHR_materials_variants | fastgltf::Extensions::KHR_texture_basisu | fastgltf::Extensions::EXT_mesh_gpu_instancing };
std::optional<Gltf> gltf;

// --------------------
Expand Down
1 change: 1 addition & 0 deletions interface/control/ImGuiTaskCollector.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace vk_gltf_viewer::control {
void menuBar(const std::list<std::filesystem::path> &recentGltfs, const std::list<std::filesystem::path> &recentSkyboxes);
void assetInspector(fastgltf::Asset &asset, const std::filesystem::path &assetDir);
void materialEditor(fastgltf::Asset &asset, std::span<const vk::DescriptorSet> assetTextureImGuiDescriptorSets);
void materialVariants(const fastgltf::Asset &asset);
void sceneHierarchy(fastgltf::Asset &asset, std::size_t sceneIndex, const std::variant<std::vector<std::optional<bool>>, std::vector<bool>> &visibilities, const std::optional<std::uint16_t> &hoveringNodeIndex, const std::unordered_set<std::uint16_t> &selectedNodeIndices);
void nodeInspector(fastgltf::Asset &asset, const std::unordered_set<std::uint16_t> &selectedNodeIndices);
void background(bool canSelectSkyboxBackground, full_optional<glm::vec3> &solidBackground);
Expand Down
4 changes: 3 additions & 1 deletion interface/control/Task.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace vk_gltf_viewer::control {
struct TightenNearFarPlane { };
struct ChangeCameraView { };
struct InvalidateDrawCommandSeparation { };
struct SelectMaterialVariants { std::size_t variantIndex; };
}

export using Task = std::variant<
Expand All @@ -39,5 +40,6 @@ namespace vk_gltf_viewer::control {
task::ChangeSelectedNodeWorldTransform,
task::TightenNearFarPlane,
task::ChangeCameraView,
task::InvalidateDrawCommandSeparation>;
task::InvalidateDrawCommandSeparation,
task::SelectMaterialVariants>;
}
19 changes: 17 additions & 2 deletions interface/gltf/AssetGpuBuffers.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,13 @@ namespace vk_gltf_viewer::gltf {
*/
std::unordered_map<vk::IndexType, vku::AllocatedBuffer> indexBuffers;

private:
/**
* @brief Buffer that contains <tt>GpuPrimitive</tt>s.
*/
vku::AllocatedBuffer primitiveBuffer = createPrimitiveBuffer();
std::variant<vku::AllocatedBuffer, vku::MappedBuffer> primitiveBuffer = createPrimitiveBuffer();

public:
template <typename BufferDataAdapter = fastgltf::DefaultBufferDataAdapter>
AssetGpuBuffers(
const fastgltf::Asset &asset,
Expand Down Expand Up @@ -198,13 +200,24 @@ namespace vk_gltf_viewer::gltf {
}
}

[[nodiscard]] vk::Buffer getPrimitiveBuffer() const noexcept { return visit_as<vk::Buffer>(primitiveBuffer); }

/**
* @brief Get the primitive by its order, which has the same manner of <tt>primitiveBuffer</tt>.
* @param index The order of the primitive.
* @return The primitive.
*/
[[nodiscard]] const fastgltf::Primitive &getPrimitiveByOrder(std::uint16_t index) const { return *orderedPrimitives[index]; }

/**
* @brief Update \p primitive's material index inside the GPU buffer.
* @param primitive Primitive to update.
* @param materialIndex New material index.
* @param transferCommandBuffer If buffer is not host-visible memory and so is unable to be updated from the host, this command buffer will be used for recording the buffer update command. Then, its execution MUST be synchronized to be available to the <tt>primitiveBuffer</tt>'s usage. Otherwise, this parameter is not used.
* @return <tt>true</tt> if the buffer is not host-visible memory and the update command is recorded, <tt>false</tt> otherwise.
*/
[[nodiscard]] bool updatePrimitiveMaterial(const fastgltf::Primitive &primitive, std::uint32_t materialIndex, vk::CommandBuffer transferCommandBuffer);

private:
[[nodiscard]] std::vector<const fastgltf::Primitive*> createOrderedPrimitives() const;
[[nodiscard]] std::unordered_map<const fastgltf::Primitive*, AssetPrimitiveInfo> createPrimitiveInfos() const;
Expand Down Expand Up @@ -328,7 +341,7 @@ namespace vk_gltf_viewer::gltf {
| std::ranges::to<std::unordered_map>();
}

[[nodiscard]] vku::AllocatedBuffer createPrimitiveBuffer();
[[nodiscard]] std::variant<vku::AllocatedBuffer, vku::MappedBuffer> createPrimitiveBuffer();

template <typename DataBufferAdapter>
void createPrimitiveAttributeBuffers(const DataBufferAdapter &adapter) {
Expand Down Expand Up @@ -539,5 +552,7 @@ namespace vk_gltf_viewer::gltf {

internalBuffers.emplace_back(std::move(buffer));
}

[[nodiscard]] static std::uint32_t padMaterialIndex(std::uint32_t materialIndex) noexcept { return materialIndex + 1; }
};
}
37 changes: 37 additions & 0 deletions interface/gltf/MaterialVariantsMapping.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export module vk_gltf_viewer:gltf.MaterialVariantsMapping;

import std;
export import fastgltf;
import :helpers.ranges;

namespace vk_gltf_viewer::gltf {
/**
* @brief Associative data structure of mappings for KHR_materials_variants.
*
* KHR_materials_variants extension defines the material variants for each primitive. For each variant index, you can call `at` to get the list of primitives and their material indices that use the corresponding material variant.
*/
class MaterialVariantsMapping {
public:
struct VariantPrimitive {
fastgltf::Primitive *pPrimitive;
std::uint32_t materialIndex;
};

explicit MaterialVariantsMapping(fastgltf::Asset &asset [[clang::lifetimebound]]) noexcept {
for (fastgltf::Primitive &primitive : asset.meshes | std::views::transform(&fastgltf::Mesh::primitives) | std::views::join) {
for (const auto &[materialVariantIndex, mapping] : primitive.mappings | ranges::views::enumerate) {
if (mapping) {
data[materialVariantIndex].emplace_back(&primitive, *mapping);
}
}
}
}

[[nodiscard]] std::span<const VariantPrimitive> at(std::size_t materialVariantIndex) const {
return data.at(materialVariantIndex);
}

private:
std::unordered_map<std::size_t, std::vector<VariantPrimitive>> data;
};
}

0 comments on commit a776b00

Please sign in to comment.