Skip to content

Conversation

gpx1000
Copy link
Contributor

@gpx1000 gpx1000 commented Jul 31, 2025

This is the next big tutorial effort. I added the conclusion to the base line tutorial, and this is the "next thing." The game engine presented here is one example of what the tutorial describes and not a 1-1 mapping of the source code to the implementation in the attachments folder.
Future work will add new features to the tutorial in the Vulkan Samples project. This is meant to give the course to describe basics of the game engine and Samples is meant to describe features as they are implemented. This can be used as a template for a starting off point for any project as it is designed to be self contained.

Core features in this tutorial/engine:

  • HRTF (positional) audio in compute pipeline.
  • Physics basic in compute pipeline.
  • PBR gltf loading.
  • KTX2 with BasisU compression.
  • ImGUI

Several features are left out intentionally as a method of what we'll implement and feature in the Samples including parallel loading of the assets. Bistro takes a few moments to load as it stands here.

gpx1000 added 30 commits July 14, 2025 00:09
…y commit to start getting proofreading while I still iterate on getting the engine to work.

Engine still needs assets.
Engine still needs Android project created.
Engine currently crashes on running.

Add `SimpleEngine` with Entity-Component-System (ECS) implementation

- Introduced the `SimpleEngine` project featuring a modular ECS-based architecture.
- Implemented core files: `Entity`, `Component`, and `TransformComponent` for managing entities and transformations.
- Added CMake setup with Vulkan, GLFW, GLM, TinyGLTF, and KTX dependencies for rendering support.
- Integrated shader compilation workflow using `slangc`.
- Included initial scene setup with basic camera and cube entities in `main.cpp`.
…mmendations

- Consolidated sections to address vendor-agnostic optimizations for mobile GPUs.
- Replaced Huawei-specific details with broader guidance applicable to various mobile GPU architectures (Mali, Adreno, PowerVR, etc.).
- Improved Vulkan extension examples and added checks for multiple vendor IDs for better compatibility.
- Updated best practices and performance tuning sections to emphasize device diversity and testing requirements.
…n details

- Simplified and reorganized GPU optimization lists for readability.
- Expanded explanations for `VK_KHR_dynamic_rendering_local_read` and `VK_EXT_shader_tile_image` to highlight memory bandwidth reduction benefits.
- Improved consistency in vendor-specific and tile-based optimization recommendations.
- Added detailed examples and practical use cases for extension advantages in mobile rendering pipelines.
- Expanded tutorial with a comprehensive explanation of rendergraphs, including resource management, dependency tracking, and synchronization benefits.
- Added a sample `Rendergraph` class implementation with methods for resource and pass management, including compile and execute functions.
- Included practical examples such as deferred renderer setup and Vulkan synchronization best practices using semaphores, fences, and pipeline barriers.
- Enhanced Vulkan tutorial content with a focus on simplifying complex rendering workflows and optimizing synchronization.
- Inserted new diagrams for layered architecture, component-based architecture, data-oriented design, and service locator pattern in the engine tutorial.
- Adjusted and reordered content in corresponding sections for improved clarity and structure.
- Updated references to foundational concepts, ensuring consistency across sections.
- Included a detailed clarification about windowing libraries and their purpose.
- Provided examples of popular windowing libraries like GLFW, SDL, Qt, and SFML.
- Enhanced understanding of platform-specific abstraction benefits for developers.
…i needs some help. it also looks like cleanup logic and resizing needs work.
…the semaphore. resizing still doesn't work. Next step is to get the parts of the engine to demonstrate things.
…the semaphore. resizing still doesn't work. Next step is to get the parts of the engine to demonstrate things.
…nk that's due to material only looking at the vespa. This includes camera controls.
@SaschaWillems
Copy link
Collaborator

Also need to check all external links. Afair already mentioned that and there are still links that are either wrong or just dead like https://developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch17.html

Copy link
Contributor

@asuessenbach asuessenbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log level is just some kind of categorization with the generated output, you can't filter for e.g. warnings and above, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, although I might change that. I've been debating about doing that change. Might make things easier for people wanting to get used to Android development to have it work the same everywhere.


// Convenience macros for performance measurement
#define MEASURE_START(name) DebugSystem::GetInstance().StartMeasurement(name)
#define MEASURE_END(name) DebugSystem::GetInstance().StopMeasurement(name)
Copy link
Contributor

@asuessenbach asuessenbach Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEASURE_END -> MEASURE_STOP ?

/**
* @brief Clean up engine resources.
*/
void Cleanup();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Cleanup called from somewhere else than the destructor?

// Initialize per-frame buffers containers
if (renderer) {
uint32_t frames = renderer->GetMaxFramesInFlight();
vertexBuffers.clear(); vertexBuffers.reserve(frames);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the vertexBuffers be non-empty before reserving here?
Maybe an assert(vertexBuffers.empty()); would be appropriate, instead.


// Bind vertex and index buffers for this frame
std::array<vk::Buffer, 1> vertexBuffersArr = {*vertexBuffers[frameIndex]};
std::array<vk::DeviceSize, 1> offsets = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you use std::arrays of size 1, instead of just one value?

samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;
samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use designated initializers and use default values for irrelevant settings here?

vk::PushConstantRange pushConstantRange;
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(float) * 4; // 2 floats for scale, 2 floats for translate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you have some struct holding this data?

if (frameIndex >= vertexCounts.size()) return; // Safety

if (drawData->TotalVtxCount > vertexCounts[frameIndex]) {
// Clean up old buffer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you explicitly clean up the old buffers? Aren't they automatically cleaned when you assign some new buffers?


vertexBufferMemories[frameIndex] = vk::raii::DeviceMemory(device, allocInfo);
vertexBuffers[frameIndex].bindMemory(*vertexBufferMemories[frameIndex], 0);
vertexCounts[frameIndex] = drawData->TotalVtxCount;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should either check for drawData->TotalVtxCount != vertexCounts[frameIndex], or move this assignment out of this block.

Copy link
Contributor

@asuessenbach asuessenbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments...

@@ -23,6 +24,7 @@
*/
class Engine {
public:
using TimeDelta = std::chrono::milliseconds;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout the code, you're sometimes using TimeDelta, and sometimes using std::chrono::milliseconds. Maybe you should use just one of them?

components.erase(vecIt);
for (auto it = components.rbegin(); it != components.rend(); ++it) {
if (dynamic_cast<T*>(it->get()) != nullptr) {
components.erase(std::next(it).base());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you erase std::next(it), not it?


// Pre-allocate at least one block for each pool type
for (const auto& [poolType, config] : poolConfigs) {
auto poolIt = pools.find(poolType);
if (poolIt == pools.end()) {
pools[poolType] = std::vector<std::unique_ptr<MemoryBlock>>();
poolIt = pools.find(poolType);
poolIt = pools.try_emplace( poolType ).first;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No find and check needed, just try_emplace an empty pool.

auto it = models.find(filename);
if (it != models.end()) {
return it->second.get();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it reasonable to cache the models in the ModelLoader?

std::cout << "Blender generator detected, applying blender factor" << std::endl;
light_scale = EMISSIVE_SCALE_FACTOR;
}
light_scale = EMISSIVE_SCALE_FACTOR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

light_scale is EMISSIVE_SCALE_FACTOR, no matter what generator is used?

return emptyVector;
}

Material* ModelLoader::GetMaterial(const std::string& materialName) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A const function should return a const *.

light.type = ExtractedLight::Type::Point;
} else if (type == "spot") {
light.type = ExtractedLight::Type::Spot;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if type is none of "directional", "point", or "spot"?

colorArray[1].IsNumber() ? static_cast<float>(colorArray[1].Get<double>()) : 1.0f,
colorArray[2].IsNumber() ? static_cast<float>(colorArray[2].Get<double>()) : 1.0f
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default color?

// Compute world transforms for all nodes in the default scene
std::vector<glm::mat4> nodeWorldTransforms(gltfModel.nodes.size(), glm::mat4(1.0f));

auto calcLocal = [](const tinygltf::Node& n) -> glm::mat4 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already had the very same lambda, but called it calculateNodeTransform up at line 668.

}

// Now assign positions and directions using world transforms
for (size_t nodeIndex = 0; nodeIndex < gltfModel.nodes.size(); ++nodeIndex) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, what's the expected node to light node ratio.
Is it worth to calculate the world transform for each node, and then use those from light nodes only?

Copy link
Contributor

@asuessenbach asuessenbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And some more comments...

*/
struct MaterialMesh {
int materialIndex;
std::string materialName;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The materialndex is the index into the gltfModel::materials, and the materialName is the key for the ModelLoader::materials, right?
Why don't you hold the ModelLoader::materials as an array, corresponding to the gltfModel::materials? Then you would just need that index.

std::string normalTexturePath; // Normal map texture
std::string metallicRoughnessTexturePath; // Metallic-roughness texture
std::string occlusionTexturePath; // Ambient occlusion texture
std::string emissiveTexturePath; // Emissive texture
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are all those texture paths related to those in the Material, referenced by the materialName?


// Instancing support
std::vector<InstanceData> instances; // Instance data for instanced rendering
bool isInstanced = false; // Flag to indicate if this mesh uses instancing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it simplify things if you added some default InstanceData for the not instanced meshes?
Resulting in a one-element vector for the non-instanced meshes, and a multi-element vector for the instanced ones.

* @param transform The transform matrix for this instance.
* @param matIndex The material index for this instance (default: use materialIndex).
*/
void AddInstance(const glm::mat4& transform, uint32_t matIndex = 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should matIndex be renamed to materialIndex?
In the context of matrix handling, matIndex might be misleading.

*/
void AddInstance(const glm::mat4& transform, uint32_t matIndex = 0) {
if (matIndex == 0) matIndex = static_cast<uint32_t>(materialIndex);
instances.emplace_back(transform, matIndex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and why have the instances a materialIndex?
I would have expected that everything in a MaterialMesh is related to the very same material.
Maybe you should decouple Mesh (vertices and indices) and Material?

.setDstArrayElement(0)
.setDescriptorCount(1)
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
.setPBufferInfo(&paramsBufferInfo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Designated initializers, maybe?

vulkanResources.collisionBufferMemory = nullptr;
vulkanResources.physicsBuffer = nullptr;
vulkanResources.physicsBufferMemory = nullptr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all those resources are ordered correctly in the vulkanResources, you would just need to waitForIdle and to unmap the memories. All others would be automatically done.

// Validate Vulkan resources and persistent memory pointers before using them
if (*vulkanResources.physicsBuffer == VK_NULL_HANDLE || *vulkanResources.physicsBufferMemory == VK_NULL_HANDLE ||
*vulkanResources.counterBuffer == VK_NULL_HANDLE || *vulkanResources.counterBufferMemory == VK_NULL_HANDLE ||
*vulkanResources.paramsBuffer == VK_NULL_HANDLE || *vulkanResources.paramsBufferMemory == VK_NULL_HANDLE ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of comparing with VK_NULL_HANDLE, you could as well check for !*vulkanResources.physicsBuffer, etc.

concreteRigidBody->GetRotation().z, concreteRigidBody->GetRotation().w);
gpuData[i].linearVelocity = glm::vec4(concreteRigidBody->GetLinearVelocity(), concreteRigidBody->GetRestitution());
gpuData[i].angularVelocity = glm::vec4(concreteRigidBody->GetAngularVelocity(), concreteRigidBody->GetFriction());
// CRITICAL FIX: Initialize forces properly instead of always resetting to zero
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this comment mean?
You still initialize with zero.

switch (concreteRigidBody->GetShape()) {
case CollisionShape::Sphere:
// Use tennis ball radius (0.0335f) instead of hardcoded 0.5f
gpuData[i].colliderData = glm::vec4(0.0335f, 0.0f, 0.0f, static_cast<float>(0)); // 0 = Sphere
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again this hardcoded 0.0335f.
Maybe at least some constexpr somewhere?

Copy link
Contributor

@asuessenbach asuessenbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments...

/**
* @brief Class representing a rigid body for physics simulation.
*/
class RigidBody {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, there's just one class deriving from RigidBody: ConcreteRigidBody.
Do you plan to introduce more classes later on, or why do you introduce this pure virtual base class?

* @param rigidBody The rigid body to remove.
* @return True if removal was successful, false otherwise.
*/
bool RemoveRigidBody(RigidBody* rigidBody);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the RigidBody removed from?
As it's the counterpart to CreateRigidBody, you might name it DestroyRigidBody?

However, one could argue whether creating a RigidBody should be part of the PhysicsSystem at all. Maybe you should instead change CreateRigidBody to AddRigidBody(std::unique_ptr<RigidBody> rb);.


// Destructor
Pipeline::~Pipeline() {
// RAII will handle destruction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the destructor does nothing, you just don't need it.

}

// Create descriptor set layout
bool Pipeline::createDescriptorSetLayout() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the alternative to createPBRDescriptorSetLayout?
Then you might name it more specific, like createPhongDescriptorSetLayout?


// Create shader modules
vk::raii::ShaderModule vertShaderModule = createShaderModule(vertShaderCode);
vk::raii::ShaderModule fragShaderModule = createShaderModule(fragShaderCode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are two separate shader modules out of two separate but identical shader codes necessary?

.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said above: no need to set members to their default value.


// Create shader modules
vk::raii::ShaderModule vertShaderModule = createShaderModule(vertShaderCode);
vk::raii::ShaderModule fragShaderModule = createShaderModule(fragShaderCode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again: two separate shader modules out of two separate but identical shader codes?

.pName = "PSMain" // Changed from FSMain to PSMain to match the shader
};

vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again: use a std::array?

.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again: no need to set pViewports and pScissors for dynamic viewport and scissor

std::cerr << "Failed to create lighting pipeline: " << e.what() << std::endl;
return false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createGraphicsPipeline, createPBRPipeline, and createLightingPipeline are some lengthy functions that in large parts are identical. Worth to add some helper function(s)?

@SaschaWillems
Copy link
Collaborator

@gpx1000 : Still can't build this on Windows with MSVC:

C:\VulkanSDK\1.4.321.1\include\glm/gtx/matrix_decompose.hpp(24,10): fatal error C1189: #error:  "GLM: GLM_GTX_matrix_de
compose is an experimental extension and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including
 it, if you really want to use it." [V:\Vulkan-Docs-Site\Vulkan-Tutorial\attachments\simple_engine\build\SimpleEngine.v
cxproj]

V:\Vulkan-Docs-Site\Vulkan-Tutorial\attachments\simple_engine\renderer_resources.cpp(16,10): fatal error C1083: Datei (
Include) kann nicht ge÷ffnet werden: "ktxvulkan.h": No such file or directory [V:\Vulkan-Docs-Site\Vulkan-Tutorial\atta
chments\simple_engine\build\SimpleEngine.vcxproj]
C:\VulkanSDK\1.4.321.1\include\glm\gtx\matrix_decompose.hpp(24,10): error C1189: #error:  "GLM: GLM_GTX_matrix_decompos
e is an experimental extension and may change in the future. Use #define GLM_ENABLE_EXPERIMENTAL before including it, i
f you really want to use it." [V:\Vulkan-Docs-Site\Vulkan-Tutorial\attachments\simple_engine\build\SimpleEngine.vcxproj
]

V:\Vulkan-Docs-Site\Vulkan-Tutorial\attachments\simple_engine\renderer_resources.cpp(16,10): error C1083: Datei (Includ
e) kann nicht geöffnet werden: "ktxvulkan.h": No such file or directory [V:\Vulkan-Docs-Site\Vulkan-Tutorial\attachment
s\simple_engine\build\SimpleEngine.vcxproj]

And some more errors.

Would like to run this at least once for a review.

Any idea on how to fix?

@gpx1000
Copy link
Contributor Author

gpx1000 commented Aug 22, 2025

I haven't touched windows compiling yet. I'll try to get that addressed ASAP.

@SaschaWillems
Copy link
Collaborator

To get it compile with MSVC I did this:

  • Manually add ktx_vulkan.h, for me it's not included with the libktx that vcpkg is downloading
  • Use math.h and define _USE_MATH_DEFINES to get M_PI
  • Remove constexpr from static constexpr std::vector emptyVector; in model_loader

@SaschaWillems
Copy link
Collaborator

SaschaWillems commented Aug 22, 2025

While it does compile and run with above adjustments, it doesn't work properly.

This is how it looks for me on Win11 + with NV RTX 4070:

image
  • No colors
  • No audio
  • All objects are transparent
  • Only 10~20 fps
  • Lots of validation errors

Looks like it can't load any of the images of that model. Looking at the folder where the bistro scene gets downloaded, I can't see a single KTX2 file?

@gpx1000
Copy link
Contributor Author

gpx1000 commented Aug 22, 2025

That explains it. looks like the KTX2 file being missing and it's using the hand coded default files. That's the root cause of the no colors.

The audio, select default ping and hit play. You should hear something. I don't think I checked in the wav I've been using for debug as I don't think that's a good one to use.
Objects transparency is due to the texture issue mentioned.

Validation is clean here. give me a sample of them.

frame rate here is around 16fps. The intention is to have culling be part of the samples version of the game engine but that wasn't introduced here for this one so while I want to do things like improve the loading speed of the model, I'm trying to hold off and keep it as simple as possible yet be something we can build off of for the samples version. I chose optimization as a topic for the Samples version when it comes to getting it to work in Android / iOS.

@gpx1000
Copy link
Contributor Author

gpx1000 commented Aug 22, 2025

Grab the latest of the bistro and you should have textures.

@gpx1000
Copy link
Contributor Author

gpx1000 commented Aug 22, 2025

@SaschaWillems if you want, the changes you did for Windows, please make a PR against my branch here and I'll merge in your changes. That way we can keep a history and enable any further help you might be willing to give.

@SaschaWillems
Copy link
Collaborator

Textures now work. I guess the validation errors were caused by them missing, though I di get descriptor freeing related errors when resizing.

But lighting looks off when starting the app:

image

The basic lighting setting looks better, but the sun position slider doesn't do anything.

IMO optimizations are a must for this. It's doing ~12fps for me in fullscreen mode. I don't think we should leave it at that.

@SaschaWillems
Copy link
Collaborator

SaschaWillems commented Aug 23, 2025

For reference, here is the Bistro scene in one of my renderers:

image

That renderer doesn't cull either, but you can see some visual differences like normal mapping, discard for alpha-masked objects, etc.

@gpx1000
Copy link
Contributor Author

gpx1000 commented Aug 24, 2025

For what it's worth, the model you're using is slightly different. You're using a version of the bistro that's a lot simpler in that it doesn't come from the NVIDIA one that opens up the bistro and lets you go inside. There's also other differences. But I think a key one is the blender specified lumans value which does make the scene too bright during the noon day. Yes, there's still plenty of bugs in the rendering that I'm still trying to work out. But I'm still not entirely sure how much of it is due to rendering bugs from the punctual light source (sun) vs not. And the time of day slider is more for debugging the PBR pipeline. I'm still struggling with a few things in it. The phong version doesn't have the slider support because I haven't found a need to validate it there yet, but maybe it makes sense to keep the slider as it is pretty neat and add support to the phong path.

@gpx1000
Copy link
Contributor Author

gpx1000 commented Aug 25, 2025

Screenshot from 2025-08-25 02-49-06 Main issue was not treating Spec-Gloss extension correctly.

However, still have less than optimal treatment of the glassware:
Screenshot from 2025-08-25 02-49-34

Copy link
Contributor

@asuessenbach asuessenbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments...


// Call the resize callback if set
if (platform->resizeCallback) {
platform->resizeCallback(platform->width, platform->height);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for APP_CMD_INIT_WINDOW and APP_CMD_WINDOW_RESIZED are identical?


bool AndroidPlatform::HasWindowResized() {
bool resized = windowResized;
windowResized = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct that a query (HasWindowResized) is not const and potentially modifies the queried state?


void AndroidPlatform::SetWindowTitle(const std::string& title) {
// No-op on Android - mobile apps don't have window titles
(void)title; // Suppress unused parameter warning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use [[maybe_unused]] instead?

env->DeleteLocalRef(serviceStr);

// Check Vulkan support
// In a real implementation, this would check for Vulkan support and available extensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And in this implementation, you don't do that because of?

* @brief Get the Vulkan RAII device.
* @return The Vulkan RAII device.
*/
const vk::raii::Device& GetRaiiDevice() const { return device; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth to have both, GetDevice() and GetRAIIDevice()?
Wouldn't getting the vk::raii::Device const & be sufficient, as you get to the corresponding vk::Device that easy?

* @param commandBuffer The command buffer to submit.
* @param fence The fence to signal when the operation completes.
*/
void SubmitToComputeQueue(vk::CommandBuffer commandBuffer, vk::Fence fence) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep the API consistent, you could pass in a vk::raii::CommandBuffer const & and a vk::raii::Fence const & here.

* @param oldLayout The old layout.
* @param newLayout The new layout.
*/
void TransitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass in a vk::raii::Image const & here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file, some functions get/return a vk::Handle, some get/return a vk::raii::Handle.
Maybe it would be more consistent to always use a vk::raii::Handle.

std::vector<vk::DescriptorSet> descriptorSetsToBindRaw;
descriptorSetsToBindRaw.reserve(1);
descriptorSetsToBindRaw.push_back(*computeDescriptorSets[0]);
commandBufferRaii.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, descriptorSetsToBindRaw, {});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to copy stuff into a one-element vector.
You could just call
commandBufferRaii.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, *computeDescriptorSets[0], {});

// Extract raw command buffer for submission and release RAII ownership
// This prevents premature destruction while preserving the recorded commands
vk::CommandBuffer rawCommandBuffer = *commandBufferRaii;
commandBufferRaii.release(); // Release RAII ownership to prevent destruction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where will the rawCommandBuffer be destroyed, then?

…lkan queue handling.

Bistro.gltf loads all textures, and creates physics geometry in under 44 seconds.  Seeing 55 fps as well.
@SaschaWillems
Copy link
Collaborator

SaschaWillems commented Aug 26, 2025

Looking better now, but the image is very noisy. I'm not sure where these come from, but I can see a lot of artifacts like these, changing when moving around:

image

And it looks like there is no mip mapping,

image

Update: This seems to be caused by over-saturation. Changing lighting so the affected surfaces get darker, the artifacts go away.

@SaschaWillems
Copy link
Collaborator

A code related question, that Andreas might've already asked (hard to see, github hides older comments): What is the idea behind converting (Vulkan-hpp) exceptions into bools? See e.g. Pipeline::createGraphicsPipeline. It catches exceptions from Vulkan-hpp and "converts" them into a bool. At work I would consider that bad practice, as the conversion removes important information (the exception).

Since this is exclusively using Vulkan-hpp and C++, why not stick to exceptions. So in above case, try, catch, log inside the function and raise the exception up to the calling code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants