From 6e45aba65ba382a82e61366cca846b4708c52aa5 Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Thu, 26 Jan 2023 21:17:07 +0000 Subject: [PATCH] ALlow texture filtering to be changed --- include/gfx.hpp | 6 +++ include/gfx_device.hpp | 9 +++- include/resources/texture.hpp | 9 +++- src/application.cpp | 21 +++++---- src/gfx_device_vulkan.cpp | 88 ++++++++++++++++++++--------------- src/resources/texture.cpp | 45 ++++++++++++++++-- src/util/model_loader.cpp | 8 ++-- test/src/game.cpp | 9 +++- 8 files changed, 136 insertions(+), 59 deletions(-) diff --git a/include/gfx.hpp b/include/gfx.hpp index b34cbbf..c5e6325 100644 --- a/include/gfx.hpp +++ b/include/gfx.hpp @@ -35,6 +35,12 @@ namespace engine::gfx { NEAREST, }; + enum class MipmapSetting { + OFF, + NEAREST, + LINEAR, + }; + struct VertexBufferDesc { uint64_t size; }; diff --git a/include/gfx_device.hpp b/include/gfx_device.hpp index 262ea7b..b0a2d15 100644 --- a/include/gfx_device.hpp +++ b/include/gfx_device.hpp @@ -35,7 +35,14 @@ namespace engine { gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); void destroyBuffer(const gfx::Buffer* buffer); - gfx::Texture* createTexture(const void* imageData, uint32_t w, uint32_t h, gfx::TextureFilter minFilter, gfx::TextureFilter magFilter); + gfx::Texture* createTexture( + const void* imageData, + uint32_t width, + uint32_t height, + gfx::TextureFilter minFilter, + gfx::TextureFilter magFilter, + gfx::MipmapSetting mipmapSetting, + bool useAnisotropy = false); void destroyTexture(const gfx::Texture* texture); // wait until all the active GPU queues have finished working diff --git a/include/resources/texture.hpp b/include/resources/texture.hpp index 249483b..1149264 100644 --- a/include/resources/texture.hpp +++ b/include/resources/texture.hpp @@ -9,7 +9,14 @@ namespace engine::resources { class Texture { public: - Texture(GFXDevice* gfxDevice, const std::string& path); + enum class Filtering { + OFF, + BILINEAR, + TRILINEAR, + ANISOTROPIC, + }; + + Texture(GFXDevice* gfxDevice, const std::string& path, Filtering filtering, bool useMipmaps, bool useLinearMagFilter); ~Texture(); Texture(const Texture&) = delete; Texture& operator=(const Texture&) = delete; diff --git a/src/application.cpp b/src/application.cpp index a37af1c..3004201 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -67,17 +67,17 @@ namespace engine { m_resourcesPath = getResourcesPath(); // register resource managers - registerResourceManager(); - registerResourceManager(); - registerResourceManager(); - registerResourceManager(); + registerResourceManager(); + registerResourceManager(); + registerResourceManager(); + registerResourceManager(); // default resources { resources::Shader::VertexParams vertParams{}; vertParams.hasNormal = true; vertParams.hasUV0 = true; - auto texturedShader = std::make_unique( + auto texturedShader = std::make_unique( gfx(), getResourcePath("engine/shaders/texture.vert").c_str(), getResourcePath("engine/shaders/texture.frag").c_str(), @@ -85,14 +85,17 @@ namespace engine { false, true ); - getResourceManager()->addPersistent("engine.textured", std::move(texturedShader)); + getResourceManager()->addPersistent("engine.textured", std::move(texturedShader)); } { - auto whiteTexture = std::make_unique( + auto whiteTexture = std::make_unique( gfx(), - getResourcePath("engine/textures/white.png") + getResourcePath("engine/textures/white.png"), + resources::Texture::Filtering::OFF, + false, + false ); - getResourceManager()->addPersistent("engine.white", std::move(whiteTexture)); + getResourceManager()->addPersistent("engine.white", std::move(whiteTexture)); } } diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index 9d73873..141e85d 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -488,7 +488,6 @@ namespace engine { static VkSampleCountFlagBits getMaxSampleCount(VkPhysicalDevice physicalDevice) { - VkPhysicalDeviceProperties physicalDeviceProperties; vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); @@ -500,7 +499,7 @@ namespace engine { if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } - return VK_SAMPLE_COUNT_1_BIT; + throw std::runtime_error("MSAA is not supported"); } // This is called not just on initialisation, but also when the window is resized. @@ -636,6 +635,7 @@ namespace engine { res = vkGetSwapchainImagesKHR(device, swapchain->swapchain, &swapchainImageCount, swapchain->images.data()); assert(res == VK_SUCCESS); + // Use multisample anti-aliasing swapchain->msaaSamples = getMaxSampleCount(physicalDevice); // create depth buffer if old depth buffer is wrong size. @@ -1004,6 +1004,8 @@ namespace engine { VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; + Swapchain swapchain{}; + std::vector queues{}; Queue gfxQueue{}; Queue presentQueue{}; @@ -1011,20 +1013,17 @@ namespace engine { VmaAllocator allocator = nullptr; + // device settings bool vsync = false; + float maxSamplerAnisotropy; - Swapchain swapchain{}; - + // render loop uint64_t FRAMECOUNT = 0; - std::array commandBuffers{}; std::array inFlightFences{}; - std::queue drawQueue{}; - VkDescriptorSetLayoutBinding uboLayoutBinding{}; VkDescriptorSetLayout descriptorSetLayout{}; - VkDescriptorSetLayoutBinding samplerLayoutBinding{}; VkDescriptorSetLayout samplerSetLayout{}; @@ -1043,14 +1042,13 @@ namespace engine { // initialise vulkan res = volkInitialize(); - if (res == VK_ERROR_INITIALIZATION_FAILED) { + if (res != VK_SUCCESS) { throw std::runtime_error("Unable to load vulkan, is it installed?"); } - assert(res == VK_SUCCESS); - uint32_t vulkanVersion = volkGetInstanceVersion(); - if (vulkanVersion < VK_MAKE_VERSION(1, 3, 0)) { + assert(vulkanVersion != 0); + if (vulkanVersion < VK_API_VERSION_1_3) { throw std::runtime_error("The loaded Vulkan version must be at least 1.3"); } @@ -1122,17 +1120,16 @@ namespace engine { -#ifndef NDEBUG for (const char* ext : extensions) { - TRACE("Using Vulkan instance extension: {}", ext); + DEBUG("Using Vulkan instance extension: {}", ext); } -#endif res = vkCreateInstance(&instanceInfo, nullptr, &pimpl->instance); if (res == VK_ERROR_INCOMPATIBLE_DRIVER) { throw std::runtime_error("The graphics driver is incompatible with vulkan"); + } else if (res != VK_SUCCESS) { + throw std::runtime_error("vkCreateInstance failed: " + std::to_string(res)); } - assert(res == VK_SUCCESS); @@ -1145,9 +1142,11 @@ namespace engine { { VkDebugUtilsMessengerCreateInfoEXT createInfo = getDebugMessengerCreateInfo(); - [[maybe_unused]] VkResult res; + VkResult res; res = vkCreateDebugUtilsMessengerEXT(pimpl->instance, &createInfo, nullptr, &pimpl->debugMessenger); - assert(res == VK_SUCCESS); + if (res != VK_SUCCESS) { + throw std::runtime_error("vkCreateDebugUtilsMessengerExt failed: " + std::to_string(res)); + } } @@ -1188,7 +1187,7 @@ namespace engine { res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, availableExtensions.data()); assert(res == VK_SUCCESS); - for (const auto& extToFind : requiredDeviceExtensions) { + for (const char* extToFind : requiredDeviceExtensions) { bool extFound = false; for (const auto& ext : availableExtensions) { if (strcmp(extToFind, ext.extensionName) == 0) { @@ -1216,6 +1215,7 @@ namespace engine { vkGetPhysicalDeviceFeatures(dev, &devFeatures); // anisotropic filtering is needed if (devFeatures.samplerAnisotropy == VK_FALSE) continue; + pimpl->maxSamplerAnisotropy = devProps.limits.maxSamplerAnisotropy; // check for linear filtering for mipmaps VkFormatProperties formatProperties{}; @@ -1342,7 +1342,6 @@ namespace engine { } pimpl->presentQueue = getQueueSupporting(pimpl->queues, QueueFlags::TRANSFER); - pimpl->gfxQueue = getQueueSupporting(pimpl->queues, QueueFlags::GRAPHICS); VkCommandPoolCreateInfo gfxCmdPoolInfo{ @@ -2064,15 +2063,26 @@ namespace engine { delete buffer; } - gfx::Texture* GFXDevice::createTexture(const void* imageData, uint32_t w, uint32_t h, gfx::TextureFilter minFilter, gfx::TextureFilter magFilter) + gfx::Texture* GFXDevice::createTexture( + const void* imageData, + uint32_t width, + uint32_t height, + gfx::TextureFilter minFilter, + gfx::TextureFilter magFilter, + gfx::MipmapSetting mipmapSetting, + bool useAnisotropy) { auto out = new gfx::Texture; [[maybe_unused]] VkResult res; - size_t imageSize = w * h * 4; + size_t imageSize = width * height * 4; - out->mipLevels = static_cast(std::floor(std::log2(std::max(w, h)))) + 1; + if (mipmapSetting == gfx::MipmapSetting::OFF) { + out->mipLevels = 1; + } else { + out->mipLevels = static_cast(std::floor(std::log2(std::max(width, height)))) + 1; + } // first load image into staging buffer VkBuffer stagingBuffer; @@ -2104,8 +2114,8 @@ namespace engine { VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = w; - imageInfo.extent.height = h; + imageInfo.extent.width = width; + imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = out->mipLevels; imageInfo.arrayLayers = 1; @@ -2140,14 +2150,14 @@ namespace engine { region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset = { 0, 0, 0 }; - region.imageExtent.width = w; - region.imageExtent.height = h; + region.imageExtent.width = width; + region.imageExtent.height = height; region.imageExtent.depth = 1; vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, out->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); // Mipmap generation handles the transition to SHADER_READ_ONLY_OPTIMAL - cmdGenerateMipmaps(commandBuffer, out->image, w, h, out->mipLevels); + cmdGenerateMipmaps(commandBuffer, out->image, width, height, out->mipLevels); // end cmd buffer endOneTimeCommands(pimpl->device, pimpl->commandPool, commandBuffer, pimpl->gfxQueue.handle); @@ -2174,12 +2184,12 @@ namespace engine { res = vkCreateImageView(pimpl->device, &imageViewInfo, nullptr, &out->imageView); assert(res == VK_SUCCESS); + VkFilter magFilterInternal = vkinternal::getTextureFilter(magFilter); + VkFilter minFilterInternal = vkinternal::getTextureFilter(minFilter); + // create texture sampler { - VkFilter magFilterInternal = vkinternal::getTextureFilter(magFilter); - VkFilter minFilterInternal = vkinternal::getTextureFilter(minFilter); - VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = magFilterInternal; @@ -2187,19 +2197,21 @@ namespace engine { samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.anisotropyEnable = VK_FALSE; - if (magFilterInternal == VK_FILTER_LINEAR && minFilterInternal == VK_FILTER_LINEAR) { - // Find max anisotropic filtering level - VkPhysicalDeviceProperties properties{}; - vkGetPhysicalDeviceProperties(pimpl->physicalDevice, &properties); + if (useAnisotropy) { samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + } else { + samplerInfo.anisotropyEnable = VK_FALSE; } + samplerInfo.maxAnisotropy = pimpl->maxSamplerAnisotropy; samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; samplerInfo.unnormalizedCoordinates = VK_FALSE; samplerInfo.compareEnable = VK_FALSE; samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + if (mipmapSetting == gfx::MipmapSetting::LINEAR) { + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + } else { + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + } samplerInfo.minLod = 0.0f; samplerInfo.maxLod = static_cast(out->mipLevels); samplerInfo.mipLodBias = 0.0f; diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp index 3f236a6..911634d 100644 --- a/src/resources/texture.cpp +++ b/src/resources/texture.cpp @@ -7,19 +7,54 @@ namespace engine::resources { -Texture::Texture(GFXDevice* gfxDevice, const std::string& path) +Texture::Texture(GFXDevice* gfxDevice, const std::string& path, Filtering filtering, bool useMipmaps, bool useLinearMagFilter) : m_gfxDevice(gfxDevice) { int width, height; auto texbuf = util::readImageFile(path, &width, &height); - gfx::TextureFilter filter = gfx::TextureFilter::LINEAR; - if (width <= 8 || height <= 8) { - filter = gfx::TextureFilter::NEAREST; + gfx::TextureFilter minFilter, magFilter; + gfx::MipmapSetting mipmapSetting; + bool anisotropyEnable; + + if (useLinearMagFilter) { + magFilter = gfx::TextureFilter::LINEAR; + } else { + magFilter = gfx::TextureFilter::NEAREST; } - m_gpuTexture = m_gfxDevice->createTexture(texbuf->data(), (uint32_t)width, (uint32_t)height, gfx::TextureFilter::LINEAR, filter); + switch (filtering) { + case Filtering::OFF: + minFilter = gfx::TextureFilter::NEAREST; + mipmapSetting = gfx::MipmapSetting::NEAREST; + anisotropyEnable = false; + break; + case Filtering::BILINEAR: + minFilter = gfx::TextureFilter::LINEAR; + mipmapSetting = gfx::MipmapSetting::NEAREST; + anisotropyEnable = false; + break; + case Filtering::TRILINEAR: + minFilter = gfx::TextureFilter::LINEAR; + mipmapSetting = gfx::MipmapSetting::LINEAR; + anisotropyEnable = false; + break; + case Filtering::ANISOTROPIC: + minFilter = gfx::TextureFilter::LINEAR; + mipmapSetting = gfx::MipmapSetting::LINEAR; + anisotropyEnable = true; + } + + if (useMipmaps == false) { + mipmapSetting = gfx::MipmapSetting::OFF; + } + + m_gpuTexture = m_gfxDevice->createTexture( + texbuf->data(), (uint32_t)width, (uint32_t)height, + minFilter, magFilter, + mipmapSetting, + anisotropyEnable); INFO("Loaded texture: {}, width: {} height: {}", path, width, height); diff --git a/src/util/model_loader.cpp b/src/util/model_loader.cpp index bdd4727..0a50bdb 100644 --- a/src/util/model_loader.cpp +++ b/src/util/model_loader.cpp @@ -148,11 +148,11 @@ namespace engine::util { const char* errString = importer.GetErrorString(); if (errString[0] != '\0' || scene == nullptr) { - throw std::runtime_error(errString); + throw std::runtime_error("assimp error: " + std::string(errString)); } if (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) { - throw std::runtime_error(errString); + throw std::runtime_error("assimp error (incomplete): " + std::string(errString)); } assert(scene->HasAnimations() == false); @@ -181,7 +181,9 @@ namespace engine::util { absPath = absPath.parent_path(); absPath /= texPath.C_Str(); try { - textures[i] = std::make_shared(parent->app()->gfx(), absPath.string()); + textures[i] = std::make_shared( + parent->app()->gfx(), absPath.string(), + resources::Texture::Filtering::TRILINEAR, true, true); } catch (const std::runtime_error&) { textures[i] = parent->app()->getResource("engine.white"); } diff --git a/test/src/game.cpp b/test/src/game.cpp index 8d362a9..ff0d084 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -42,6 +42,8 @@ void playGame() { engine::Application app(PROJECT_NAME, PROJECT_VERSION); + app.setFrameLimiter(true); + // configure window app.window()->setRelativeMouseMode(true); @@ -61,11 +63,14 @@ void playGame() myScene->getSystem()->setCameraEntity(camera); -// engine::util::loadMeshFromFile(myScene, app.getResourcePath("models/astronaut/astronaut.dae")); + engine::util::loadMeshFromFile(myScene, app.getResourcePath("models/van/van.dae")); auto grassTexture = std::make_shared( app.gfx(), - app.getResourcePath("textures/grass.jpg") + app.getResourcePath("textures/grass.jpg"), + engine::resources::Texture::Filtering::ANISOTROPIC, + true, + true ); uint32_t enemy = myScene->createEntity("enemy");