diff --git a/meson.build b/meson.build index 9e1b394..5779833 100644 --- a/meson.build +++ b/meson.build @@ -31,13 +31,25 @@ deps = [ dependency('vulkan', include_type: 'system'), ] +glslang_dep = cpp.find_library('glslang', required: false) + +if not glslang_dep.found() + glslang_dep = dependency('glslang', required: true, include_type: 'system') +endif + +spirv_dep = cpp.find_library('SPIRV', required: false) + +if not spirv_dep.found() + spirv_dep = dependency('SPIRV', required: true, include_type: 'system') +endif + imgui_dep = dependency('imgui', required: false, include_type: 'system') if not imgui_dep.found() imgui_dep = cpp.find_library('imgui', required: true) endif -deps += imgui_dep +deps += [glslang_dep, spirv_dep, imgui_dep] executable( 'graphics-test', diff --git a/shaders/frag.spv b/shaders/frag.spv deleted file mode 100644 index 9495412..0000000 Binary files a/shaders/frag.spv and /dev/null differ diff --git a/shaders/shader.frag b/shaders/shader.frag deleted file mode 100644 index 873f541..0000000 --- a/shaders/shader.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 450 - -layout(binding = 1) uniform sampler2D texSampler; - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec2 fragTexCoord; - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = texture(texSampler, fragTexCoord); -} diff --git a/shaders/shader.vert b/shaders/shader.vert deleted file mode 100644 index 840711c..0000000 --- a/shaders/shader.vert +++ /dev/null @@ -1,20 +0,0 @@ -#version 450 - -layout(binding = 0) uniform UniformBufferObject { - mat4 model; - mat4 view; - mat4 proj; -} ubo; - -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; -layout(location = 2) in vec2 inTexCoord; - -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec2 fragTexCoord; - -void main() { - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); - fragColor = inColor; - fragTexCoord = inTexCoord; -} diff --git a/shaders/shaderc b/shaders/shaderc deleted file mode 100755 index 9f7e6e5..0000000 --- a/shaders/shaderc +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" || exit - -glslc -c ./shader.vert -o ./vert.spv -glslc -c ./shader.frag -o ./frag.spv \ No newline at end of file diff --git a/shaders/vert.spv b/shaders/vert.spv deleted file mode 100644 index 99280dc..0000000 Binary files a/shaders/vert.spv and /dev/null differ diff --git a/src/main.cpp b/src/main.cpp index 9447aed..c47d212 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE // Include custom utility headers +#include "util/shader.h" // Shader compilation utilities #include "util/types.h" // Custom type definitions #include "util/unique_image.h" // Custom image handling utilities #include "util/vertex.h" // Custom vertex structure definition @@ -39,14 +40,6 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE constexpr i32 WIDTH = 800; constexpr i32 HEIGHT = 600; -// File paths for 3D model and texture -constexpr const char* MODEL_PATH = "models/viking_room.obj"; -constexpr const char* TEXTURE_PATH = "textures/viking_room.png"; - -// File paths for shader programs -constexpr const char* FRAGMENT_SHADER_PATH = "shaders/frag.spv"; -constexpr const char* VERTEX_SHADER_PATH = "shaders/vert.spv"; - // Maximum number of frames that can be processed concurrently constexpr i32 MAX_FRAMES_IN_FLIGHT = 2; @@ -73,10 +66,12 @@ class VulkanApp { * It also cleans up resources when the application is closed. */ fn run() -> void { + InitGlslang(); initWindow(); // Initialize the application window initVulkan(); // Initialize Vulkan mainLoop(); // Enter the main rendering loop + CleanupGlslang(); cleanupSwapChain(); // Clean up swap chain resources for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) @@ -472,7 +467,7 @@ class VulkanApp { // Application metadata vk::ApplicationInfo appInfo { - .pApplicationName = "Hello Triangle", + .pApplicationName = "Vulkan App", .applicationVersion = 1, .pEngineName = "No Engine", .engineVersion = 1, @@ -848,8 +843,8 @@ class VulkanApp { * states. */ fn createGraphicsPipeline() -> void { - std::vector vertShaderCode = readFile(VERTEX_SHADER_PATH); - std::vector fragShaderCode = readFile(FRAGMENT_SHADER_PATH); + std::vector vertShaderCode = CompileShader(vertShaderSrc, EShLangVertex); + std::vector fragShaderCode = CompileShader(fragShaderSrc, EShLangFragment); vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode); vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode); @@ -1105,7 +1100,7 @@ class VulkanApp { * @param format The format to check. * @return True if the format has a stencil component, false otherwise. */ - static fn hasStencilComponent(vk::Format format) { + static fn hasStencilComponent(const vk::Format& format) { return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; } @@ -1117,7 +1112,8 @@ class VulkanApp { * buffer to the texture image. It also generates mipmaps for the texture. */ fn createTextureImage() -> void { - stb::UniqueImage image(TEXTURE_PATH); + std::filesystem::path texturePath = std::filesystem::current_path() / "textures" / "viking_room.png"; + stb::UniqueImage image(texturePath); u8* pixels = image.getData(); i32 texWidth = image.getWidth(), texHeight = image.getHeight(); @@ -1176,8 +1172,13 @@ class VulkanApp { * This function generates mipmaps for the given texture image by repeatedly scaling down * the image by half until reaching the smallest mip level. */ - fn generateMipmaps(vk::Image image, vk::Format imageFormat, i32 texWidth, i32 texHeight, u32 mipLevels) - -> void { + fn generateMipmaps( + const vk::Image& image, + const vk::Format& imageFormat, + const i32& texWidth, + const i32& texHeight, + const u32& mipLevels + ) -> void { vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(imageFormat); if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) @@ -1527,7 +1528,9 @@ class VulkanApp { std::vector materials; std::string warn, err; - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH)) + std::filesystem::path modelPath = std::filesystem::current_path() / "models" / "viking_room.obj"; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, modelPath.string().c_str())) throw std::runtime_error(warn + err); std::unordered_map uniqueVertices {}; @@ -2183,10 +2186,10 @@ class VulkanApp { * * This function takes compiled shader code and creates a Vulkan shader module from it. */ - fn createShaderModule(const std::vector& code) -> vk::UniqueShaderModule { + fn createShaderModule(const std::vector& code) -> vk::UniqueShaderModule { vk::ShaderModuleCreateInfo createInfo { - .codeSize = code.size(), - .pCode = std::bit_cast(code.data()), + .codeSize = code.size() * sizeof(u32), + .pCode = code.data(), }; return mDevice->createShaderModuleUnique(createInfo); diff --git a/src/util/shader.h b/src/util/shader.h new file mode 100644 index 0000000..fd41eb6 --- /dev/null +++ b/src/util/shader.h @@ -0,0 +1,196 @@ +#include +#include +#include + +#include "types.h" + +const TBuiltInResource DefaultTBuiltInResource = { + .maxLights = 32, + .maxClipPlanes = 6, + .maxTextureUnits = 32, + .maxTextureCoords = 32, + .maxVertexAttribs = 64, + .maxVertexUniformComponents = 4096, + .maxVaryingFloats = 64, + .maxVertexTextureImageUnits = 32, + .maxCombinedTextureImageUnits = 80, + .maxTextureImageUnits = 32, + .maxFragmentUniformComponents = 4096, + .maxDrawBuffers = 32, + .maxVertexUniformVectors = 128, + .maxVaryingVectors = 8, + .maxFragmentUniformVectors = 16, + .maxVertexOutputVectors = 16, + .maxFragmentInputVectors = 15, + .minProgramTexelOffset = -8, + .maxProgramTexelOffset = 7, + .maxClipDistances = 8, + .maxComputeWorkGroupCountX = 65535, + .maxComputeWorkGroupCountY = 65535, + .maxComputeWorkGroupCountZ = 65535, + .maxComputeWorkGroupSizeX = 1024, + .maxComputeWorkGroupSizeY = 1024, + .maxComputeWorkGroupSizeZ = 64, + .maxComputeUniformComponents = 1024, + .maxComputeTextureImageUnits = 16, + .maxComputeImageUniforms = 8, + .maxComputeAtomicCounters = 8, + .maxComputeAtomicCounterBuffers = 1, + .maxVaryingComponents = 60, + .maxVertexOutputComponents = 64, + .maxGeometryInputComponents = 64, + .maxGeometryOutputComponents = 128, + .maxFragmentInputComponents = 128, + .maxImageUnits = 8, + .maxCombinedImageUnitsAndFragmentOutputs = 8, + .maxCombinedShaderOutputResources = 8, + .maxImageSamples = 0, + .maxVertexImageUniforms = 0, + .maxTessControlImageUniforms = 0, + .maxTessEvaluationImageUniforms = 0, + .maxGeometryImageUniforms = 0, + .maxFragmentImageUniforms = 8, + .maxCombinedImageUniforms = 8, + .maxGeometryTextureImageUnits = 16, + .maxGeometryOutputVertices = 256, + .maxGeometryTotalOutputComponents = 1024, + .maxGeometryUniformComponents = 1024, + .maxGeometryVaryingComponents = 64, + .maxTessControlInputComponents = 128, + .maxTessControlOutputComponents = 128, + .maxTessControlTextureImageUnits = 16, + .maxTessControlUniformComponents = 1024, + .maxTessControlTotalOutputComponents = 4096, + .maxTessEvaluationInputComponents = 128, + .maxTessEvaluationOutputComponents = 128, + .maxTessEvaluationTextureImageUnits = 16, + .maxTessEvaluationUniformComponents = 1024, + .maxTessPatchComponents = 120, + .maxPatchVertices = 32, + .maxTessGenLevel = 64, + .maxViewports = 16, + .maxVertexAtomicCounters = 0, + .maxTessControlAtomicCounters = 0, + .maxTessEvaluationAtomicCounters = 0, + .maxGeometryAtomicCounters = 0, + .maxFragmentAtomicCounters = 8, + .maxCombinedAtomicCounters = 8, + .maxAtomicCounterBindings = 1, + .maxVertexAtomicCounterBuffers = 0, + .maxTessControlAtomicCounterBuffers = 0, + .maxTessEvaluationAtomicCounterBuffers = 0, + .maxGeometryAtomicCounterBuffers = 0, + .maxFragmentAtomicCounterBuffers = 1, + .maxCombinedAtomicCounterBuffers = 1, + .maxAtomicCounterBufferSize = 16384, + .maxTransformFeedbackBuffers = 4, + .maxTransformFeedbackInterleavedComponents = 64, + .maxCullDistances = 8, + .maxCombinedClipAndCullDistances = 8, + .maxSamples = 4, + .maxMeshOutputVerticesNV = 256, + .maxMeshOutputPrimitivesNV = 512, + .maxMeshWorkGroupSizeX_NV = 32, + .maxMeshWorkGroupSizeY_NV = 1, + .maxMeshWorkGroupSizeZ_NV = 1, + .maxTaskWorkGroupSizeX_NV = 32, + .maxTaskWorkGroupSizeY_NV = 1, + .maxTaskWorkGroupSizeZ_NV = 1, + .maxMeshViewCountNV = 4, + .maxMeshOutputVerticesEXT = 256, + .maxMeshOutputPrimitivesEXT = 512, + .maxMeshWorkGroupSizeX_EXT = 32, + .maxMeshWorkGroupSizeY_EXT = 1, + .maxMeshWorkGroupSizeZ_EXT = 1, + .maxTaskWorkGroupSizeX_EXT = 32, + .maxTaskWorkGroupSizeY_EXT = 1, + .maxTaskWorkGroupSizeZ_EXT = 1, + .maxMeshViewCountEXT = 4, + .maxDualSourceDrawBuffersEXT = 1, + .limits = { + .nonInductiveForLoops = true, + .whileLoops = true, + .doWhileLoops = true, + .generalUniformIndexing = true, + .generalAttributeMatrixVectorIndexing = true, + .generalVaryingIndexing = true, + .generalSamplerIndexing = true, + .generalVariableIndexing = true, + .generalConstantMatrixVectorIndexing = true, + }, +}; + +constexpr const char* vertShaderSrc = R"glsl( + #version 450 + + layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; + } ubo; + + layout(location = 0) in vec3 inPosition; + layout(location = 1) in vec3 inColor; + layout(location = 2) in vec2 inTexCoord; + + layout(location = 0) out vec3 fragColor; + layout(location = 1) out vec2 fragTexCoord; + + void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; + } +)glsl"; + +constexpr const char* fragShaderSrc = R"glsl( + #version 450 + + layout(binding = 1) uniform sampler2D texSampler; + + layout(location = 0) in vec3 fragColor; + layout(location = 1) in vec2 fragTexCoord; + + layout(location = 0) out vec4 outColor; + + void main() { + outColor = texture(texSampler, fragTexCoord); + } +)glsl"; + +inline void InitGlslang() { glslang::InitializeProcess(); } + +inline void CleanupGlslang() { glslang::FinalizeProcess(); } + +inline fn CompileShader(const char* source, EShLanguage shaderType) -> std::vector { + glslang::TShader shader(shaderType); + shader.setStrings(&source, 1); + + // Set shader environment details (language version, profile) + shader.setEnvInput(glslang::EShSourceGlsl, shaderType, glslang::EShClientVulkan, 450); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_2); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); + + // Parse GLSL to AST (abstract syntax tree) + if (!shader.parse(&DefaultTBuiltInResource, 450, false, EShMsgDefault)) { + std::cerr << "GLSL parsing failed: " << shader.getInfoLog() << std::endl; + return {}; + } + + // Create a program to hold the compiled shader + glslang::TProgram program; + program.addShader(&shader); + + // Link the program + if (!program.link(EShMsgDefault)) { + std::cerr << "Linking failed: " << program.getInfoLog() << std::endl; + return {}; + } + + // Convert AST to SPIR-V + std::vector spirv; + glslang::SpvOptions spvOptions; + glslang::GlslangToSpv(*program.getIntermediate(shaderType), spirv, &spvOptions); + + return spirv; +} diff --git a/src/util/unique_image.h b/src/util/unique_image.h index d3f2a5d..07a2b24 100644 --- a/src/util/unique_image.h +++ b/src/util/unique_image.h @@ -1,3 +1,5 @@ +#include + #define STB_IMAGE_IMPLEMENTATION #include @@ -7,7 +9,7 @@ namespace stb { class UniqueImage { public: // Constructor - UniqueImage(const char* filename) { load(filename); } + UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); } // Deleted copy constructor and assignment operator UniqueImage(const UniqueImage&) = delete; diff --git a/textures/texture.jpg b/textures/texture.jpg deleted file mode 100644 index 975e338..0000000 Binary files a/textures/texture.jpg and /dev/null differ