Skip to content
Merged
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,20 @@ target_include_directories(imgui PUBLIC
)
target_link_libraries(imgui PUBLIC SDL3::SDL3)

FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.17.0
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(googletest)

include(CTest)
include(GoogleTest)

add_subdirectory(sdl_wrapper)
add_subdirectory(glsl_reflector)

add_executable(SDL_TEST main.cpp)
target_link_libraries(SDL_TEST PRIVATE imgui sdl_wrapper)
Expand Down
22 changes: 22 additions & 0 deletions glsl_reflector/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.30)
project(glsl_reflector LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
add_library(glsl_reflector STATIC)

target_sources(glsl_reflector
PUBLIC
FILE_SET cxx_modules TYPE CXX_MODULES FILES
modules/glsl_reflector.ixx
modules/glsl_reflector.type.ixx
)

target_link_libraries(glsl_reflector PUBLIC glslang glslang::glslang-default-resource-limits)

add_executable(glslr test/test.cpp)

target_link_libraries(glslr PUBLIC glsl_reflector gtest gtest_main)

add_test(NAME unit_tests COMMAND glslr)
gtest_discover_tests(glslr
DISCOVERY_TIMEOUT 60)
87 changes: 87 additions & 0 deletions glsl_reflector/modules/glsl_reflector.ixx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// glsl_reflector.ixx
// Created by sophomore on 11/19/25.
//
module;
#include <iostream>
#include <string>
#include <vector>
#include "glslang/Include/intermediate.h"
#include "glslang/MachineIndependent/localintermediate.h"
#include "glslang/Public/ResourceLimits.h"
#include "glslang/Public/ShaderLang.h"
export module glsl_reflector;
export import :type;

namespace sopho
{

class GlslangProcessGuard
{
public:
GlslangProcessGuard() { glslang::InitializeProcess(); }
~GlslangProcessGuard() { glslang::FinalizeProcess(); }
};

auto to_basic_type(glslang::TBasicType gl_basic_type)
{
switch (gl_basic_type)
{
case glslang::EbtFloat:
return BasicType::FLOAT;
default:
return BasicType::NONE;
}
}
Comment on lines +25 to +34
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete type support in to_basic_type.

The function only handles glslang::EbtFloat, returning BasicType::NONE for all other types. Common GLSL types like int, uint, bool, and double are unhandled, which will cause vertex reflection to report incorrect types for non-float attributes.

If this is intentional (e.g., your shaders only use floats), add a comment explaining the limitation. Otherwise, expand support:

 auto to_basic_type(glslang::TBasicType gl_basic_type)
 {
     switch (gl_basic_type)
     {
     case glslang::EbtFloat:
         return BasicType::FLOAT;
+    case glslang::EbtInt:
+        return BasicType::INT;
+    case glslang::EbtUint:
+        return BasicType::UINT;
+    case glslang::EbtBool:
+        return BasicType::BOOL;
+    case glslang::EbtDouble:
+        return BasicType::DOUBLE;
     default:
         return BasicType::NONE;
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In glsl_reflector/modules/glsl_reflector.ixx around lines 25 to 34, the
to_basic_type function only maps glslang::EbtFloat and returns BasicType::NONE
for all other GLSL basic types, causing non-float attributes (int, uint, bool,
double, etc.) to be reported incorrectly; update the switch to map all relevant
glslang::TBasicType enum values (at minimum EbtInt -> BasicType::INT, EbtUint ->
BasicType::UINT, EbtBool -> BasicType::BOOL, EbtDouble -> BasicType::DOUBLE,
plus any sampler/struct types you need) to the corresponding BasicType entries,
and if the omission was intentional add a clear comment explaining the
limitation and keep an explicit default that logs or asserts on unsupported
types so it’s obvious at runtime.


export auto reflect_vertex(const std::string& vertex_source)
{
GlslangProcessGuard glslang_initializer{};
VertexReflection result{};
glslang::TShader shader(EShLangVertex);
std::vector sources{vertex_source.data()};
shader.setStrings(sources.data(), sources.size());

int clientInputSemanticsVersion = 100; // not used for desktop GL
glslang::EShTargetClientVersion clientVersion = glslang::EShTargetOpenGL_450;
glslang::EShTargetLanguageVersion targetVersion = glslang::EShTargetSpv_1_0;

shader.setEnvInput(glslang::EShSourceGlsl, EShLangVertex, glslang::EShClientOpenGL,
clientInputSemanticsVersion);
shader.setEnvClient(glslang::EShClientOpenGL, clientVersion);
shader.setEnvTarget(glslang::EShTargetSpv, targetVersion);

EShMessages messages = (EShMessages)(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);

if (!shader.parse(GetDefaultResources(), 450, ENoProfile, false, false, messages))
{
std::cerr << "Parse failed:\n" << shader.getInfoLog() << std::endl;
return result;
}

glslang::TProgram program;
program.addShader(&shader);
if (!program.link(messages))
{
std::cerr << "Link failed:\n" << program.getInfoLog() << std::endl;
return result;
}
program.buildReflection();
auto count = program.getNumPipeInputs();
for (auto i = 0; i < count; i++)
{
const auto& var = program.getPipeInput(i);

const auto& type = var.getType();
const auto& q = type->getQualifier();

std::string name = var.name.c_str();
auto vector = type->getVectorSize();
std::cout << name << vector << std::endl;
result.inputs.emplace_back(VertexInfo{.location = var.layoutLocation(),
.name = var.name,
.basic_type = to_basic_type(type->getBasicType()),
.vector_size = type->getVectorSize()});
}
return result;
}
} // namespace sopho
30 changes: 30 additions & 0 deletions glsl_reflector/modules/glsl_reflector.type.ixx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// glsl_reflector.type.ixx
// Created by wsqsy on 11/20/2025.
//
module;
#include <cstdint>
#include <string>
#include <vector>
export module glsl_reflector:type;

export namespace sopho
{
enum class BasicType : std::uint8_t
{
NONE,
FLOAT
};

struct VertexInfo
{
std::uint32_t location{};
std::string name{};
BasicType basic_type{};
int vector_size{};
};

struct VertexReflection
{
std::vector<VertexInfo> inputs{};
};
} // namespace sopho
38 changes: 38 additions & 0 deletions glsl_reflector/test/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// test.cpp
// Created by wsqsy on 11/19/2025.
//
#include <string>
#include <vector>

#include <gtest/gtest.h>

import glsl_reflector;

TEST(BasicTest, Basic) { EXPECT_EQ(1 + 1, 2); }

TEST(reflect_vertex, Basic)
{
auto reflect_info = sopho::reflect_vertex(R"(
#version 450
layout(location = 0) in vec3 inPos;
layout(location = 1) in vec3 inNormal;

layout(location = 0) out vec3 outNormal;

void main() {
gl_Position = vec4(inPos, 1.0);
outNormal = inNormal;
}
)");
EXPECT_EQ(reflect_info.inputs.size(), 2);

EXPECT_EQ(reflect_info.inputs[0].location, 0);
EXPECT_EQ(reflect_info.inputs[0].name, "inPos");
EXPECT_EQ(reflect_info.inputs[0].basic_type, sopho::BasicType::FLOAT);
EXPECT_EQ(reflect_info.inputs[0].vector_size, 3);

EXPECT_EQ(reflect_info.inputs[1].location, 1);
EXPECT_EQ(reflect_info.inputs[1].name, "inNormal");
EXPECT_EQ(reflect_info.inputs[1].basic_type, sopho::BasicType::FLOAT);
EXPECT_EQ(reflect_info.inputs[1].vector_size, 3);
}
35 changes: 25 additions & 10 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include <array>
#include <cmath>
#include <expected>
#include <format>
#include <iostream>

Choose a reason for hiding this comment

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

warning: included header format is not used directly [misc-include-cleaner]

Suggested change
#include <iostream>
#include <iostream>

#include <memory>
#include <numbers>
#include <string>
#include <variant>

#include "imgui.h"
#include "imgui_impl_sdl3.h"
Expand All @@ -19,6 +21,7 @@
#include "SDL3/SDL_gpu.h"
#include "SDL3/SDL_keycode.h"

import glsl_reflector;
import sdl_wrapper;

struct CameraUniform
Expand Down Expand Up @@ -229,22 +232,28 @@ void main()
auto ptr = editor_data.raw;
for (int vertex_index = 0; vertex_index < editor_data.vertex_count; ++vertex_index)
{
for (const auto& format : editor_data.layout.get_vertex_format())
for (const auto& format : editor_data.layout.get_vertex_reflection().inputs)
{
switch (format)
switch (format.basic_type)
{
case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3:
changed |= ImGui::DragFloat3(std::format("node{}", vertex_index).data(),
reinterpret_cast<float*>(ptr), 0.01f, -1.f, 1.f);
break;
case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4:
changed |= ImGui::DragFloat4(std::format("color{}", vertex_index).data(),
reinterpret_cast<float*>(ptr), 0.01f, -1.f, 1.f);
case sopho::BasicType::FLOAT:
{
switch (format.vector_size)
{
case 3:
changed |= ImGui::DragFloat3(std::format("{}{}", format.name, vertex_index).data(),
reinterpret_cast<float*>(ptr), 0.01f, -1.f, 1.f);
break;
case 4:
changed |= ImGui::DragFloat4(std::format("{}{}", format.name, vertex_index).data(),
reinterpret_cast<float*>(ptr), 0.01f, -1.f, 1.f);
}
}
break;
default:
break;
}
auto size = sopho::get_size(format);
auto size = sopho::get_size(sopho::to_sdl_format(format.basic_type, format.vector_size));
ptr += size;
}
}
Expand Down Expand Up @@ -276,6 +285,12 @@ void main()
SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to set vertex shader from editor, error = %d",
static_cast<int>(result.error()));
}
else
{
auto new_data = m_gpu->create_data(*m_renderable->procedural(), 3);
m_renderable->data() = std::make_shared<sopho::RenderData>(std::move(new_data.value()));
m_renderable->data()->upload();
}
}
}
break;
Expand Down
2 changes: 1 addition & 1 deletion sdl_wrapper/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ target_sources(sdl_wrapper
sdl_callback_implement.cpp
)

target_link_libraries(sdl_wrapper PUBLIC SDL3::SDL3 shaderc)
target_link_libraries(sdl_wrapper PUBLIC SDL3::SDL3 shaderc glsl_reflector)
6 changes: 4 additions & 2 deletions sdl_wrapper/sdl_wrapper.render_procedural.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module;
module sdl_wrapper;
import :render_procedural;
import :gpu;

import glsl_reflector;
namespace sopho
{
/**
Expand Down Expand Up @@ -60,7 +60,6 @@ namespace sopho
RenderProcedural::RenderProcedural(std::shared_ptr<GpuWrapper> gpu, SDL_GPUTextureFormat swapchain_format) noexcept
: m_gpu(std::move(gpu))
{
m_vertex_layout.set_vertex_attributes({SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4});
// Configure shaderc to target Vulkan/SPIR-V.
m_options.SetTargetEnvironment(shaderc_target_env_vulkan, 0);

Expand Down Expand Up @@ -238,6 +237,9 @@ namespace sopho
m_pipeline_info.vertex_shader = new_shader;
m_modified = true;

auto reflect_result = reflect_vertex(source);
set_vertex_reflection(reflect_result);

return std::monostate{};
}

Expand Down
18 changes: 16 additions & 2 deletions sdl_wrapper/sdl_wrapper.render_procedural.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,23 @@ export namespace sopho
/// On failure, a compile or creation error is returned.
[[nodiscard]] std::expected<std::monostate, GpuError> set_fragment_shader(const std::string& source);

auto set_vertex_attributes(std::vector<SDL_GPUVertexElementFormat> vertex_attributes)
auto set_vertex_reflection(const VertexReflection& vertex_attributes)
{
m_vertex_layout.set_vertex_attributes(std::move(vertex_attributes));
m_vertex_layout.set_vertex_reflection(vertex_attributes);

// Setup vertex buffer description.
m_vertex_buffer_descriptions.clear();
SDL_GPUVertexBufferDescription vb_desc{};
vb_desc.slot = 0;
vb_desc.pitch = m_vertex_layout.get_stride();
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
vb_desc.instance_step_rate = 0;
m_vertex_buffer_descriptions.push_back(vb_desc);

m_pipeline_info.vertex_input_state.vertex_attributes = m_vertex_layout.get_vertex_attributes().data();
m_pipeline_info.vertex_input_state.num_vertex_attributes =
static_cast<std::uint32_t>(m_vertex_layout.get_vertex_attributes().size());

m_modified = true;
}

Expand Down
Loading
Loading