Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,20 @@ if(NOT BUILD_TESTING)

# Compiler-specific flags
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /permissive-)
target_compile_options(${PROJECT_NAME} PRIVATE
/W4 /permissive-
$<$<CONFIG:Debug>:/Od /Zi>
$<$<CONFIG:Release>:/O2 /GL>
)
target_link_options(${PROJECT_NAME} PRIVATE
$<$<CONFIG:Release>:/LTCG>
)
else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall -Wextra -Wpedantic -Werror
$<$<CONFIG:Debug>:-O0 -g>
$<$<CONFIG:Release>:-O3>
)
endif()

# Debug/Release definitions
Expand Down Expand Up @@ -111,14 +122,18 @@ if(NOT BUILD_TESTING)

# Generate embedded shaders header
set(EMBEDDED_HEADER "${CMAKE_BINARY_DIR}/generated/embedded_shaders.hpp")
set(SHADER_LIST_FILE "${CMAKE_BINARY_DIR}/generated/shader_list.txt")

# Escape semicolons in the list for passing to cmake -P
string(REPLACE ";" "\\;" SPIRV_OUTPUTS_ESCAPED "${SPIRV_OUTPUTS}")
# Write shader list to a file (more reliable than command-line args)
file(WRITE "${SHADER_LIST_FILE}" "")
foreach(SHADER_FILE ${SPIRV_OUTPUTS})
file(APPEND "${SHADER_LIST_FILE}" "${SHADER_FILE}\n")
endforeach()

add_custom_command(
OUTPUT "${EMBEDDED_HEADER}"
COMMAND ${CMAKE_COMMAND}
"-DSHADER_FILES=${SPIRV_OUTPUTS_ESCAPED}"
"-DSHADER_LIST_FILE=${SHADER_LIST_FILE}"
"-DOUTPUT_FILE=${EMBEDDED_HEADER}"
-P "${CMAKE_SOURCE_DIR}/cmake/EmbedShaders.cmake"
DEPENDS ${SPIRV_OUTPUTS} "${CMAKE_SOURCE_DIR}/cmake/EmbedShaders.cmake"
Expand Down
14 changes: 10 additions & 4 deletions cmake/EmbedShaders.cmake
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# EmbedShaders.cmake - Generate C++ header with embedded SPIR-V shaders
# Usage: cmake -DSHADER_FILES="file1;file2;..." -DOUTPUT_FILE="output.hpp" -P EmbedShaders.cmake
# Usage: cmake -DSHADER_LIST_FILE="list.txt" -DOUTPUT_FILE="output.hpp" -P EmbedShaders.cmake

if(NOT SHADER_FILES)
message(FATAL_ERROR "SHADER_FILES not specified")
if(NOT SHADER_LIST_FILE)
message(FATAL_ERROR "SHADER_LIST_FILE not specified")
endif()

if(NOT OUTPUT_FILE)
message(FATAL_ERROR "OUTPUT_FILE not specified")
endif()

# Read shader files from the list file
file(STRINGS "${SHADER_LIST_FILE}" SHADER_FILES)
if(NOT SHADER_FILES)
message(FATAL_ERROR "No shader files found in ${SHADER_LIST_FILE}")
endif()

# Ensure output directory exists
get_filename_component(OUTPUT_DIR "${OUTPUT_FILE}" DIRECTORY)
file(MAKE_DIRECTORY "${OUTPUT_DIR}")
Expand Down Expand Up @@ -80,7 +86,7 @@ endforeach()

# Create a getter function that returns shader data by name
file(APPEND "${OUTPUT_FILE}" "// Get shader by name\n")
file(APPEND "${OUTPUT_FILE}" "inline std::span<const uint8_t> getShader(std::string_view name) {\n")
file(APPEND "${OUTPUT_FILE}" "inline std::span<const uint8_t> getShader([[maybe_unused]] std::string_view name) {\n")

set(FIRST_SHADER TRUE)
foreach(SHADER_NAME ${SHADER_NAMES})
Expand Down
5 changes: 4 additions & 1 deletion shaders/basic.frag
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ layout(push_constant) uniform MaterialData {
vec4 diffuseColor; // RGB + alpha
vec4 emissiveColor; // RGB + intensity
vec4 specularColor; // RGB + shininess
vec3 hoverTint; // RGB tint for hover highlighting (1,1,1 = no tint)
uint flags; // Material flags
float alphaThreshold;
uint useTexture; // 1 = sample texture
float padding;
} material;

// Material flags
Expand Down Expand Up @@ -74,5 +74,8 @@ void main() {
result += material.emissiveColor.rgb;
}

// Apply hover tint
result *= material.hoverTint;

outColor = vec4(result, baseColor.a);
}
8 changes: 7 additions & 1 deletion shaders/skeleton.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ layout(location = 0) in vec3 fragColor;

layout(location = 0) out vec4 outColor;

// Push constant for hover tint
layout(push_constant) uniform HoverData {
vec3 hoverTint; // RGB tint for hover highlighting (1,1,1 = no tint)
} hover;

void main() {
outColor = vec4(fragColor, 1.0);
vec3 color = fragColor * hover.hoverTint;
outColor = vec4(color, 1.0);
}
2 changes: 1 addition & 1 deletion src/core/pipeline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ struct MaterialPushConstant {
alignas(16) glm::vec4 diffuseColor; // RGB + alpha
alignas(16) glm::vec4 emissiveColor; // RGB + intensity
alignas(16) glm::vec4 specularColor; // RGB + shininess
alignas(16) glm::vec3 hoverTint; // RGB tint for hover highlighting (1,1,1 = no tint)
Copy link

Choose a reason for hiding this comment

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

Potential shader layout mismatch: vec3 in std140 layout takes 12 bytes but following fields may not align correctly. In GLSL push constants, after a vec3 at offset 48, the next field starts at offset 60. The C++ alignas(4) on flags may not match this layout, potentially causing the shader to read wrong values.

Consider using vec4 instead of vec3 for hoverTint, or verify the struct size matches expected shader layout (should be 76 bytes total).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/pipeline.hpp
Line: 71:71

Comment:
Potential shader layout mismatch: `vec3` in std140 layout takes 12 bytes but following fields may not align correctly. In GLSL push constants, after a `vec3` at offset 48, the next field starts at offset 60. The C++ `alignas(4)` on `flags` may not match this layout, potentially causing the shader to read wrong values.

Consider using `vec4` instead of `vec3` for `hoverTint`, or verify the struct size matches expected shader layout (should be 76 bytes total).

How can I resolve this? If you propose a fix, please make it concise.

alignas(4) uint32_t flags; // Material flags
alignas(4) float alphaThreshold; // For alpha testing
alignas(4) uint32_t useTexture; // 1 = sample texture, 0 = use vertex color
alignas(4) float padding;
};

// Pipeline configuration for different blend modes
Expand Down
121 changes: 116 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "render/bone_buffer.hpp"
#include "render/camera.hpp"
#include "render/hlod_model.hpp"
#include "render/hover_detector.hpp"
#include "render/material.hpp"
#include "render/renderable_mesh.hpp"
#include "render/skeleton.hpp"
Expand Down Expand Up @@ -111,6 +112,9 @@ class VulkanW3DViewer {
float lastFrameTime_ = 0.0f;
float lastAppliedFrame_ = -1.0f; // Track last frame applied to pose

// Hover detection
w3d::HoverDetector hoverDetector_;

static constexpr uint32_t WIDTH = 1280;
static constexpr uint32_t HEIGHT = 720;
static constexpr int MAX_FRAMES_IN_FLIGHT = 2;
Expand Down Expand Up @@ -510,6 +514,40 @@ class VulkanW3DViewer {
ImGui::ShowDemoWindow(&showDemoWindow_);
}
#endif

// Display hover name overlay
const auto &hover = hoverDetector_.state();
if (hover.isHovering() && !hover.objectName.empty()) {
// Position tooltip near mouse cursor
ImVec2 mousePos = ImGui::GetMousePos();
ImGui::SetNextWindowPos(ImVec2(mousePos.x + 15, mousePos.y + 15));

ImGui::Begin("##HoverTooltip", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing);

// Display object type and name
const char *typeStr = "";
switch (hover.type) {
case w3d::HoverType::Mesh:
typeStr = "Mesh";
break;
case w3d::HoverType::Bone:
typeStr = "Bone";
break;
case w3d::HoverType::Joint:
typeStr = "Joint";
break;
default:
break;
}

ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.5f, 1.0f), "%s: %s", typeStr,
hover.objectName.c_str());

ImGui::End();
}
}

void drawViewportWindow() {
Expand Down Expand Up @@ -714,6 +752,58 @@ class VulkanW3DViewer {
ImGui::End();
}

void updateHover() {
// Reset hover state by default
hoverDetector_.state().reset();

// Skip if ImGui wants mouse (over UI elements)
if (ImGui::GetIO().WantCaptureMouse) {
return;
}

// We need to check if mouse is over the viewport window and get its coordinates
// This must be done during ImGui frame, but we're outside of ImGui rendering here
// So we'll store viewport info during drawViewportWindow() and use it here

// For now, use a simpler approach: check if viewport is hovered during next frame
// We'll need to refactor to call this from within the viewport window context

// Get mouse position in window coordinates
double mouseX, mouseY;
glfwGetCursorPos(window_, &mouseX, &mouseY);

// Get swapchain (full render target) dimensions
auto extent = context_.swapchainExtent();

// Get camera matrices (must match rendering)
auto view = camera_.viewMatrix();
auto proj = glm::perspective(
glm::radians(45.0f), static_cast<float>(extent.width) / static_cast<float>(extent.height),
0.01f, 10000.0f);
proj[1][1] *= -1; // Vulkan Y-flip

// Update hover detector with ray using full window coordinates
// Note: This assumes the viewport fills the entire window
hoverDetector_.update(
glm::vec2(static_cast<float>(mouseX), static_cast<float>(mouseY)),
glm::vec2(static_cast<float>(extent.width), static_cast<float>(extent.height)), view, proj);

// Test skeleton first (priority over meshes)
if (showSkeleton_ && skeletonRenderer_.hasData()) {
hoverDetector_.testSkeleton(skeletonRenderer_, 0.05f);
}

// Test meshes
if (showMesh_) {
if (useHLodModel_ && hlodModel_.hasData()) {
// TODO: Implement HLod hover detection
// For now, skip HLod models
} else if (renderableMesh_.hasData()) {
hoverDetector_.testMeshes(renderableMesh_);
}
}
}

void recordCommandBuffer(vk::CommandBuffer cmd, uint32_t imageIndex) {
vk::CommandBufferBeginInfo beginInfo{};
cmd.begin(beginInfo);
Expand Down Expand Up @@ -767,6 +857,7 @@ class VulkanW3DViewer {
materialData.diffuseColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
materialData.emissiveColor = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
materialData.specularColor = glm::vec4(0.2f, 0.2f, 0.2f, 32.0f);
materialData.hoverTint = glm::vec3(1.0f); // No tint for HLod (not yet implemented)
materialData.flags = 0;
materialData.alphaThreshold = 0.5f;

Expand Down Expand Up @@ -807,6 +898,7 @@ class VulkanW3DViewer {
materialData.diffuseColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
materialData.emissiveColor = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
materialData.specularColor = glm::vec4(0.2f, 0.2f, 0.2f, 32.0f);
materialData.hoverTint = glm::vec3(1.0f); // No tint for HLod (not yet implemented)
materialData.flags = 0;
materialData.alphaThreshold = 0.5f;

Expand Down Expand Up @@ -849,10 +941,17 @@ class VulkanW3DViewer {
materialData.alphaThreshold = 0.5f;
materialData.useTexture = 0;

cmd.pushConstants(pipeline_.layout(), vk::ShaderStageFlagBits::eFragment, 0,
sizeof(w3d::MaterialPushConstant), &materialData);

renderableMesh_.draw(cmd);
// Use hover detection for simple meshes
const glm::vec3 hoverTint(1.5f, 1.5f, 1.3f); // Warm highlight
const auto &hover = hoverDetector_.state();

renderableMesh_.drawWithHover(
cmd, hover.type == w3d::HoverType::Mesh ? static_cast<int>(hover.objectIndex) : -1,
hoverTint, [&](size_t /*meshIndex*/, const glm::vec3 &tint) {
materialData.hoverTint = tint;
cmd.pushConstants(pipeline_.layout(), vk::ShaderStageFlagBits::eFragment, 0,
sizeof(w3d::MaterialPushConstant), &materialData);
});
}
}

Expand All @@ -861,7 +960,16 @@ class VulkanW3DViewer {
// Skeleton uses same descriptor set layout, so we can reuse the bound descriptor
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, skeletonRenderer_.pipelineLayout(),
0, descriptorManager_.descriptorSet(currentFrame_), {});
skeletonRenderer_.draw(cmd);

// Apply hover tint if hovering over skeleton
const glm::vec3 hoverTint(1.5f, 1.5f, 1.3f); // Warm highlight
const auto &hover = hoverDetector_.state();
glm::vec3 skeletonTint =
(hover.type == w3d::HoverType::Bone || hover.type == w3d::HoverType::Joint)
? hoverTint
: glm::vec3(1.0f);

skeletonRenderer_.drawWithHover(cmd, skeletonTint);
}

// Draw ImGui
Expand Down Expand Up @@ -970,6 +1078,9 @@ class VulkanW3DViewer {
// Update camera
camera_.update(window_);

// Update hover detection
updateHover();

// Update animation
animationPlayer_.update(deltaTime);

Expand Down
Loading