From e1cb168ccec67e06afc9ba3eac678e08383cb472 Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Wed, 15 Mar 2023 01:49:03 +0000 Subject: [PATCH] Fix subpass dependency and do other things --- include/gfx.hpp | 8 +- include/gfx_device.hpp | 2 + res/engine/shaders/skybox.frag | 2 +- src/gfx_device_vulkan.cpp | 139 +++++++++++++++++++------ src/systems/collisions.cpp | 12 +-- src/util/model_loader.cpp | 10 +- src/vulkan/device.cpp | 56 ++++------- src/vulkan/device.h | 13 +-- src/vulkan/swapchain.cpp | 178 ++++++++++++++++++--------------- src/vulkan/swapchain.h | 17 ++-- test/src/camera_controller.cpp | 4 + test/src/game.cpp | 17 +++- test/src/game.hpp | 7 +- test/src/main.cpp | 16 ++- 14 files changed, 295 insertions(+), 186 deletions(-) diff --git a/include/gfx.hpp b/include/gfx.hpp index 1136c0f..6fc5475 100644 --- a/include/gfx.hpp +++ b/include/gfx.hpp @@ -15,6 +15,7 @@ namespace engine::gfx { struct DrawBuffer; struct DescriptorSetLayout; struct DescriptorSet; + struct Image; enum class MSAALevel { MSAA_OFF, @@ -29,13 +30,16 @@ namespace engine::gfx { GraphicsSettings() { // sane defaults + enableValidation = true; vsync = true; waitForPresent = true; // not all GPUs/drivers support immediate present with V-sync enabled msaaLevel = MSAALevel::MSAA_OFF; } + bool enableValidation; bool vsync; - bool waitForPresent; // idle CPU after render until the frame has been presented (no affect with V-sync disabled) + // idle CPU after render until the frame has been presented (no affect with V-sync disabled) + bool waitForPresent; MSAALevel msaaLevel; }; @@ -98,7 +102,7 @@ namespace engine::gfx { struct PipelineInfo { const char* vertShaderPath; const char* fragShaderPath; - gfx::VertexFormat vertexFormat; + VertexFormat vertexFormat; bool alphaBlending; bool backfaceCulling; std::vector descriptorSetLayouts; diff --git a/include/gfx_device.hpp b/include/gfx_device.hpp index c368974..67e774d 100644 --- a/include/gfx_device.hpp +++ b/include/gfx_device.hpp @@ -48,6 +48,8 @@ namespace engine { gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); void destroyBuffer(const gfx::Buffer* buffer); + gfx::Image* createImage(uint32_t w, uint32_t h, const void* imageData); + gfx::Texture* createTexture( const void* imageData, uint32_t width, diff --git a/res/engine/shaders/skybox.frag b/res/engine/shaders/skybox.frag index 4661915..df34ad9 100644 --- a/res/engine/shaders/skybox.frag +++ b/res/engine/shaders/skybox.frag @@ -10,7 +10,7 @@ void main() { gl_FragDepth = 0.9999; //outColor = texture(texSampler, fragUV); - outColor = vec4(0.1, 0.1, 0.1, 1.0); + outColor = vec4(fragUV, 0.0, 1.0); } diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index 5002a14..5bd8513 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -51,6 +51,7 @@ namespace engine { VkFence renderFence = VK_NULL_HANDLE; VkSemaphore presentSemaphore = VK_NULL_HANDLE; VkSemaphore renderSemaphore = VK_NULL_HANDLE; + VkCommandPool commandPool = VK_NULL_HANDLE; VkCommandBuffer drawBuf = VK_NULL_HANDLE; }; @@ -80,9 +81,9 @@ namespace engine { }; struct gfx::DrawBuffer { - FrameData frameData; - uint32_t currentFrameIndex; // corresponds to the frameData - uint32_t imageIndex; // for swapchain present + FrameData frameData{}; + uint32_t currentFrameIndex = 0; // corresponds to the frameData + uint32_t imageIndex = 0; // for swapchain present }; struct gfx::DescriptorSetLayout { @@ -559,6 +560,7 @@ namespace engine { Swapchain swapchain{}; VkDescriptorPool descriptorPool; + VkCommandPool transferCommandPool = VK_NULL_HANDLE; std::array, FRAMES_IN_FLIGHT> descriptorBufferWriteQueues{}; uint64_t FRAMECOUNT = 0; @@ -592,13 +594,7 @@ namespace engine { throw std::runtime_error("The loaded Vulkan version must be at least 1.3"); } -#ifdef NDEBUG - bool useValidation = false; -#else - bool useValidation = true; -#endif - - pimpl->instance = createVulkanInstance(pimpl->window, appName, appVersion, useValidation, MessageSeverity::SEV_WARNING); + pimpl->instance = createVulkanInstance(pimpl->window, appName, appVersion, pimpl->graphicsSettings.enableValidation, MessageSeverity::SEV_WARNING); if (SDL_Vulkan_CreateSurface(pimpl->window, pimpl->instance.instance, &pimpl->surface) == false) { throw std::runtime_error("Unable to create window surface"); @@ -607,7 +603,58 @@ namespace engine { DeviceRequirements deviceRequirements{}; deviceRequirements.requiredExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; deviceRequirements.requiredFeatures.samplerAnisotropy = VK_TRUE; - deviceRequirements.sampledImageLinearFilter = true; + deviceRequirements.requiredFeatures.fillModeNonSolid = VK_TRUE; + deviceRequirements.formats.push_back( + FormatRequirements{ + .format = VK_FORMAT_R8G8B8A8_SRGB, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT, + .bufferFeatures = {}, + } + } + ); + deviceRequirements.formats.push_back( + FormatRequirements{ + .format = VK_FORMAT_R32G32_SFLOAT, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = {}, + .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, + } + } + ); + deviceRequirements.formats.push_back( + FormatRequirements{ + .format = VK_FORMAT_R32G32B32_SFLOAT, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = {}, + .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, + } + } + ); + deviceRequirements.formats.push_back( + FormatRequirements{ + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = {}, + .bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT, + } + } + ); + deviceRequirements.formats.push_back( // Depth buffer format + FormatRequirements{ + .format = VK_FORMAT_D16_UNORM, + .properties = VkFormatProperties{ + .linearTilingFeatures = {}, + .optimalTilingFeatures = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, + .bufferFeatures = {}, + } + } + ); + pimpl->device = createDevice(pimpl->instance.instance, deviceRequirements, pimpl->surface); pimpl->allocator = createAllocator(pimpl->instance.instance, pimpl->device.device, pimpl->device.physicalDevice); @@ -642,27 +689,44 @@ namespace engine { res = vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, &pimpl->frameData[i].renderSemaphore); if (res != VK_SUCCESS) throw std::runtime_error("Failed to create semaphore!"); + VkCommandPoolCreateInfo poolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = 0, // Command buffers cannot be individually reset (more performant this way) + .queueFamilyIndex = pimpl->device.queues.presentAndDrawQueueFamily + }; + VKCHECK(vkCreateCommandPool(pimpl->device.device, &poolInfo, nullptr, &pimpl->frameData[i].commandPool)); + VkCommandBufferAllocateInfo cmdAllocInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, - .commandPool = pimpl->device.commandPools.draw, + .commandPool = pimpl->frameData[i].commandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1 }; VKCHECK(vkAllocateCommandBuffers(pimpl->device.device, &cmdAllocInfo, &pimpl->frameData[i].drawBuf)); } + /* create command pool for transfer operations */ + VkCommandPoolCreateInfo transferPoolInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, // These command buffers don't last very long + .queueFamilyIndex = pimpl->device.queues.transferQueueFamily + }; + VKCHECK(vkCreateCommandPool(pimpl->device.device, &transferPoolInfo, nullptr, &pimpl->transferCommandPool)); + /* create a global descriptor pool */ std::vector poolSizes{}; - poolSizes.emplace_back(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 5); // purposely low limit + poolSizes.emplace_back(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 100); // purposely low limit VkDescriptorPoolCreateInfo descriptorPoolInfo{}; descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolInfo.pNext = nullptr; descriptorPoolInfo.flags = 0; - descriptorPoolInfo.maxSets = 5; // purposely low limit - descriptorPoolInfo.poolSizeCount = poolSizes.size(); + descriptorPoolInfo.maxSets = 100; // purposely low limit + descriptorPoolInfo.poolSizeCount = (uint32_t)poolSizes.size(); descriptorPoolInfo.pPoolSizes = poolSizes.data(); VKCHECK(vkCreateDescriptorPool(pimpl->device.device, &descriptorPoolInfo, nullptr, &pimpl->descriptorPool)); @@ -672,8 +736,10 @@ namespace engine { { vkDestroyDescriptorPool(pimpl->device.device, pimpl->descriptorPool, nullptr); + vkDestroyCommandPool(pimpl->device.device, pimpl->transferCommandPool, nullptr); + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - vkFreeCommandBuffers(pimpl->device.device, pimpl->device.commandPools.draw, 1, &pimpl->frameData[i].drawBuf); + vkDestroyCommandPool(pimpl->device.device, pimpl->frameData[i].commandPool, nullptr); vkDestroySemaphore(pimpl->device.device, pimpl->frameData[i].renderSemaphore, nullptr); vkDestroySemaphore(pimpl->device.device, pimpl->frameData[i].presentSemaphore, nullptr); vkDestroyFence(pimpl->device.device, pimpl->frameData[i].renderFence, nullptr); @@ -707,10 +773,17 @@ namespace engine { const uint32_t currentFrameIndex = pimpl->FRAMECOUNT % FRAMES_IN_FLIGHT; const FrameData frameData = pimpl->frameData[currentFrameIndex]; + /* wait until the previous frame RENDERING has finished */ + res = vkWaitForFences(pimpl->device.device, 1, &frameData.renderFence, VK_TRUE, 1000000000LL); + VKCHECK(res); + res = vkResetFences(pimpl->device.device, 1, &frameData.renderFence); + VKCHECK(res); + /* first empty the descriptor buffer write queue */ auto& writeQueue = pimpl->descriptorBufferWriteQueues[currentFrameIndex]; + //if (writeQueue.empty() == false) vkQueueWaitIdle(pimpl->device.queues.drawQueues[0]); for (gfx::DescriptorBuffer* buffer : writeQueue) { - copyBuffer(pimpl->device.device, pimpl->device.commandPools.transfer, pimpl->device.queues.transferQueues[0], buffer->stagingBuffer.buffer, buffer->gpuBuffers[currentFrameIndex].buffer, buffer->stagingBuffer.size); + copyBuffer(pimpl->device.device, pimpl->transferCommandPool, pimpl->device.queues.transferQueues[0], buffer->stagingBuffer.buffer, buffer->gpuBuffers[currentFrameIndex].buffer, buffer->stagingBuffer.size); } writeQueue.clear(); @@ -731,14 +804,8 @@ namespace engine { if (res == VK_SUCCESS) pimpl->swapchainIsOutOfDate = false; } while (pimpl->swapchainIsOutOfDate); - /* wait until the previous frame RENDERING has finished */ - res = vkWaitForFences(pimpl->device.device, 1, &frameData.renderFence, VK_TRUE, 1000000000LL); - VKCHECK(res); - res = vkResetFences(pimpl->device.device, 1, &frameData.renderFence); - VKCHECK(res); - /* record command buffer */ - res = vkResetCommandBuffer(frameData.drawBuf, 0); + res = vkResetCommandPool(pimpl->device.device, frameData.commandPool, 0); VKCHECK(res); VkCommandBufferBeginInfo beginInfo{ @@ -763,10 +830,10 @@ namespace engine { passBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; passBegin.pNext = nullptr; passBegin.renderPass = pimpl->swapchain.renderpass; - passBegin.framebuffer = std::get<2>(pimpl->swapchain.images[swapchainImageIndex]); + passBegin.framebuffer = pimpl->swapchain.framebuffers[swapchainImageIndex]; passBegin.renderArea.extent = pimpl->swapchain.extent; passBegin.renderArea.offset = { 0, 0 }; - passBegin.clearValueCount = clearValues.size(); + passBegin.clearValueCount = (uint32_t)clearValues.size(); passBegin.pClearValues = clearValues.data(); vkCmdBeginRenderPass(frameData.drawBuf, &passBegin, VK_SUBPASS_CONTENTS_INLINE); @@ -824,6 +891,7 @@ namespace engine { .pSignalSemaphores = &drawBuffer->frameData.renderSemaphore, }; res = vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, drawBuffer->frameData.renderFence); + assert(res == VK_SUCCESS); // VKCHECK(res); // expensive operation for some reason // PRESENT @@ -913,10 +981,10 @@ namespace engine { attribDescs.reserve(info.vertexFormat.attributeDescriptions.size()); for (const auto& desc : info.vertexFormat.attributeDescriptions) { VkVertexInputAttributeDescription vulkanAttribDesc{}; - vulkanAttribDesc.binding = 0; vulkanAttribDesc.location = desc.location; - vulkanAttribDesc.offset = desc.offset; + vulkanAttribDesc.binding = 0; vulkanAttribDesc.format = vkinternal::getVertexAttribFormat(desc.format); + vulkanAttribDesc.offset = desc.offset; attribDescs.push_back(vulkanAttribDesc); } @@ -982,7 +1050,7 @@ namespace engine { VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; // enabling this will not run the fragment shaders at all rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; if (info.backfaceCulling == true) { @@ -1060,7 +1128,7 @@ namespace engine { VkPipelineLayoutCreateInfo layoutInfo{}; layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - layoutInfo.setLayoutCount = descriptorSetLayouts.size(); + layoutInfo.setLayoutCount = (uint32_t)descriptorSetLayouts.size(); layoutInfo.pSetLayouts = descriptorSetLayouts.data(); layoutInfo.pushConstantRangeCount = 1; layoutInfo.pPushConstantRanges = &pushConstantRange; @@ -1119,7 +1187,7 @@ namespace engine { info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.pNext = nullptr; info.flags = 0; - info.bindingCount = bindings.size(); + info.bindingCount = (uint32_t)bindings.size(); info.pBindings = bindings.data(); VKCHECK(vkCreateDescriptorSetLayout(pimpl->device.device, &info, nullptr, &out->layout)); @@ -1230,7 +1298,7 @@ namespace engine { VKCHECK(vmaCreateBuffer(pimpl->allocator, &gpuBufferInfo, &gpuAllocationInfo, &out->gpuBuffers[i].buffer, &out->gpuBuffers[i].allocation, nullptr)); /* copy staging buffer into both */ - copyBuffer(pimpl->device.device, pimpl->device.commandPools.transfer, pimpl->device.queues.transferQueues[0], out->stagingBuffer.buffer, out->gpuBuffers[i].buffer, out->stagingBuffer.size); + copyBuffer(pimpl->device.device, pimpl->transferCommandPool, pimpl->device.queues.transferQueues[0], out->stagingBuffer.buffer, out->gpuBuffers[i].buffer, out->stagingBuffer.size); } return out; @@ -1320,7 +1388,7 @@ namespace engine { } // copy the data from the staging buffer to the gpu buffer - copyBuffer(pimpl->device.device, pimpl->device.commandPools.transfer, pimpl->device.queues.transferQueues[0], stagingBuffer, out->buffer, out->size); + copyBuffer(pimpl->device.device, pimpl->transferCommandPool, pimpl->device.queues.transferQueues[0], stagingBuffer, out->buffer, out->size); // destroy staging buffer vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); @@ -1334,6 +1402,11 @@ namespace engine { delete buffer; } + gfx::Image* GFXDevice::createImage(uint32_t w, uint32_t h, const void* imageData) + { + return nullptr; + } + gfx::Texture* GFXDevice::createTexture( const void* imageData, uint32_t width, diff --git a/src/systems/collisions.cpp b/src/systems/collisions.cpp index 8c1b887..db36afb 100644 --- a/src/systems/collisions.cpp +++ b/src/systems/collisions.cpp @@ -38,7 +38,7 @@ namespace engine { const float NZdistance = glm::abs(subjectCentre.z - object.pos1.z); const std::array distances { PXdistance, NXdistance, PYdistance, NYdistance, PZdistance, NZdistance }; const auto minDistance = std::min_element(distances.begin(), distances.end()); - const int index = minDistance - distances.begin(); + const int index = static_cast(minDistance - distances.begin()); switch (index) { case 0: // P_X @@ -97,7 +97,7 @@ namespace engine { const glm::vec3 globalPosition = t->worldMatrix[3]; const AABB localBoundingBox = c->aabb; - AABB globalBoundingBox; + AABB globalBoundingBox{}; globalBoundingBox.pos1 = globalPosition + localBoundingBox.pos1; globalBoundingBox.pos2 = globalPosition + localBoundingBox.pos2; @@ -117,8 +117,8 @@ namespace engine { // Check every static collider against every dynamic collider, and every dynamic collider against every other one // This technique is inefficient for many entities. - for (auto [staticEntity, staticAABB, staticTrigger] : m_staticAABBs) { - for (auto [dynamicEntity, dynamicAABB, dynamicTrigger] : m_dynamicAABBs) { + for (const auto& [staticEntity, staticAABB, staticTrigger] : m_staticAABBs) { + for (const auto& [dynamicEntity, dynamicAABB, dynamicTrigger] : m_dynamicAABBs) { if (checkCollisionFast(staticAABB, dynamicAABB)) { if (staticTrigger || dynamicTrigger) { // only check collisions involved with triggers m_possibleCollisions.emplace_back( @@ -131,7 +131,7 @@ namespace engine { } // get collision details and submit events - for (auto possibleCollision : m_possibleCollisions) { + for (const auto& possibleCollision : m_possibleCollisions) { if (possibleCollision.staticTrigger) { CollisionEvent info{}; info.isCollisionEnter = true; @@ -156,7 +156,7 @@ namespace engine { } } - for (auto [entity, info] : m_collisionInfos) { + for (const auto& [entity, info] : m_collisionInfos) { m_scene->events()->queueEvent(EventSubscriberKind::ENTITY, entity, info); } } diff --git a/src/util/model_loader.cpp b/src/util/model_loader.cpp index 1177ef2..3cde87a 100644 --- a/src/util/model_loader.cpp +++ b/src/util/model_loader.cpp @@ -35,7 +35,7 @@ namespace engine::util { { // convert to glm column major - glm::mat4 transform; + glm::mat4 transform{}; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { transform[i][j] = parentNode->mTransformation[j][i]; @@ -196,7 +196,7 @@ namespace engine::util { const aiMesh* m = scene->mMeshes[i]; meshMaterialIndices.push_back(m->mMaterialIndex); std::vector vertices(m->mNumVertices); - std::vector indices(m->mNumFaces * 3); + std::vector indices((size_t)m->mNumFaces * 3); LOG_TRACE("Mesh {}: vertex count {}", i, vertices.size()); LOG_TRACE("Mesh {}: index count {}", i, indices.size()); @@ -219,9 +219,9 @@ namespace engine::util { } for (uint32_t j = 0; j < indices.size() / 3; j++) { - indices[j * 3 + 0] = m->mFaces[j].mIndices[0]; - indices[j * 3 + 1] = m->mFaces[j].mIndices[1]; - indices[j * 3 + 2] = m->mFaces[j].mIndices[2]; + indices[(size_t)j * 3 + 0] = m->mFaces[j].mIndices[0]; + indices[(size_t)j * 3 + 1] = m->mFaces[j].mIndices[1]; + indices[(size_t)j * 3 + 2] = m->mFaces[j].mIndices[2]; } meshes.push_back(std::make_shared(parent->app()->gfx(), vertices, indices)); } diff --git a/src/vulkan/device.cpp b/src/vulkan/device.cpp index c6529b8..8758943 100644 --- a/src/vulkan/device.cpp +++ b/src/vulkan/device.cpp @@ -20,8 +20,8 @@ namespace engine { return supportsPresent; } - /* chooses a device, creates it, gets its function pointers, and creates command pools */ - Device createDevice(VkInstance instance, DeviceRequirements requirements, VkSurfaceKHR surface) + /* chooses a device, creates it, gets its function pointers, and retrieves queues */ + Device createDevice(VkInstance instance, const DeviceRequirements& requirements, VkSurfaceKHR surface) { Device d{}; @@ -187,14 +187,21 @@ namespace engine { if (devFeatures.inheritedQueries == VK_FALSE) continue; } - if (requirements.sampledImageLinearFilter == true) { - // check for linear filtering for mipmaps - VkFormatProperties formatProperties{}; - vkGetPhysicalDeviceFormatProperties(physDev, VK_FORMAT_R8G8B8A8_SRGB, &formatProperties); - if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { - continue; + bool formatsSupported = true; + for (const FormatRequirements& formatRequirements : requirements.formats) { + VkFormatFeatureFlags requiredLinearFlags = formatRequirements.properties.linearTilingFeatures; + VkFormatFeatureFlags requiredOptimalFlags = formatRequirements.properties.optimalTilingFeatures; + VkFormatFeatureFlags requiredBufferFlags = formatRequirements.properties.bufferFeatures; + VkFormatProperties deviceFormatProperties{}; + vkGetPhysicalDeviceFormatProperties(physDev, formatRequirements.format, &deviceFormatProperties); + if ((deviceFormatProperties.linearTilingFeatures & requiredLinearFlags) != requiredLinearFlags || + (deviceFormatProperties.optimalTilingFeatures & requiredOptimalFlags) != requiredOptimalFlags || + (deviceFormatProperties.bufferFeatures & requiredBufferFlags) != requiredBufferFlags) { + formatsSupported = false; + break; } } + if (formatsSupported == false) continue; /* USE THIS PHYSICAL DEVICE */ d.physicalDevice = physDev; @@ -223,7 +230,7 @@ namespace engine { if (p.queueCount < 2) continue; // ideally have one queue for presenting and at least one other for rendering if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, i)) { + if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, static_cast(i))) { graphicsFamily = static_cast(i); break; } @@ -233,7 +240,7 @@ namespace engine { for (size_t i = 0; i < queueFamilies.size(); i++) { VkQueueFamilyProperties p = queueFamilies[i]; if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, i)) { + if (checkQueueFamilySupportsPresent(d.physicalDevice, surface, static_cast(i))) { graphicsFamily = static_cast(i); } } @@ -313,7 +320,7 @@ namespace engine { if (transferFamily != graphicsFamily) { vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue); if (queueFamilies[graphicsFamily].queueCount >= 2) { - d.queues.drawQueues.resize(queueFamilies[graphicsFamily].queueCount - 1); + d.queues.drawQueues.resize((size_t)queueFamilies[graphicsFamily].queueCount - 1); for (uint32_t i = 0; i < d.queues.drawQueues.size(); i++) { vkGetDeviceQueue(d.device, graphicsFamily, i + 1, &d.queues.drawQueues[i]); } @@ -334,7 +341,7 @@ namespace engine { vkGetDeviceQueue(d.device, graphicsFamily, 1, &d.queues.transferQueues[0]); // use the remaining queues for drawing if (queueCount >= 3) { - d.queues.drawQueues.resize(queueCount - 2); + d.queues.drawQueues.resize((size_t)queueCount - 2); for (uint32_t i = 0; i < queueCount - 2; i++) { vkGetDeviceQueue(d.device, graphicsFamily, i + 2, &d.queues.drawQueues[i]); } @@ -357,37 +364,12 @@ namespace engine { d.queues.presentAndDrawQueueFamily = graphicsFamily; d.queues.transferQueueFamily = transferFamily; - /* generate command pools */ - VkCommandPoolCreateInfo poolCreateInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = 0, // set individually after - .queueFamilyIndex = 0, // set individually after - }; - - // present queue does not need a command pool as it does not use command buffers - - // draw command pools: - poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - poolCreateInfo.queueFamilyIndex = graphicsFamily; - res = vkCreateCommandPool(d.device, &poolCreateInfo, nullptr, &d.commandPools.draw); - if (res != VK_SUCCESS) throw std::runtime_error("Failed to create command pool"); - - // transfer command pools: - poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; // buffers from this pool are often short-lived, - // as is usually the case for transfer operations - poolCreateInfo.queueFamilyIndex = transferFamily; - res = vkCreateCommandPool(d.device, &poolCreateInfo, nullptr, &d.commandPools.transfer); - if (res != VK_SUCCESS) throw std::runtime_error("Failed to create command pool"); - return d; } void destroyDevice(Device device) { - vkDestroyCommandPool(device.device, device.commandPools.transfer, nullptr); - vkDestroyCommandPool(device.device, device.commandPools.draw, nullptr); vkDestroyDevice(device.device, nullptr); } diff --git a/src/vulkan/device.h b/src/vulkan/device.h index 9e3e025..6686614 100644 --- a/src/vulkan/device.h +++ b/src/vulkan/device.h @@ -6,10 +6,15 @@ namespace engine { + struct FormatRequirements { + VkFormat format{}; + VkFormatProperties properties{}; + }; + struct DeviceRequirements { std::vector requiredExtensions; VkPhysicalDeviceFeatures requiredFeatures; - bool sampledImageLinearFilter; + std::vector formats{}; }; struct Device { @@ -27,13 +32,9 @@ namespace engine { uint32_t transferQueueFamily = UINT32_MAX; } queues{}; - struct CommandPools { - VkCommandPool draw = VK_NULL_HANDLE; // use with the drawQueues - VkCommandPool transfer = VK_NULL_HANDLE; // use with the transferQueues - } commandPools{}; }; - Device createDevice(VkInstance instance, DeviceRequirements requirements, VkSurfaceKHR surface); + Device createDevice(VkInstance instance, const DeviceRequirements& requirements, VkSurfaceKHR surface); void destroyDevice(Device device); } diff --git a/src/vulkan/swapchain.cpp b/src/vulkan/swapchain.cpp index 43ccfcf..b6ab455 100644 --- a/src/vulkan/swapchain.cpp +++ b/src/vulkan/swapchain.cpp @@ -131,62 +131,6 @@ namespace engine { vkDestroySwapchainKHR(info.device, scInfo.oldSwapchain, nullptr); } - { /* create the depth buffer */ - - sc->depthStencil.format = VK_FORMAT_D32_SFLOAT; - - if (sc->depthStencil.image != VK_NULL_HANDLE) { - vkDestroyImageView(info.device, sc->depthStencil.view, nullptr); - vmaDestroyImage(sc->allocator, sc->depthStencil.image, sc->depthStencil.allocation); - } - VkImageCreateInfo imageInfo{}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.pNext = nullptr; - imageInfo.flags = 0; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = sc->depthStencil.format; - imageInfo.extent.width = sc->extent.width; - imageInfo.extent.height = sc->extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.queueFamilyIndexCount = 0; // ignored - imageInfo.pQueueFamilyIndices = nullptr; // ignored - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VmaAllocationCreateInfo allocInfo{}; - allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - allocInfo.priority = 1.0f; - - res = vmaCreateImage(sc->allocator, &imageInfo, &allocInfo, &sc->depthStencil.image, &sc->depthStencil.allocation, nullptr); - if (res != VK_SUCCESS) throw std::runtime_error("Failed to create depth buffer image! Code: " + std::to_string(res)); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.pNext = nullptr; - viewInfo.flags = 0; - viewInfo.image = sc->depthStencil.image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = sc->depthStencil.format; - viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - res = vkCreateImageView(info.device, &viewInfo, nullptr, &sc->depthStencil.view); - assert(res == VK_SUCCESS); - - } - /* create the render pass */ if (sc->renderpass != VK_NULL_HANDLE) { vkDestroyRenderPass(sc->device, sc->renderpass, nullptr); @@ -204,7 +148,7 @@ namespace engine { }; VkAttachmentDescription depthStencilAttachment{ .flags = 0, - .format = sc->depthStencil.format, + .format = sc->depthStencilFormat, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, // the depth buffer is not used after the fragment shader @@ -234,25 +178,38 @@ namespace engine { .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, }; - VkSubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - .dependencyFlags = 0 + VkSubpassDependency attachmentDependencies[2] = { + { + // Depth buffer + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0, + }, + { + // Image Layout Transition + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .dependencyFlags = 0, + }, }; VkRenderPassCreateInfo renderPassInfo{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = nullptr, .flags = 0, - .attachmentCount = attachments.size(), + .attachmentCount = (uint32_t)attachments.size(), .pAttachments = attachments.data(), .subpassCount = 1, .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency, + .dependencyCount = 2, + .pDependencies = attachmentDependencies, }; res = vkCreateRenderPass(sc->device, &renderPassInfo, nullptr, &sc->renderpass); if (res != EXIT_SUCCESS) throw std::runtime_error("Failed to create renderpass!"); @@ -266,21 +223,31 @@ namespace engine { assert(res == VK_SUCCESS); /* create image view and framebuffer for each image */ - sc->images.resize(swapchainImageCount); + if (sc->swapchainImages.size() == 0) { + sc->swapchainImages.resize(swapchainImageCount); + sc->depthImages.resize(swapchainImageCount); + sc->framebuffers.resize(swapchainImageCount); + } for (uint32_t i = 0; i < swapchainImageCount; i++) { - auto& [image, imageView, framebuffer] = sc->images.at(i); + auto& [swapchainImage, swapchainImageView] = sc->swapchainImages.at(i); + auto& [depthImage, depthAllocation, depthImageView] = sc->depthImages.at(i); + auto& framebuffer = sc->framebuffers.at(i); - if (imageView != VK_NULL_HANDLE) vkDestroyImageView(sc->device, imageView, nullptr); + if (swapchainImageView != VK_NULL_HANDLE) vkDestroyImageView(sc->device, swapchainImageView, nullptr); + if (depthImageView != VK_NULL_HANDLE) { + vkDestroyImageView(sc->device, depthImageView, nullptr); + vmaDestroyImage(sc->allocator, depthImage, depthAllocation); + } if (framebuffer != VK_NULL_HANDLE) vkDestroyFramebuffer(sc->device, framebuffer, nullptr); - image = swapchainImages[i]; + swapchainImage = swapchainImages[i]; /* make the image view */ VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.pNext = nullptr; viewInfo.flags = 0; - viewInfo.image = image; + viewInfo.image = swapchainImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = sc->surfaceFormat.format; viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; @@ -292,11 +259,59 @@ namespace engine { viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - VkResult res = vkCreateImageView(sc->device, &viewInfo, nullptr, &imageView); + VkResult res = vkCreateImageView(sc->device, &viewInfo, nullptr, &swapchainImageView); if (res != VK_SUCCESS) throw std::runtime_error("Failed to create image view from swapchain image!"); + /* create the depth buffer */ + + VkImageCreateInfo depthImageInfo{}; + depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + depthImageInfo.pNext = nullptr; + depthImageInfo.flags = 0; + depthImageInfo.imageType = VK_IMAGE_TYPE_2D; + depthImageInfo.format = sc->depthStencilFormat; + depthImageInfo.extent.width = sc->extent.width; + depthImageInfo.extent.height = sc->extent.height; + depthImageInfo.extent.depth = 1; + depthImageInfo.mipLevels = 1; + depthImageInfo.arrayLayers = 1; + depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + depthImageInfo.queueFamilyIndexCount = 0; // ignored + depthImageInfo.pQueueFamilyIndices = nullptr; // ignored + depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo depthAllocInfo{}; + depthAllocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + depthAllocInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + depthAllocInfo.priority = 1.0f; + + res = vmaCreateImage(sc->allocator, &depthImageInfo, &depthAllocInfo, &depthImage, &depthAllocation, nullptr); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create depth buffer image! Code: " + std::to_string(res)); + + VkImageViewCreateInfo depthViewInfo{}; + depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depthViewInfo.pNext = nullptr; + depthViewInfo.flags = 0; + depthViewInfo.image = depthImage; + depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthViewInfo.format = sc->depthStencilFormat; + depthViewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + depthViewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + depthViewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + depthViewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + depthViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depthViewInfo.subresourceRange.baseMipLevel = 0; + depthViewInfo.subresourceRange.levelCount = 1; + depthViewInfo.subresourceRange.baseArrayLayer = 0; + depthViewInfo.subresourceRange.layerCount = 1; + res = vkCreateImageView(info.device, &depthViewInfo, nullptr, &depthImageView); + assert(res == VK_SUCCESS); + std::array attachments { - imageView, sc->depthStencil.view + swapchainImageView, depthImageView }; VkFramebufferCreateInfo fbInfo{}; @@ -304,7 +319,7 @@ namespace engine { fbInfo.pNext = nullptr; fbInfo.flags = 0; fbInfo.renderPass = sc->renderpass; - fbInfo.attachmentCount = attachments.size(); + fbInfo.attachmentCount = (uint32_t)attachments.size(); fbInfo.pAttachments = attachments.data(); fbInfo.width = sc->extent.width; fbInfo.height = sc->extent.height; @@ -319,13 +334,18 @@ namespace engine { void destroySwapchain(const Swapchain& sc) { - for (const auto& [image, view, framebuffer] : sc.images) { + for (VkFramebuffer framebuffer : sc.framebuffers) { vkDestroyFramebuffer(sc.device, framebuffer, nullptr); + } + for (const auto& [image, view] : sc.swapchainImages) { vkDestroyImageView(sc.device, view, nullptr); } + for (const auto& [image, allocation, view] : sc.depthImages) { + vkDestroyImageView(sc.device, view, nullptr); + vmaDestroyImage(sc.allocator, image, allocation); + } + vkDestroyRenderPass(sc.device, sc.renderpass, nullptr); - vkDestroyImageView(sc.device, sc.depthStencil.view, nullptr); - vmaDestroyImage(sc.allocator, sc.depthStencil.image, sc.depthStencil.allocation); vkDestroySwapchainKHR(sc.device, sc.swapchain, nullptr); } diff --git a/src/vulkan/swapchain.h b/src/vulkan/swapchain.h index a784868..5835ba7 100644 --- a/src/vulkan/swapchain.h +++ b/src/vulkan/swapchain.h @@ -11,6 +11,12 @@ namespace engine { + struct DepthStencil { + VkImage image = VK_NULL_HANDLE; + VmaAllocation allocation = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + }; + struct Swapchain { VkSwapchainKHR swapchain = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; // the associated device @@ -20,13 +26,10 @@ namespace engine { VkPresentModeKHR presentMode{}; VkExtent2D extent{}; VkRenderPass renderpass = VK_NULL_HANDLE; - std::vector> images{}; - struct DepthStencil { - VkImage image = VK_NULL_HANDLE; - VmaAllocation allocation = VK_NULL_HANDLE; - VkImageView view = VK_NULL_HANDLE; - VkFormat format{}; - } depthStencil{}; + const VkFormat depthStencilFormat = VK_FORMAT_D16_UNORM; + std::vector> swapchainImages{}; + std::vector depthImages{}; + std::vector framebuffers{}; }; struct SwapchainInfo { diff --git a/test/src/camera_controller.cpp b/test/src/camera_controller.cpp index b46d4f4..6348fe9 100644 --- a/test/src/camera_controller.cpp +++ b/test/src/camera_controller.cpp @@ -161,6 +161,10 @@ void CameraControllerSystem::onUpdate(float ts) m_scene->app()->window()->toggleFullscreen(); } + if (m_scene->app()->inputManager()->getButtonPress("exit")) { + m_scene->app()->window()->setCloseFlag(); + } + c->justCollided = false; } diff --git a/test/src/game.cpp b/test/src/game.cpp index a8415a3..cbae24b 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -21,10 +21,13 @@ #include "util/model_loader.hpp" +#include "game.hpp" + static void configureInputs(engine::InputManager* inputManager) { // user interface mappings inputManager->addInputButton("fullscreen", engine::inputs::Key::K_F11); + inputManager->addInputButton("exit", engine::inputs::Key::K_ESCAPE); // game buttons inputManager->addInputButton("fire", engine::inputs::MouseButton::M_LEFT); inputManager->addInputButton("aim", engine::inputs::MouseButton::M_RIGHT); @@ -38,17 +41,19 @@ static void configureInputs(engine::InputManager* inputManager) inputManager->addInputAxis("looky", engine::inputs::MouseAxis::Y); } -void playGame(bool enableFrameLimiter) +void playGame(GameSettings settings) { - LOG_INFO("FPS limiter: {}", enableFrameLimiter ? "ON" : "OFF"); + LOG_INFO("FPS limiter: {}", settings.enableFrameLimiter ? "ON" : "OFF"); + LOG_INFO("Graphics Validation: {}", settings.enableValidation ? "ON" : "OFF"); engine::gfx::GraphicsSettings graphicsSettings{}; + graphicsSettings.enableValidation = settings.enableValidation; graphicsSettings.vsync = true; graphicsSettings.waitForPresent = false; graphicsSettings.msaaLevel = engine::gfx::MSAALevel::MSAA_OFF; engine::Application app(PROJECT_NAME, PROJECT_VERSION, graphicsSettings); - app.setFrameLimiter(enableFrameLimiter); + app.setFrameLimiter(settings.enableFrameLimiter); // configure window app.window()->setRelativeMouseMode(true); @@ -100,8 +105,8 @@ void playGame(bool enableFrameLimiter) skyboxRenderable->material = std::make_unique(app.getResource("builtin.skybox")); skyboxRenderable->material->m_texture = spaceTexture; // skyboxRenderable->mesh = genSphereMesh(app.gfx(), 1.0f, 50, true); - skyboxRenderable->mesh = genCuboidMesh(app.gfx(), 2.0f, 2.0f, 2.0f, 1.0f, true); - myScene->getComponent(skybox)->position = { -1.0f, -1.0f, -1.0f }; + skyboxRenderable->mesh = genCuboidMesh(app.gfx(), 10.0f, 10.0f, 10.0f, 1.0f, true); + myScene->getComponent(skybox)->position = { -5.0f, -5.0f, -5.0f }; } /* cube */ @@ -127,6 +132,8 @@ void playGame(bool enableFrameLimiter) floorCollider->aabb = { { 0.0f, 0.0f, 0.0f }, { 10000.0f, 1.0f, 10000.0f } }; } + //engine::util::loadMeshFromFile(myScene, app.getResourcePath("models/astronaut/astronaut.dae")); + app.gameLoop(); } diff --git a/test/src/game.hpp b/test/src/game.hpp index f3d8e59..b6a47d5 100644 --- a/test/src/game.hpp +++ b/test/src/game.hpp @@ -1,3 +1,8 @@ #pragma once -void playGame(bool enableFrameLimiter); +struct GameSettings { + bool enableFrameLimiter; + bool enableValidation; +}; + +void playGame(GameSettings settings); diff --git a/test/src/main.cpp b/test/src/main.cpp index 6f9472b..a2a3d44 100644 --- a/test/src/main.cpp +++ b/test/src/main.cpp @@ -7,14 +7,22 @@ // standard library #include +#include #include int main(int argc, char* argv[]) { - bool enableFrameLimiter = true; - if (argc == 2) { - if (strcmp(argv[1], "nofpslimit") == 0) enableFrameLimiter = false; + GameSettings settings{}; + settings.enableFrameLimiter = true; + settings.enableValidation = false; + if (argc >= 2) { + std::unordered_set args{}; + for (size_t i = 1; i < argc; i++) { + args.insert(std::string(argv[i])); + } + if (args.contains("nofpslimit")) settings.enableFrameLimiter = false; + if (args.contains("gpuvalidation")) settings.enableValidation = true; } engine::setupLog(PROJECT_NAME); @@ -22,7 +30,7 @@ int main(int argc, char* argv[]) LOG_INFO("{} v{}", PROJECT_NAME, PROJECT_VERSION); try { - playGame(enableFrameLimiter); + playGame(settings); } catch (const std::exception& e) { LOG_CRITICAL("{}", e.what());