diff --git a/CMakeLists.txt b/CMakeLists.txt index e1d5ca2..edf6dfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/glsl_reflector/CMakeLists.txt b/glsl_reflector/CMakeLists.txt new file mode 100644 index 0000000..2b0dc45 --- /dev/null +++ b/glsl_reflector/CMakeLists.txt @@ -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) diff --git a/glsl_reflector/modules/glsl_reflector.ixx b/glsl_reflector/modules/glsl_reflector.ixx new file mode 100644 index 0000000..ad7e841 --- /dev/null +++ b/glsl_reflector/modules/glsl_reflector.ixx @@ -0,0 +1,87 @@ +// glsl_reflector.ixx +// Created by sophomore on 11/19/25. +// +module; +#include +#include +#include +#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; + } + } + + 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 diff --git a/glsl_reflector/modules/glsl_reflector.type.ixx b/glsl_reflector/modules/glsl_reflector.type.ixx new file mode 100644 index 0000000..29f83af --- /dev/null +++ b/glsl_reflector/modules/glsl_reflector.type.ixx @@ -0,0 +1,30 @@ +// glsl_reflector.type.ixx +// Created by wsqsy on 11/20/2025. +// +module; +#include +#include +#include +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 inputs{}; + }; +} // namespace sopho diff --git a/glsl_reflector/test/test.cpp b/glsl_reflector/test/test.cpp new file mode 100644 index 0000000..4bb8d00 --- /dev/null +++ b/glsl_reflector/test/test.cpp @@ -0,0 +1,38 @@ +// test.cpp +// Created by wsqsy on 11/19/2025. +// +#include +#include + +#include + +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); +} diff --git a/main.cpp b/main.cpp index ceb8407..7676a5f 100644 --- a/main.cpp +++ b/main.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include #include "imgui.h" #include "imgui_impl_sdl3.h" @@ -19,6 +21,7 @@ #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_keycode.h" +import glsl_reflector; import sdl_wrapper; struct CameraUniform @@ -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(ptr), 0.01f, -1.f, 1.f); - break; - case SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4: - changed |= ImGui::DragFloat4(std::format("color{}", vertex_index).data(), - reinterpret_cast(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(ptr), 0.01f, -1.f, 1.f); + break; + case 4: + changed |= ImGui::DragFloat4(std::format("{}{}", format.name, vertex_index).data(), + reinterpret_cast(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; } } @@ -276,6 +285,12 @@ void main() SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to set vertex shader from editor, error = %d", static_cast(result.error())); } + else + { + auto new_data = m_gpu->create_data(*m_renderable->procedural(), 3); + m_renderable->data() = std::make_shared(std::move(new_data.value())); + m_renderable->data()->upload(); + } } } break; diff --git a/sdl_wrapper/CMakeLists.txt b/sdl_wrapper/CMakeLists.txt index 6d3c439..24b37cb 100644 --- a/sdl_wrapper/CMakeLists.txt +++ b/sdl_wrapper/CMakeLists.txt @@ -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) diff --git a/sdl_wrapper/sdl_wrapper.render_procedural.cpp b/sdl_wrapper/sdl_wrapper.render_procedural.cpp index 8c60ae2..9558bdc 100644 --- a/sdl_wrapper/sdl_wrapper.render_procedural.cpp +++ b/sdl_wrapper/sdl_wrapper.render_procedural.cpp @@ -14,7 +14,7 @@ module; module sdl_wrapper; import :render_procedural; import :gpu; - +import glsl_reflector; namespace sopho { /** @@ -60,7 +60,6 @@ namespace sopho RenderProcedural::RenderProcedural(std::shared_ptr 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); @@ -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{}; } diff --git a/sdl_wrapper/sdl_wrapper.render_procedural.ixx b/sdl_wrapper/sdl_wrapper.render_procedural.ixx index 557c01d..adac10a 100644 --- a/sdl_wrapper/sdl_wrapper.render_procedural.ixx +++ b/sdl_wrapper/sdl_wrapper.render_procedural.ixx @@ -66,9 +66,23 @@ export namespace sopho /// On failure, a compile or creation error is returned. [[nodiscard]] std::expected set_fragment_shader(const std::string& source); - auto set_vertex_attributes(std::vector 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(m_vertex_layout.get_vertex_attributes().size()); + m_modified = true; } diff --git a/sdl_wrapper/sdl_wrapper.vertex_layout.ixx b/sdl_wrapper/sdl_wrapper.vertex_layout.ixx index 8ce07c6..c3203c0 100644 --- a/sdl_wrapper/sdl_wrapper.vertex_layout.ixx +++ b/sdl_wrapper/sdl_wrapper.vertex_layout.ixx @@ -6,7 +6,7 @@ module; #include #include "SDL3/SDL_gpu.h" export module sdl_wrapper:vertex_layout; - +import glsl_reflector; namespace sopho { @@ -80,31 +80,52 @@ namespace sopho } } + export auto to_sdl_format(BasicType basic_type, int vector_size) + { + switch (basic_type) + { + case BasicType::FLOAT: + { + switch (vector_size) + { + case 3: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; + case 4: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + default: + return SDL_GPU_VERTEXELEMENTFORMAT_INVALID; + } + } + default: + return SDL_GPU_VERTEXELEMENTFORMAT_INVALID; + } + } + class VertexLayout { + VertexReflection m_vertex_reflection{}; std::vector raw{}; std::vector attributes{}; uint32_t stride = 0; public: - auto set_vertex_attributes(std::vector vertex_attributes) + auto set_vertex_reflection(const VertexReflection& vertex_reflection) { - raw.swap(vertex_attributes); + m_vertex_reflection = vertex_reflection; stride = 0; attributes.clear(); - for (int i = 0; i < raw.size(); ++i) + for (const auto& input : vertex_reflection.inputs) { SDL_GPUVertexAttribute vertex_attribute{}; - vertex_attribute.location = i; + vertex_attribute.location = input.location; vertex_attribute.buffer_slot = 0; - vertex_attribute.format = raw[i]; + vertex_attribute.format = to_sdl_format(input.basic_type, input.vector_size); vertex_attribute.offset = stride; stride += get_size(vertex_attribute.format); attributes.push_back(vertex_attribute); } } - - const auto& get_vertex_format() { return raw; } + const auto& get_vertex_reflection() { return m_vertex_reflection; } const auto& get_vertex_attributes() { return attributes; } auto get_stride() const { return stride; } };