Skip to content

Commit 6378bef

Browse files
committed
Add deferred mesh uploads, batch resource staging, priority-based texture uploads, and thread-safe texture tracking. Refactor Vulkan queue handling and improve modern C++ usage.
BRDF now works as intended so we have true PBR scene. We're going to add environment mapping in the complex samples, so this should complete the tutorial verison of the game engine.
1 parent ce7c00d commit 6378bef

25 files changed

+1251
-490
lines changed

attachments/simple_engine/engine.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,20 +198,20 @@ bool Engine::RemoveEntity(Entity* entity) {
198198
std::string name = entity->GetName();
199199

200200
// Find the entity in the vector
201-
auto it = std::find_if(entities.begin(), entities.end(),
202-
[entity](const std::unique_ptr<Entity>& e) {
203-
return e.get() == entity;
204-
});
201+
auto it = std::ranges::find_if(entities,
202+
[entity](const std::unique_ptr<Entity>& e) {
203+
return e.get() == entity;
204+
});
205205

206206
if (it != entities.end()) {
207207
// Remove from the vector (ownership)
208208
entities.erase(it);
209209

210210
// Update the map: point to another entity with the same name if one exists
211-
auto remainingIt = std::find_if(entities.begin(), entities.end(),
212-
[&name](const std::unique_ptr<Entity>& e) {
213-
return e->GetName() == name;
214-
});
211+
auto remainingIt = std::ranges::find_if(entities,
212+
[&name](const std::unique_ptr<Entity>& e) {
213+
return e->GetName() == name;
214+
});
215215

216216
if (remainingIt != entities.end()) {
217217
entityMap[name] = remainingIt->get();
@@ -357,6 +357,8 @@ void Engine::handleKeyInput(uint32_t key, bool pressed) {
357357
case GLFW_KEY_PAGE_DOWN:
358358
cameraControl.moveDown = pressed;
359359
break;
360+
case GLFW_KEY_0:
361+
break;
360362
default: break;
361363
}
362364

@@ -366,13 +368,20 @@ void Engine::handleKeyInput(uint32_t key, bool pressed) {
366368
}
367369

368370
void Engine::Update(TimeDelta deltaTime) {
369-
// Debug: Verify Update method is being called
370-
static int updateCallCount = 0;
371-
updateCallCount++;
371+
// During background scene loading we avoid touching the live entity
372+
// list from the main thread. This lets the loading thread construct
373+
// entities/components safely while the main thread only drives the
374+
// UI/loading overlay.
375+
if (renderer && renderer->IsLoading()) {
376+
if (imguiSystem) {
377+
imguiSystem->NewFrame();
378+
}
379+
return;
380+
}
381+
372382
// Process pending ball creations (outside rendering loop to avoid memory pool constraints)
373383
ProcessPendingBalls();
374384

375-
376385
if (activeCamera) {
377386
glm::vec3 currentCameraPosition = activeCamera->GetPosition();
378387
physicsSystem->SetCameraPosition(currentCameraPosition);

attachments/simple_engine/engine.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,5 @@ class Engine {
355355
*/
356356
void HandleMouseHover(float mouseX, float mouseY);
357357

358+
358359
};

attachments/simple_engine/imgui_system.cpp

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,14 @@ void ImGuiSystem::NewFrame() {
123123

124124
ImGui::NewFrame();
125125

126-
// Loading overlay: show only a fullscreen progress bar while model/textures are loading
126+
// Loading overlay: show only a fullscreen progress bar while the model
127+
// itself is loading. Once the scene is ready and geometry is visible,
128+
// we no longer block the view with a full-screen progress bar.
127129
if (renderer) {
128130
const uint32_t scheduled = renderer->GetTextureTasksScheduled();
129131
const uint32_t completed = renderer->GetTextureTasksCompleted();
130132
const bool modelLoading = renderer->IsLoading();
131-
if (modelLoading || (scheduled > 0 && completed < scheduled)) {
133+
if (modelLoading) {
132134
ImGuiIO& io = ImGui::GetIO();
133135
// Suppress right-click while loading
134136
if (io.MouseDown[1]) io.MouseDown[1] = false;
@@ -158,11 +160,7 @@ void ImGuiSystem::NewFrame() {
158160
ImGui::ProgressBar(frac, ImVec2(barWidth, 0.0f));
159161
ImGui::Dummy(ImVec2(0.0f, 10.0f));
160162
ImGui::SetCursorPosX(barX);
161-
if (modelLoading) {
162-
ImGui::Text("Loading model...");
163-
} else {
164-
ImGui::Text("Loading textures: %u / %u", completed, scheduled);
165-
}
163+
ImGui::Text("Loading scene...");
166164
ImGui::EndGroup();
167165
ImGui::PopStyleVar();
168166
}
@@ -172,6 +170,41 @@ void ImGuiSystem::NewFrame() {
172170
}
173171
}
174172

173+
// --- Streaming status: small progress indicator in the upper-right ---
174+
// Once the scene is visible, textures may continue streaming to the GPU.
175+
// Show a compact progress bar in the top-right while there are still
176+
// outstanding texture tasks, and hide it once everything is fully loaded.
177+
if (renderer) {
178+
const uint32_t uploadTotal = renderer->GetUploadJobsTotal();
179+
const uint32_t uploadDone = renderer->GetUploadJobsCompleted();
180+
const bool modelLoading = renderer->IsLoading();
181+
182+
if (!modelLoading && uploadTotal > 0 && uploadDone < uploadTotal) {
183+
ImGuiIO& io = ImGui::GetIO();
184+
const ImVec2 dispSize = io.DisplaySize;
185+
186+
const float windowWidth = std::min(260.0f, dispSize.x * 0.35f);
187+
const float windowHeight = 60.0f;
188+
const ImVec2 winPos(dispSize.x - windowWidth - 10.0f, 10.0f);
189+
190+
ImGui::SetNextWindowPos(winPos, ImGuiCond_Always);
191+
ImGui::SetNextWindowSize(ImVec2(windowWidth, windowHeight));
192+
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar |
193+
ImGuiWindowFlags_NoResize |
194+
ImGuiWindowFlags_NoMove |
195+
ImGuiWindowFlags_NoScrollbar|
196+
ImGuiWindowFlags_NoSavedSettings |
197+
ImGuiWindowFlags_NoCollapse;
198+
199+
if (ImGui::Begin("##StreamingTextures", nullptr, flags)) {
200+
ImGui::TextUnformatted("Streaming textures to GPU");
201+
float frac = (uploadTotal > 0) ? (float)uploadDone / (float)uploadTotal : 0.0f;
202+
ImGui::ProgressBar(frac, ImVec2(-1.0f, 0.0f));
203+
}
204+
ImGui::End();
205+
}
206+
}
207+
175208
// Create HRTF Audio Control UI
176209
ImGui::Begin("HRTF Audio Controls");
177210
ImGui::Text("Hello, Vulkan!");
@@ -241,22 +274,6 @@ void ImGuiSystem::NewFrame() {
241274
ImGui::Text("Note: Quality controls affect BRDF rendering only");
242275
}
243276

244-
ImGui::Separator();
245-
246-
// Sun position control (punctual light in GLTF)
247-
ImGui::Text("Sun Position in Sky:");
248-
if (renderer) {
249-
float sun = renderer->GetSunPosition();
250-
if (ImGui::SliderFloat("Sun Position", &sun, 0.0f, 1.0f, "%.2f")) {
251-
renderer->SetSunPosition(sun);
252-
}
253-
ImGui::SameLine();
254-
if (ImGui::Button("Noon")) { sun = 0.5f; renderer->SetSunPosition(sun); }
255-
ImGui::SameLine();
256-
if (ImGui::Button("Night")) { sun = 0.0f; renderer->SetSunPosition(sun); }
257-
ImGui::Text("Tip: 0/1 = Night, 0.5 = Noon. Warmer tint near horizon simulates evening.");
258-
}
259-
260277
ImGui::Separator();
261278
ImGui::Text("3D Audio Position Control");
262279

attachments/simple_engine/main.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ void SetupScene(Engine* engine) {
3939
// Set the camera as the active camera
4040
engine->SetActiveCamera(camera);
4141

42-
// Kick off GLTF model loading on a background thread so the main loop can start and render the UI/progress bar
42+
// Kick off GLTF model loading on a background thread so the main loop
43+
// can start and render the UI/progress bar while the scene is being
44+
// constructed. Engine::Update will avoid updating entities while
45+
// loading is in progress to prevent data races.
4346
if (auto* renderer = engine->GetRenderer()) {
4447
renderer->SetLoading(true);
4548
}

attachments/simple_engine/mesh_component.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ void MeshComponent::CreateSphere(float radius, const glm::vec3& color, int segme
1111

1212
// Generate sphere vertices using parametric equations
1313
for (int lat = 0; lat <= segments; ++lat) {
14-
auto theta = static_cast<float>(lat * M_PI / segments); // Latitude angle (0 to PI)
15-
float sinTheta = sinf(theta);
16-
float cosTheta = cosf(theta);
14+
const auto theta = static_cast<float>(lat * M_PI / segments); // Latitude angle (0 to PI)
15+
const float sinTheta = sinf(theta);
16+
const float cosTheta = cosf(theta);
1717

1818
for (int lon = 0; lon <= segments; ++lon) {
19-
auto phi = static_cast<float>(lon * 2.0 * M_PI / segments); // Longitude angle (0 to 2*PI)
20-
float sinPhi = sinf(phi);
21-
float cosPhi = cosf(phi);
19+
const auto phi = static_cast<float>(lon * 2.0 * M_PI / segments); // Longitude angle (0 to 2*PI)
20+
const float sinPhi = sinf(phi);
21+
const float cosPhi = cosf(phi);
2222

2323
// Calculate position
2424
glm::vec3 position = {
@@ -31,7 +31,7 @@ void MeshComponent::CreateSphere(float radius, const glm::vec3& color, int segme
3131
glm::vec3 normal = glm::normalize(position);
3232

3333
// Texture coordinates
34-
glm::vec2 texCoord = {
34+
const glm::vec2 texCoord = {
3535
static_cast<float>(lon) / static_cast<float>(segments),
3636
static_cast<float>(lat) / static_cast<float>(segments)
3737
};
@@ -66,8 +66,8 @@ void MeshComponent::CreateSphere(float radius, const glm::vec3& color, int segme
6666
// Generate indices for triangles
6767
for (int lat = 0; lat < segments; ++lat) {
6868
for (int lon = 0; lon < segments; ++lon) {
69-
int current = lat * (segments + 1) + lon;
70-
int next = current + segments + 1;
69+
const int current = lat * (segments + 1) + lon;
70+
const int next = current + segments + 1;
7171

7272
// Create two triangles for each quad
7373
indices.push_back(current);

attachments/simple_engine/mesh_component.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ struct InstanceData {
6666

6767

6868
static vk::VertexInputBindingDescription getBindingDescription() {
69-
vk::VertexInputBindingDescription bindingDescription(
69+
constexpr vk::VertexInputBindingDescription bindingDescription(
7070
1, // binding (binding 1 for instance data)
7171
sizeof(InstanceData), // stride
7272
vk::VertexInputRate::eInstance // inputRate
@@ -78,7 +78,7 @@ struct InstanceData {
7878
constexpr uint32_t modelBase = offsetof(InstanceData, modelMatrix);
7979
constexpr uint32_t normalBase = offsetof(InstanceData, normalMatrix);
8080
constexpr uint32_t vec4Size = sizeof(glm::vec4);
81-
std::array<vk::VertexInputAttributeDescription, 7> attributeDescriptions = {
81+
constexpr std::array<vk::VertexInputAttributeDescription, 7> attributeDescriptions = {
8282
// Model matrix columns (locations 4-7)
8383
vk::VertexInputAttributeDescription{
8484
.location = 4,
@@ -131,7 +131,7 @@ struct InstanceData {
131131
static std::array<vk::VertexInputAttributeDescription, 4> getModelMatrixAttributeDescriptions() {
132132
constexpr uint32_t modelBase = offsetof(InstanceData, modelMatrix);
133133
constexpr uint32_t vec4Size = sizeof(glm::vec4);
134-
std::array<vk::VertexInputAttributeDescription, 4> attributeDescriptions = {
134+
constexpr std::array<vk::VertexInputAttributeDescription, 4> attributeDescriptions = {
135135
vk::VertexInputAttributeDescription{
136136
.location = 4,
137137
.binding = 1,
@@ -164,7 +164,7 @@ struct InstanceData {
164164
static std::array<vk::VertexInputAttributeDescription, 3> getNormalMatrixAttributeDescriptions() {
165165
constexpr uint32_t normalBase = offsetof(InstanceData, normalMatrix);
166166
constexpr uint32_t vec4Size = sizeof(glm::vec4);
167-
std::array<vk::VertexInputAttributeDescription, 3> attributeDescriptions = {
167+
constexpr std::array<vk::VertexInputAttributeDescription, 3> attributeDescriptions = {
168168
vk::VertexInputAttributeDescription{
169169
.location = 8,
170170
.binding = 1,
@@ -205,7 +205,7 @@ struct Vertex {
205205
}
206206

207207
static vk::VertexInputBindingDescription getBindingDescription() {
208-
vk::VertexInputBindingDescription bindingDescription(
208+
constexpr vk::VertexInputBindingDescription bindingDescription(
209209
0, // binding
210210
sizeof(Vertex), // stride
211211
vk::VertexInputRate::eVertex // inputRate
@@ -214,7 +214,7 @@ struct Vertex {
214214
}
215215

216216
static std::array<vk::VertexInputAttributeDescription, 4> getAttributeDescriptions() {
217-
std::array<vk::VertexInputAttributeDescription, 4> attributeDescriptions = {
217+
constexpr std::array<vk::VertexInputAttributeDescription, 4> attributeDescriptions = {
218218
vk::VertexInputAttributeDescription{
219219
.location = 0,
220220
.binding = 0,
@@ -247,7 +247,7 @@ struct Vertex {
247247
/**
248248
* @brief Component that handles the mesh data for rendering.
249249
*/
250-
class MeshComponent : public Component {
250+
class MeshComponent final : public Component {
251251
private:
252252
std::vector<Vertex> vertices;
253253
std::vector<uint32_t> indices;

0 commit comments

Comments
 (0)