better handling of models, textures and shaders

This commit is contained in:
Mars 2024-10-18 14:34:26 -04:00
parent 4a4939e65b
commit edbcf2406b
10 changed files with 234 additions and 60 deletions

View file

@ -31,13 +31,25 @@ deps = [
dependency('vulkan', include_type: 'system'), 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') imgui_dep = dependency('imgui', required: false, include_type: 'system')
if not imgui_dep.found() if not imgui_dep.found()
imgui_dep = cpp.find_library('imgui', required: true) imgui_dep = cpp.find_library('imgui', required: true)
endif endif
deps += imgui_dep deps += [glslang_dep, spirv_dep, imgui_dep]
executable( executable(
'graphics-test', 'graphics-test',

Binary file not shown.

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

Binary file not shown.

View file

@ -22,6 +22,7 @@
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
// Include custom utility headers // Include custom utility headers
#include "util/shader.h" // Shader compilation utilities
#include "util/types.h" // Custom type definitions #include "util/types.h" // Custom type definitions
#include "util/unique_image.h" // Custom image handling utilities #include "util/unique_image.h" // Custom image handling utilities
#include "util/vertex.h" // Custom vertex structure definition #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 WIDTH = 800;
constexpr i32 HEIGHT = 600; 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 // Maximum number of frames that can be processed concurrently
constexpr i32 MAX_FRAMES_IN_FLIGHT = 2; constexpr i32 MAX_FRAMES_IN_FLIGHT = 2;
@ -73,10 +66,12 @@ class VulkanApp {
* It also cleans up resources when the application is closed. * It also cleans up resources when the application is closed.
*/ */
fn run() -> void { fn run() -> void {
InitGlslang();
initWindow(); // Initialize the application window initWindow(); // Initialize the application window
initVulkan(); // Initialize Vulkan initVulkan(); // Initialize Vulkan
mainLoop(); // Enter the main rendering loop mainLoop(); // Enter the main rendering loop
CleanupGlslang();
cleanupSwapChain(); // Clean up swap chain resources cleanupSwapChain(); // Clean up swap chain resources
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
@ -472,7 +467,7 @@ class VulkanApp {
// Application metadata // Application metadata
vk::ApplicationInfo appInfo { vk::ApplicationInfo appInfo {
.pApplicationName = "Hello Triangle", .pApplicationName = "Vulkan App",
.applicationVersion = 1, .applicationVersion = 1,
.pEngineName = "No Engine", .pEngineName = "No Engine",
.engineVersion = 1, .engineVersion = 1,
@ -848,8 +843,8 @@ class VulkanApp {
* states. * states.
*/ */
fn createGraphicsPipeline() -> void { fn createGraphicsPipeline() -> void {
std::vector<char> vertShaderCode = readFile(VERTEX_SHADER_PATH); std::vector<u32> vertShaderCode = CompileShader(vertShaderSrc, EShLangVertex);
std::vector<char> fragShaderCode = readFile(FRAGMENT_SHADER_PATH); std::vector<u32> fragShaderCode = CompileShader(fragShaderSrc, EShLangFragment);
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode); vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode); vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
@ -1105,7 +1100,7 @@ class VulkanApp {
* @param format The format to check. * @param format The format to check.
* @return True if the format has a stencil component, false otherwise. * @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; 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. * buffer to the texture image. It also generates mipmaps for the texture.
*/ */
fn createTextureImage() -> void { 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(); u8* pixels = image.getData();
i32 texWidth = image.getWidth(), texHeight = image.getHeight(); 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 * This function generates mipmaps for the given texture image by repeatedly scaling down
* the image by half until reaching the smallest mip level. * the image by half until reaching the smallest mip level.
*/ */
fn generateMipmaps(vk::Image image, vk::Format imageFormat, i32 texWidth, i32 texHeight, u32 mipLevels) fn generateMipmaps(
-> void { const vk::Image& image,
const vk::Format& imageFormat,
const i32& texWidth,
const i32& texHeight,
const u32& mipLevels
) -> void {
vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(imageFormat); vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(imageFormat);
if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear))
@ -1527,7 +1528,9 @@ class VulkanApp {
std::vector<tinyobj::material_t> materials; std::vector<tinyobj::material_t> materials;
std::string warn, err; 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); throw std::runtime_error(warn + err);
std::unordered_map<Vertex, u32> uniqueVertices {}; std::unordered_map<Vertex, u32> uniqueVertices {};
@ -2183,10 +2186,10 @@ class VulkanApp {
* *
* This function takes compiled shader code and creates a Vulkan shader module from it. * This function takes compiled shader code and creates a Vulkan shader module from it.
*/ */
fn createShaderModule(const std::vector<char>& code) -> vk::UniqueShaderModule { fn createShaderModule(const std::vector<u32>& code) -> vk::UniqueShaderModule {
vk::ShaderModuleCreateInfo createInfo { vk::ShaderModuleCreateInfo createInfo {
.codeSize = code.size(), .codeSize = code.size() * sizeof(u32),
.pCode = std::bit_cast<const u32*>(code.data()), .pCode = code.data(),
}; };
return mDevice->createShaderModuleUnique(createInfo); return mDevice->createShaderModuleUnique(createInfo);

196
src/util/shader.h Normal file
View file

@ -0,0 +1,196 @@
#include <glslang/Public/ShaderLang.h>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <iostream>
#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<uint32_t> {
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<uint32_t> spirv;
glslang::SpvOptions spvOptions;
glslang::GlslangToSpv(*program.getIntermediate(shaderType), spirv, &spvOptions);
return spirv;
}

View file

@ -1,3 +1,5 @@
#include <filesystem>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h> #include <stb_image.h>
@ -7,7 +9,7 @@ namespace stb {
class UniqueImage { class UniqueImage {
public: public:
// Constructor // Constructor
UniqueImage(const char* filename) { load(filename); } UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); }
// Deleted copy constructor and assignment operator // Deleted copy constructor and assignment operator
UniqueImage(const UniqueImage&) = delete; UniqueImage(const UniqueImage&) = delete;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB