Skip to content

Commit 03d05cf

Browse files
committed
implementing support for asynchronous texture loading and improved Vulkan queue handling.
Bistro.gltf loads all textures, and creates physics geometry in under 44 seconds. Seeing 55 fps as well.
1 parent bbbae41 commit 03d05cf

14 files changed

+920
-378
lines changed

attachments/simple_engine/engine.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ void Engine::handleMouseInput(float x, float y, uint32_t buttons) {
285285
// Check if ImGui wants to capture mouse input first
286286
bool imguiWantsMouse = imguiSystem && imguiSystem->WantCaptureMouse();
287287

288+
// Suppress right-click while loading
289+
if (renderer && renderer->IsLoading()) {
290+
buttons &= ~2u; // clear right button bit
291+
}
292+
288293
if (!imguiWantsMouse) {
289294
// Handle mouse click for ball throwing (right mouse button)
290295
if (buttons & 2) { // Right mouse button (bit 1)

attachments/simple_engine/imgui_system.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,55 @@ void ImGuiSystem::NewFrame() {
123123

124124
ImGui::NewFrame();
125125

126+
// Loading overlay: show only a fullscreen progress bar while model/textures are loading
127+
if (renderer) {
128+
const uint32_t scheduled = renderer->GetTextureTasksScheduled();
129+
const uint32_t completed = renderer->GetTextureTasksCompleted();
130+
const bool modelLoading = renderer->IsLoading();
131+
if (modelLoading || (scheduled > 0 && completed < scheduled)) {
132+
ImGuiIO& io = ImGui::GetIO();
133+
// Suppress right-click while loading
134+
if (io.MouseDown[1]) io.MouseDown[1] = false;
135+
136+
const ImVec2 dispSize = io.DisplaySize;
137+
138+
ImGui::SetNextWindowPos(ImVec2(0, 0));
139+
ImGui::SetNextWindowSize(dispSize);
140+
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
141+
ImGuiWindowFlags_NoResize |
142+
ImGuiWindowFlags_NoMove |
143+
ImGuiWindowFlags_NoScrollbar |
144+
ImGuiWindowFlags_NoCollapse |
145+
ImGuiWindowFlags_NoSavedSettings |
146+
ImGuiWindowFlags_NoBringToFrontOnFocus |
147+
ImGuiWindowFlags_NoNav;
148+
149+
if (ImGui::Begin("##LoadingOverlay", nullptr, flags)) {
150+
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
151+
// Center the progress elements
152+
const float barWidth = dispSize.x * 0.8f;
153+
const float barX = (dispSize.x - barWidth) * 0.5f;
154+
const float barY = dispSize.y * 0.45f;
155+
ImGui::SetCursorPos(ImVec2(barX, barY));
156+
ImGui::BeginGroup();
157+
float frac = (scheduled > 0) ? (float)completed / (float)scheduled : 0.0f;
158+
ImGui::ProgressBar(frac, ImVec2(barWidth, 0.0f));
159+
ImGui::Dummy(ImVec2(0.0f, 10.0f));
160+
ImGui::SetCursorPosX(barX);
161+
if (modelLoading) {
162+
ImGui::Text("Loading model...");
163+
} else {
164+
ImGui::Text("Loading textures: %u / %u", completed, scheduled);
165+
}
166+
ImGui::EndGroup();
167+
ImGui::PopStyleVar();
168+
}
169+
ImGui::End();
170+
// Do not draw the rest of the UI until loading finishes
171+
return;
172+
}
173+
}
174+
126175
// Create HRTF Audio Control UI
127176
ImGui::Begin("HRTF Audio Controls");
128177
ImGui::Text("Hello, Vulkan!");
@@ -359,6 +408,19 @@ void ImGuiSystem::NewFrame() {
359408
ImGui::Text("Camera: Manual control (WASD + mouse)");
360409
}
361410

411+
// Texture loading progress
412+
if (renderer) {
413+
const uint32_t scheduled = renderer->GetTextureTasksScheduled();
414+
const uint32_t completed = renderer->GetTextureTasksCompleted();
415+
if (scheduled > 0 && completed < scheduled) {
416+
ImGui::Separator();
417+
float frac = scheduled ? (float)completed / (float)scheduled : 1.0f;
418+
ImGui::Text("Loading textures: %u / %u", completed, scheduled);
419+
ImGui::ProgressBar(frac, ImVec2(-FLT_MIN, 0.0f));
420+
ImGui::Text("You can continue interacting while textures stream in...");
421+
}
422+
}
423+
362424
ImGui::End();
363425
}
364426

attachments/simple_engine/main.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <iostream>
77
#include <stdexcept>
8+
#include <thread>
89

910
// Constants
1011
constexpr int WINDOW_WIDTH = 800;
@@ -38,8 +39,13 @@ void SetupScene(Engine* engine) {
3839
// Set the camera as the active camera
3940
engine->SetActiveCamera(camera);
4041

41-
// Load GLTF model synchronously on the main thread
42-
LoadGLTFModel(engine, "../Assets/bistro/bistro.gltf");
42+
// Kick off GLTF model loading on a background thread so the main loop can start and render the UI/progress bar
43+
if (auto* renderer = engine->GetRenderer()) {
44+
renderer->SetLoading(true);
45+
}
46+
std::thread([engine]{
47+
LoadGLTFModel(engine, "../Assets/bistro/bistro.gltf");
48+
}).detach();
4349
}
4450

4551
#if defined(PLATFORM_ANDROID)

attachments/simple_engine/memory_pool.cpp

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#include "memory_pool.h"
22
#include <iostream>
33
#include <algorithm>
4+
#include <vulkan/vulkan.hpp>
45

56
MemoryPool::MemoryPool(const vk::raii::Device& device, const vk::raii::PhysicalDevice& physicalDevice)
67
: device(device), physicalDevice(physicalDevice) {
78
}
89

10+
911
MemoryPool::~MemoryPool() {
1012
// RAII will handle cleanup automatically
1113
std::lock_guard lock(poolMutex);
@@ -153,6 +155,48 @@ std::unique_ptr<MemoryPool::MemoryBlock> MemoryPool::createMemoryBlock(PoolType
153155
return block;
154156
}
155157

158+
std::unique_ptr<MemoryPool::MemoryBlock> MemoryPool::createMemoryBlockWithType(PoolType poolType, vk::DeviceSize size, uint32_t memoryTypeIndex) {
159+
auto configIt = poolConfigs.find(poolType);
160+
if (configIt == poolConfigs.end()) {
161+
throw std::runtime_error("Pool type not configured");
162+
}
163+
const PoolConfig& config = configIt->second;
164+
165+
// Allocate the memory block with the exact requested size
166+
vk::MemoryAllocateInfo allocInfo{
167+
.allocationSize = size,
168+
.memoryTypeIndex = memoryTypeIndex
169+
};
170+
171+
// Determine properties from the chosen memory type
172+
const auto memProps = physicalDevice.getMemoryProperties();
173+
if (memoryTypeIndex >= memProps.memoryTypeCount) {
174+
throw std::runtime_error("Invalid memoryTypeIndex for createMemoryBlockWithType");
175+
}
176+
const vk::MemoryPropertyFlags typeProps = memProps.memoryTypes[memoryTypeIndex].propertyFlags;
177+
178+
auto block = std::unique_ptr<MemoryBlock>(new MemoryBlock{
179+
.memory = vk::raii::DeviceMemory(device, allocInfo),
180+
.size = size,
181+
.used = 0,
182+
.memoryTypeIndex = memoryTypeIndex,
183+
.isMapped = false,
184+
.mappedPtr = nullptr,
185+
.freeList = {},
186+
.allocationUnit = config.allocationUnit
187+
});
188+
189+
block->isMapped = (typeProps & vk::MemoryPropertyFlagBits::eHostVisible) != vk::MemoryPropertyFlags{};
190+
if (block->isMapped) {
191+
block->mappedPtr = block->memory.mapMemory(0, size);
192+
}
193+
194+
const size_t numUnits = static_cast<size_t>(block->size / config.allocationUnit);
195+
block->freeList.resize(numUnits, true);
196+
197+
return block;
198+
}
199+
156200
std::pair<MemoryPool::MemoryBlock*, size_t> MemoryPool::findSuitableBlock(PoolType poolType, vk::DeviceSize size, vk::DeviceSize alignment) {
157201
auto poolIt = pools.find(poolType);
158202
if (poolIt == pools.end()) {
@@ -162,27 +206,42 @@ std::pair<MemoryPool::MemoryBlock*, size_t> MemoryPool::findSuitableBlock(PoolTy
162206
auto& poolBlocks = poolIt->second;
163207
const PoolConfig& config = poolConfigs[poolType];
164208

165-
// Calculate required units (accounting for alignment)
209+
// Calculate required units (accounting for size alignment)
166210
const vk::DeviceSize alignedSize = ((size + alignment - 1) / alignment) * alignment;
167-
const size_t requiredUnits = (alignedSize + config.allocationUnit - 1) / config.allocationUnit;
211+
const size_t requiredUnits = static_cast<size_t>((alignedSize + config.allocationUnit - 1) / config.allocationUnit);
168212

169-
// Search existing blocks for sufficient free space
213+
// Search existing blocks for sufficient free space with proper offset alignment
170214
for (const auto& block : poolBlocks) {
171-
// Find consecutive free units
172-
size_t consecutiveFree = 0;
173-
size_t startUnitCandidate = 0;
174-
for (size_t i = 0; i < block->freeList.size(); ++i) {
175-
if (block->freeList[i]) {
176-
if (consecutiveFree == 0) {
177-
startUnitCandidate = i;
178-
}
179-
consecutiveFree++;
180-
if (consecutiveFree >= requiredUnits) {
181-
return {block.get(), startUnitCandidate};
182-
}
183-
} else {
184-
consecutiveFree = 0;
215+
const vk::DeviceSize unit = config.allocationUnit;
216+
const size_t totalUnits = block->freeList.size();
217+
218+
size_t i = 0;
219+
while (i < totalUnits) {
220+
// Ensure starting unit produces an offset aligned to 'alignment'
221+
vk::DeviceSize startOffset = static_cast<vk::DeviceSize>(i) * unit;
222+
if ((alignment > 0) && (startOffset % alignment != 0)) {
223+
// Advance i to the next unit that aligns with 'alignment'
224+
const vk::DeviceSize remainder = startOffset % alignment;
225+
const vk::DeviceSize advanceBytes = alignment - remainder;
226+
const size_t advanceUnits = static_cast<size_t>((advanceBytes + unit - 1) / unit);
227+
i += std::max<size_t>(advanceUnits, 1);
228+
continue;
185229
}
230+
231+
// From aligned i, check for consecutive free units
232+
size_t consecutiveFree = 0;
233+
size_t j = i;
234+
while (j < totalUnits && block->freeList[j] && consecutiveFree < requiredUnits) {
235+
++consecutiveFree;
236+
++j;
237+
}
238+
239+
if (consecutiveFree >= requiredUnits) {
240+
return {block.get(), i};
241+
}
242+
243+
// Move past the checked range
244+
i = (j > i) ? j : (i + 1);
186245
}
187246
}
188247

@@ -333,13 +392,41 @@ std::pair<vk::raii::Image, std::unique_ptr<MemoryPool::Allocation>> MemoryPool::
333392

334393
vk::raii::Image image(device, imageInfo);
335394

336-
// Get memory requirements
395+
// Get memory requirements for this image
337396
vk::MemoryRequirements memRequirements = image.getMemoryRequirements();
338397

339-
// Allocate from texture pool
340-
auto allocation = allocate(PoolType::TEXTURE_IMAGE, memRequirements.size, memRequirements.alignment);
341-
if (!allocation) {
342-
throw std::runtime_error("Failed to allocate memory from texture pool");
398+
// Pick a memory type compatible with this image
399+
uint32_t memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
400+
401+
// Create a dedicated memory block for this image with the exact type and size
402+
std::unique_ptr<Allocation> allocation;
403+
{
404+
std::lock_guard<std::mutex> lock(poolMutex);
405+
auto poolIt = pools.find(PoolType::TEXTURE_IMAGE);
406+
if (poolIt == pools.end()) {
407+
poolIt = pools.try_emplace(PoolType::TEXTURE_IMAGE).first;
408+
}
409+
auto& poolBlocks = poolIt->second;
410+
auto block = createMemoryBlockWithType(PoolType::TEXTURE_IMAGE, memRequirements.size, memoryTypeIndex);
411+
412+
// Prepare allocation that uses the new block from offset 0
413+
allocation = std::make_unique<Allocation>();
414+
allocation->memory = *block->memory;
415+
allocation->offset = 0;
416+
allocation->size = memRequirements.size;
417+
allocation->memoryTypeIndex = memoryTypeIndex;
418+
allocation->isMapped = block->isMapped;
419+
allocation->mappedPtr = block->mappedPtr;
420+
421+
// Mark the entire block as used
422+
block->used = memRequirements.size;
423+
const size_t units = block->freeList.size();
424+
for (size_t i = 0; i < units; ++i) {
425+
block->freeList[i] = false;
426+
}
427+
428+
// Keep the block owned by the pool for lifetime management and deallocation support
429+
poolBlocks.push_back(std::move(block));
343430
}
344431

345432
// Bind memory to image

attachments/simple_engine/memory_pool.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class MemoryPool {
5757
private:
5858
const vk::raii::Device& device;
5959
const vk::raii::PhysicalDevice& physicalDevice;
60+
vk::PhysicalDeviceMemoryProperties memPropsCache{};
61+
6062

6163
// Pool configurations
6264
struct PoolConfig {
@@ -78,6 +80,8 @@ class MemoryPool {
7880
// Helper methods
7981
uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const;
8082
std::unique_ptr<MemoryBlock> createMemoryBlock(PoolType poolType, vk::DeviceSize size);
83+
// Create a memory block with an explicit memory type index (used for images requiring a specific type)
84+
std::unique_ptr<MemoryBlock> createMemoryBlockWithType(PoolType poolType, vk::DeviceSize size, uint32_t memoryTypeIndex);
8185
std::pair<MemoryBlock*, size_t> findSuitableBlock(PoolType poolType, vk::DeviceSize size, vk::DeviceSize alignment);
8286

8387
public:

0 commit comments

Comments
 (0)