diff --git a/README.md b/README.md index e7d8720..e12db38 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ Work in progress galaxy down to atom (mostly centimeter) online voxel game - [About The Project](#about-the-project) - [Built With](#built-with) -- [Getting Started](#getting-started) +- [Run it](#run-it) - [Prerequisites](#prerequisites) - - [Build](#build) + - [Usage](#usage) +- [Build](#build) + - [Prerequisites](#prerequisites-1) - [Optionally](#optionally) - [Installation](#installation) -- [Usage](#usage) + - [Additionally](#additionally) - [RoadMap](#roadmap) - [License](#license) - [Contact](#contact) @@ -35,16 +37,30 @@ Experimental project using OpenGL and Vulkan. -## Getting Started - -To get a local copy up and running, follow these simple steps. +## Run it +Get a release compatible with your system window library and processor capabilities, or [build](#build) it ### Prerequisites * OpenGL or Vulkan driver -#### Build +### Usage + +```sh +./univerxel +``` + +Edit `config.toml` + + +Profit ! + +## Build + +To get a local copy up and running, follow these simple steps. + +### Prerequisites * C++17 * CMake 3.11 @@ -53,8 +69,9 @@ To get a local copy up and running, follow these simple steps. #### Optionally * Python: utility scripts -* Tracy v0.7: profiling -* glslc: compile vk shaders +* [Tracy](https://github.com/wolfpld/tracy) v0.7: profiling +* [glslc](https://github.com/KhronosGroup/glslang): build vk shaders +* [Compressonator](https://github.com/GPUOpen-Tools/compressonator): build textures ### Installation @@ -68,23 +85,47 @@ mkdir build && cd build ``` 3. Build CMake ```sh -cmake .. +cmake .. ``` -4. Build Make + +CMake options: `-DKEY=VAL` +Key | Usage | Default +--- | --- | --- +SIMD_LEVEL | SIMD processor acceleration (sse2, sse4.1, avx2, avx512f) | `avx2` +USE_FMA | Fast math | `1` +CMAKE_BUILD_TYPE | Level of optimization | `Release` +PROFILING | Tracy profiling | `0` +LOG_DEBUG | Debug logs | `0` +LOG_TRACE | Trace logs | `0` + +1. Build Make ```sh -make +make -j ``` +Target | Description +--- | --- +univerxel | All in one +univerxel-server | Standalone server +univerxel-client | Light client +docs | Doxygen documentation - -## Usage +#### Additionally + +5. Recompile Vulkan shaders ```sh -./univerxel +cd resource/shaders-src +./compile.sh # Use glslc +GLSL="glslValidator -V" ./compile.sh # Other glslang compiler ``` -Profit ! - +6. Rebuild textures +```sh +cd resource/textures-src +./merge # combine grey images to RGBA +Compressonator -fd BC3 -mipsize +``` diff --git a/resource/content/textures/1024-realistic/terrain/Mapl.dds b/resource/content/textures/1024-realistic/terrain/Mapl.dds index 68afc67..b623ff1 100644 --- a/resource/content/textures/1024-realistic/terrain/Mapl.dds +++ b/resource/content/textures/1024-realistic/terrain/Mapl.dds @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b81bfafe4f0ba05dee539c6e2b3eb26b0bb1c2940be96dba9b567b003a2ee73 -size 1048704 +oid sha256:8fce11b776238550452ea5a11786474ae0e6d5cac7e5875c65664981aff53bd7 +size 1398256 diff --git a/src/client/Client.cpp b/src/client/Client.cpp index 5a3b6d4..bf41153 100644 --- a/src/client/Client.cpp +++ b/src/client/Client.cpp @@ -13,7 +13,7 @@ Client::Client(config::client::options& options): options(options) { } Client::~Client() { } void Client::run(server_handle* const localHandle) { - if (!render::Load(window, options.preferVulkan, options.renderer)) + if (!render::Load(window, options.preferVulkan, options.renderer, options.window.samples)) return; window.setTargetFPS(options.window.targetFPS); diff --git a/src/client/render/api/Images.hpp b/src/client/render/api/Images.hpp index d615570..0de2166 100644 --- a/src/client/render/api/Images.hpp +++ b/src/client/render/api/Images.hpp @@ -72,11 +72,16 @@ public: }; struct requirement: properties { requirement(const properties& props, Layout layout, Usage usage, Aspect aspect, - bool optimal = true): properties(props), layout(layout), usage(usage), - aspect(aspect), optimal(optimal) { } + int samples = 1, bool optimal = true): properties(props), layout(layout), + usage(usage), aspect(aspect), samples(samples), optimal(optimal) + { + assert(samples > 0 && (ceil(log2(n)) == floor(log2(n))) && "Samples must be pow2"); + } Layout layout; Usage usage; Aspect aspect; + //NOTE: matches VkSampleCountFlagBits + int samples; bool optimal; static requirement Texture(const properties &props) { diff --git a/src/client/render/gl/Renderer.cpp b/src/client/render/gl/Renderer.cpp index c6bb723..0432d33 100644 --- a/src/client/render/gl/Renderer.cpp +++ b/src/client/render/gl/Renderer.cpp @@ -63,11 +63,11 @@ void framebuffer_size_callback(GLFWwindow *, int width, int height) { glViewport(0, 0, width, height); } -bool Renderer::Load(Window& window, const renderOptions& opt) { +bool Renderer::Load(Window& window, const renderOptions& opt, int samples) { Window::CreateInfo windowInfo; windowInfo.pfnResize = framebuffer_size_callback; windowInfo.client = {Window::CreateInfo::Client::Type::GL, GL_MAJOR, GL_MINOR}; - windowInfo.samples = 1; + windowInfo.samples = samples; if (!window.create(windowInfo)) return false; diff --git a/src/client/render/gl/Renderer.hpp b/src/client/render/gl/Renderer.hpp index eefd475..607487c 100644 --- a/src/client/render/gl/Renderer.hpp +++ b/src/client/render/gl/Renderer.hpp @@ -15,7 +15,7 @@ class Renderer final: public render::Renderer { public: virtual ~Renderer(); - static bool Load(Window& window, const renderOptions& options); + static bool Load(Window& window, const renderOptions& options, int samples); glm::vec3 FogColor; GLfloat FogDepth; diff --git a/src/client/render/index.cpp b/src/client/render/index.cpp index 26f05bd..10d6af7 100644 --- a/src/client/render/index.cpp +++ b/src/client/render/index.cpp @@ -5,20 +5,20 @@ namespace render { -bool Load(Window& window, bool preferVulkan, const renderOptions& options) { +bool Load(Window& window, bool preferVulkan, const renderOptions& options, int samples) { if(!preferVulkan) { LOG_T("Trying OpenGL"); - if(gl::Renderer::Load(window, options)) + if(gl::Renderer::Load(window, options, samples)) return true; window.destroy(); } LOG_T("Trying Vulkan"); - if(vk::Renderer::Load(window, options)) + if(vk::Renderer::Load(window, options, samples)) return true; window.destroy(); if(preferVulkan) { LOG_I("Fallback to OpenGL"); - if(gl::Renderer::Load(window, options)) + if(gl::Renderer::Load(window, options, samples)) return true; } LOG_E("No available graphics library"); diff --git a/src/client/render/index.hpp b/src/client/render/index.hpp index 5184394..21920ce 100644 --- a/src/client/render/index.hpp +++ b/src/client/render/index.hpp @@ -4,5 +4,5 @@ class Window; namespace render { struct renderOptions; -bool Load(Window& window, bool preferVulkan, const renderOptions& options); +bool Load(Window& window, bool preferVulkan, const renderOptions& options, int samples); } \ No newline at end of file diff --git a/src/client/render/vk/Allocator.cpp b/src/client/render/vk/Allocator.cpp index 3c30208..7eaa551 100644 --- a/src/client/render/vk/Allocator.cpp +++ b/src/client/render/vk/Allocator.cpp @@ -243,6 +243,12 @@ void Allocator::transitionImageLayout(VkImage image, VkFormat format, VkImageLay sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; } else { FATAL("Unsupported layout transition!"); } diff --git a/src/client/render/vk/CommandCenter.cpp b/src/client/render/vk/CommandCenter.cpp index 4f15cf5..e9e98d1 100644 --- a/src/client/render/vk/CommandCenter.cpp +++ b/src/client/render/vk/CommandCenter.cpp @@ -34,6 +34,9 @@ CommandCenter::CommandCenter(VkDevice device, const PhysicalDeviceInfo &info, co FATAL("Failed to create texture sampler!"); } } + colorFormat = info.getSurfaceFormat().format; + colorSamples = info.samples; + LOG_D("Samples: " << colorSamples); depthFormat = info.findDepthFormat(); } CommandCenter::~CommandCenter() { @@ -46,13 +49,25 @@ CommandCenter::~CommandCenter() { void CommandCenter::allocate(const std::vector& views, const Pipeline& pipe, VkExtent2D extent, const renderOptions& opt) { assert(freed); + if (colorSamples > 1) { + colorbuffer = Image::Create(Image::requirement({{extent.height, extent.width}, 1, (Image::Format)colorFormat}, + Image::Layout::COLOR_ATTACHMENT, Image::Usage::COLOR_ATTACHMENT | Image::Usage::TRANSIENT_ATTACHMENT, + Image::Aspect::COLOR, colorSamples)); + } + depthbuffer = Image::Create(Image::requirement({{extent.height, extent.width}, 1, (Image::Format)depthFormat}, - Image::Layout::DEPTH_STENCIL_ATTACHMENT, Image::Usage::DEPTH_STENCIL_ATTACHMENT, Image::Aspect::DEPTH)); + Image::Layout::DEPTH_STENCIL_ATTACHMENT, Image::Usage::DEPTH_STENCIL_ATTACHMENT, + Image::Aspect::DEPTH, colorSamples)); framebuffers.resize(views.size()); for (size_t i = 0; i < views.size(); i++) { - std::array attachments = { views[i], depthbuffer->getView() }; + std::vector attachments; + if (colorSamples > 1) { + attachments = {colorbuffer->getView(), depthbuffer->getView(), views[i]}; + } else { + attachments = {views[i], depthbuffer->getView()}; + } VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; @@ -198,6 +213,7 @@ void CommandCenter::free() { vkDestroyDescriptorPool(device, descriptorPool, nullptr); + colorbuffer.reset(); depthbuffer.reset(); for (size_t i = 0; i < framebuffers.size(); i++) { @@ -216,6 +232,7 @@ void CommandCenter::updateUBO(uint32_t idx) { auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration(currentTime - startTime).count(); buffer::vk::UniformBufferObject ubo{}; + time = 0.1; ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.proj = proj; diff --git a/src/client/render/vk/CommandCenter.hpp b/src/client/render/vk/CommandCenter.hpp index e292497..a4d7288 100644 --- a/src/client/render/vk/CommandCenter.hpp +++ b/src/client/render/vk/CommandCenter.hpp @@ -25,6 +25,10 @@ private: VkDevice device; std::vector framebuffers; + + VkFormat colorFormat; + VkSampleCountFlagBits colorSamples; + std::unique_ptr colorbuffer; VkFormat depthFormat; std::unique_ptr depthbuffer; diff --git a/src/client/render/vk/PhysicalDeviceInfo.hpp b/src/client/render/vk/PhysicalDeviceInfo.hpp index 7a6bb04..a7ea5d9 100644 --- a/src/client/render/vk/PhysicalDeviceInfo.hpp +++ b/src/client/render/vk/PhysicalDeviceInfo.hpp @@ -24,12 +24,26 @@ struct QueueFamilyIndices { }; struct PhysicalDeviceInfo { PhysicalDeviceInfo() {} - PhysicalDeviceInfo(GLFWwindow *window, VkPhysicalDevice device, VkSurfaceKHR surface): + PhysicalDeviceInfo(GLFWwindow *window, VkPhysicalDevice device, VkSurfaceKHR surface, int targetSamples): window(window), device(device), surface(surface), swapDetails(SwapChainSupportDetails::Query(device, surface)), queueIndices(QueueFamilyIndices::Query(device, surface)) { vkGetPhysicalDeviceProperties(device, &properties); vkGetPhysicalDeviceFeatures(device, &features); + + samples = [&] { + if (targetSamples < 0) + targetSamples = 4; + VkSampleCountFlags counts = properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts; + //MAYBE: properties.limits.framebufferStencilSampleCounts + for (auto sample: {VK_SAMPLE_COUNT_64_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_16_BIT, + VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT + }) { + if (targetSamples >= sample && (counts & sample)) + return sample; + } + return VK_SAMPLE_COUNT_1_BIT; + }(); } VkSurfaceFormatKHR getSurfaceFormat() const; @@ -44,6 +58,7 @@ struct PhysicalDeviceInfo { GLFWwindow *window; VkPhysicalDevice device = VK_NULL_HANDLE; VkSurfaceKHR surface; + VkSampleCountFlagBits samples; SwapChainSupportDetails swapDetails; QueueFamilyIndices queueIndices; VkPhysicalDeviceProperties properties; diff --git a/src/client/render/vk/Pipeline.cpp b/src/client/render/vk/Pipeline.cpp index ed36dc1..f0cf3b8 100644 --- a/src/client/render/vk/Pipeline.cpp +++ b/src/client/render/vk/Pipeline.cpp @@ -13,16 +13,18 @@ constexpr auto BLENDING = true; using namespace render::vk; Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const renderOptions &options): device(device) { + const auto hasSamples = info.samples > 1; { // Render pass VkAttachmentDescription colorAttachment{}; colorAttachment.format = info.getSurfaceFormat().format; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.samples = info.samples; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = hasSamples ? + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; //TODO: layouts bind @@ -30,7 +32,7 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render VkAttachmentDescription depthAttachment{}; depthAttachment.format = info.findDepthFormat(); - depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.samples = info.samples; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; @@ -42,6 +44,20 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render depthAttachmentRef.attachment = 1; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + VkAttachmentDescription colorAttachmentResolve{}; + colorAttachmentResolve.format = colorAttachment.format; + colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentResolveRef{}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + //TODO: subpasses (world, entities, colors, sky, ui) VkSubpassDescription subpass{}; @@ -49,6 +65,9 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; subpass.pDepthStencilAttachment = &depthAttachmentRef; + if (hasSamples) { + subpass.pResolveAttachments = &colorAttachmentResolveRef; + } VkSubpassDependency dependency{}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; @@ -60,7 +79,10 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - std::array attachments = {colorAttachment, depthAttachment}; + std::vector attachments = {colorAttachment, depthAttachment}; + if (hasSamples) { + attachments.push_back(colorAttachmentResolve); + } renderPassInfo.attachmentCount = attachments.size(); renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; @@ -190,11 +212,12 @@ Pipeline::Pipeline(VkDevice device, const PhysicalDeviceInfo &info, const render rasterizer.depthBiasClamp = 0.0f; rasterizer.depthBiasSlopeFactor = 0.0f; - VkPipelineMultisampleStateCreateInfo multisampling{}; //TODO: multisampling NOTE: requires gpu feature + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - multisampling.minSampleShading = 1.0f; + multisampling.rasterizationSamples = info.samples; + //MAYBE: add option + multisampling.sampleShadingEnable = hasSamples; + multisampling.minSampleShading = .2f; multisampling.pSampleMask = nullptr; multisampling.alphaToCoverageEnable = VK_FALSE; multisampling.alphaToOneEnable = VK_FALSE; diff --git a/src/client/render/vk/Renderer.cpp b/src/client/render/vk/Renderer.cpp index 8388886..e358429 100644 --- a/src/client/render/vk/Renderer.cpp +++ b/src/client/render/vk/Renderer.cpp @@ -155,11 +155,11 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugValidationCallback(VkDebugUtilsMessageSeveri return VK_FALSE; } -bool Renderer::Load(Window& window, const renderOptions& opt) { +bool Renderer::Load(Window& window, const renderOptions& opt, int samples) { Window::CreateInfo windowInfo; windowInfo.pfnResize = on_resize_callback; windowInfo.client = {Window::CreateInfo::Client::Type::VK, 0, 0}; - windowInfo.samples = 1; + windowInfo.samples = -1; if (!window.create(windowInfo)) return false; @@ -289,7 +289,7 @@ bool Renderer::Load(Window& window, const renderOptions& opt) { uint bestScore = 0; for(const auto& device: devices) { uint score = 1; - auto infos = PhysicalDeviceInfo(window.getPtr(), device, surface); + auto infos = PhysicalDeviceInfo(window.getPtr(), device, surface, samples); { uint32_t availableExtensionsCount; @@ -321,6 +321,7 @@ bool Renderer::Load(Window& window, const renderOptions& opt) { score += 10000; score += infos.properties.limits.maxImageDimension2D; + score += infos.samples * 50; if (infos.features.geometryShader) score += 2000; diff --git a/src/client/render/vk/Renderer.hpp b/src/client/render/vk/Renderer.hpp index 89b22b1..e858592 100644 --- a/src/client/render/vk/Renderer.hpp +++ b/src/client/render/vk/Renderer.hpp @@ -13,7 +13,7 @@ class Renderer final: public render::Renderer { public: virtual ~Renderer(); - static bool Load(Window& window, const renderOptions& options); + static bool Load(Window& window, const renderOptions& options, int samples); void loadUI(Window &) override; diff --git a/src/client/render/vk/api/Images.cpp b/src/client/render/vk/api/Images.cpp index 33686b2..49ff48b 100644 --- a/src/client/render/vk/api/Images.cpp +++ b/src/client/render/vk/api/Images.cpp @@ -30,7 +30,7 @@ memory::ptr createImage(const Image::requirement& req, VkMemoryPropertyFlags pro info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; info.usage = static_cast(req.usage); info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - info.samples = VK_SAMPLE_COUNT_1_BIT; + info.samples = static_cast(req.samples); info.flags = 0; if (data) { info.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; @@ -140,6 +140,10 @@ std::unique_ptr Texture::LoadFromFile(const std::string& path, const sa samplerInfo.minLod = 0.0f; samplerInfo.maxLod = props.mipmap ? header.mipmapLevels : 0.0f; + if (props.mipmap && header.mipmapLevels <= 1) { + LOG_D("Sampler requires mipmap but image does not"); + } + VkSampler sampler; if (vkCreateSampler(device, &samplerInfo, ALLOC, &sampler) != VK_SUCCESS) { FATAL("Failed to create texture sampler!"); diff --git a/src/core/config.hpp b/src/core/config.hpp index b615091..9c6b3cc 100644 --- a/src/core/config.hpp +++ b/src/core/config.hpp @@ -23,10 +23,10 @@ public: LOG_E("Config file " << path << " not found. Creating default"); return toml::table(); }(); - if (config["server"]["enabled"].value_or(false)) { + if (config["server"]["enabled"].value_or(true)) { _server = server::options(config["server"]); } - if(config["client"]["enabled"].value_or(false)) { + if(config["client"]["enabled"].value_or(true)) { _client = client::options(config["client"]); } }