diff --git a/Assets/Shaders/Deferred/deferred.frag b/Assets/Shaders/Deferred/deferred.frag index aceb3ffa..8501131f 100644 --- a/Assets/Shaders/Deferred/deferred.frag +++ b/Assets/Shaders/Deferred/deferred.frag @@ -40,7 +40,16 @@ layout (binding = 7) uniform UBO void main() { // Get G-Buffer values - vec3 fragPos = texture(samplerposition, inUV).rgb; + vec4 position = texture(samplerposition, inUV); + + // The offscreen pass writes alpha 1 for geometry and clears to 0. Keep what is + // already in the frame (the skybox, if there is one) where there is no geometry. + if (position.a == 0.0) + { + discard; + } + + vec3 fragPos = position.rgb; vec3 normal = normalize(texture(samplerNormal, inUV).rgb); vec4 albedo = texture(samplerAlbedo, inUV); float roughness = texture(samplerRoughness, inUV).x; diff --git a/Assets/Shaders/skybox/skybox.frag b/Assets/Shaders/skybox/skybox.frag index 56b8f42c..a7da74ac 100644 --- a/Assets/Shaders/skybox/skybox.frag +++ b/Assets/Shaders/skybox/skybox.frag @@ -1,12 +1,22 @@ #version 450 +// Inverse of projection * view, with the camera translation removed. @see SkyboxSubpass.cpp +layout (binding = 0) uniform UBO +{ + mat4 invViewProjection; +} ubo; + layout (binding = 1) uniform samplerCube samplerCubeMap; -layout (location = 0) in vec3 inUVW; +layout (location = 0) in vec2 inNDC; layout (location = 0) out vec4 outFragColor; -void main() +void main() { - outFragColor = texture(samplerCubeMap, inUVW); -} \ No newline at end of file + // Change the pixel back to a world space direction from the camera + vec4 farPoint = ubo.invViewProjection * vec4(inNDC, 1.0, 1.0); + vec3 direction = normalize(farPoint.xyz / farPoint.w); + + outFragColor = vec4(texture(samplerCubeMap, direction).rgb, 1.0); +} diff --git a/Assets/Shaders/skybox/skybox.vert b/Assets/Shaders/skybox/skybox.vert index 32b1ee1b..38068bcf 100644 --- a/Assets/Shaders/skybox/skybox.vert +++ b/Assets/Shaders/skybox/skybox.vert @@ -1,25 +1,18 @@ #version 450 -layout (location = 0) in vec3 inPos; -layout(location = 1) in vec3 inColor; -layout(location = 2) in vec2 inTexCoord; +// Normalized device coordinates of this pixel, for the fragment shader to make a view ray +layout (location = 0) out vec2 outNDC; -layout (binding = 0) uniform UBO -{ - mat4 projection; - mat4 modelView; -} ubo; - -layout (location = 0) out vec3 outUVW; - -out gl_PerVertex +out gl_PerVertex { vec4 gl_Position; }; -void main() +void main() { - vec3 position = mat3(ubo.modelView) * inPos.xyz; - gl_Position = (ubo.projection * vec4(position, 0.0)).xyzz; - outUVW = inPos.xyz; + // One triangle that covers the full screen, made from the vertex index with no vertex buffer + // @see https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ + vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + outNDC = uv * 2.0 - 1.0; + gl_Position = vec4(outNDC, 1.0, 1.0); } diff --git a/Config/EngineConf.ini b/Config/EngineConf.ini index efa62ee1..4951a132 100644 --- a/Config/EngineConf.ini +++ b/Config/EngineConf.ini @@ -21,6 +21,11 @@ WindowIconImage=FlingEngineLogo.png EnableValidationLayers=false #EnableValidationLayers=true +[Graphics] +; Cube map folder for the skybox, relative to the Assets directory. Leave empty for no skybox. +; @see Cubemap.h for the face file names +Skybox=Textures/Skybox/Garden + [Camera] MoveSpeed=10 RotationSpeed=700 diff --git a/FlingEngine/Application/src/Engine.cpp b/FlingEngine/Application/src/Engine.cpp index fa74db06..de0c8df3 100644 --- a/FlingEngine/Application/src/Engine.cpp +++ b/FlingEngine/Application/src/Engine.cpp @@ -128,7 +128,7 @@ namespace Fling else { VulkanApp::Get().Init( - static_cast(PipelineFlags::DEFERRED | PipelineFlags::IMGUI), + static_cast(PipelineFlags::DEFERRED | PipelineFlags::CUBEMAP | PipelineFlags::IMGUI), g_Registry, m_DebugUI ); diff --git a/FlingEngine/Graphics/inc/VulkanApp.h b/FlingEngine/Graphics/inc/VulkanApp.h index 1c880b61..d3eb6cc9 100644 --- a/FlingEngine/Graphics/inc/VulkanApp.h +++ b/FlingEngine/Graphics/inc/VulkanApp.h @@ -16,6 +16,7 @@ namespace Fling DEFERRED = (1u << 0), REFLECTIONS = (1u << 1), IMGUI = (1u << 2), + /** Draw a skybox behind the DEFERRED pipeline. The cube map folder is the [Graphics] Skybox config value. */ CUBEMAP = (1u << 3), DEBUG = (1u << 4), ALL = 0xff diff --git a/FlingEngine/Graphics/src/Cubemap.cpp b/FlingEngine/Graphics/src/Cubemap.cpp index 0ed6fcc7..1adc871e 100644 --- a/FlingEngine/Graphics/src/Cubemap.cpp +++ b/FlingEngine/Graphics/src/Cubemap.cpp @@ -2,523 +2,264 @@ #include "ResourceManager.h" #include "GraphicsHelpers.h" #include "LogicalDevice.h" -#include "PhyscialDevice.h" -#include "HDRImage.h" #include "VulkanApp.h" +#include "Buffer.h" #include "Logger.h" +#include "stb_image.h" + +#include +#include +#include +#include namespace Fling { - Cubemap::Cubemap( - Guid t_PosX_ID, - Guid t_NegX_ID, - Guid t_PosY_ID, - Guid t_NegY_ID, - Guid t_PosZ_ID, - Guid t_NegZ_ID, - Guid t_VertexShader, - Guid t_FragShader, - VkRenderPass t_RenderPass, - LogicalDevice* t_LogicalDevice) : - m_VertexShader(t_VertexShader), - m_FragShader(t_FragShader), - m_Device(t_LogicalDevice), - m_RenderPass(t_RenderPass) + namespace { - m_Cube = Model::Create("Models/cube.obj"_hs); - - LoadCubemapImages( - t_PosX_ID, - t_NegX_ID, - t_PosY_ID, - t_NegY_ID, - t_PosZ_ID, - t_NegZ_ID); - } + constexpr VkFormat CubemapFormat = VK_FORMAT_R8G8B8A8_UNORM; + constexpr uint32 CubemapFaceCount = 6; - Cubemap::Cubemap(Guid t_CubeMap_ID, VkRenderPass t_Renderpass, VkDevice t_LogicalDevice) - { - m_Cube = Model::Create("Models/cube.obj"_hs); + /** Face file names in Vulkan cube layer order: +X, -X, +Y, -Y, +Z, -Z */ + constexpr std::array FaceNames = { + "posx", "negx", "posy", "negy", "posz", "negz"}; - LoadCubeMapImage(t_CubeMap_ID); - } + constexpr std::array FaceExtensions = {".png", ".jpg", ".jpeg", ".tga", ".bmp"}; - Cubemap::~Cubemap() - { - if (m_ImageMemory) + /** Find the file for one face in the folder. Returns an empty string if there is no file. */ + std::string FindFaceFile(const std::string& t_Folder, const char* t_FaceName) { - vkFreeMemory(m_Device->GetVkDevice(), m_ImageMemory, nullptr); - m_ImageMemory = nullptr; + for (const char* Ext : FaceExtensions) + { + std::string Path = t_Folder + "/" + t_FaceName + Ext; + if (std::filesystem::exists(Path)) + { + return Path; + } + } + return {}; } + } // namespace - delete m_GraphicsPipeline; - m_GraphicsPipeline = nullptr; - - vkDestroyDescriptorPool(m_Device->GetVkDevice(), m_DescriptorPool, nullptr); - vkDestroyDescriptorSetLayout(m_Device->GetVkDevice(), m_DescriptorSetLayout, nullptr); - vkDestroyImage(m_Device->GetVkDevice(), m_Image, nullptr); - vkDestroyImageView(m_Device->GetVkDevice(), m_Imageview, nullptr); - vkDestroySampler(m_Device->GetVkDevice(), m_Sampler, nullptr); - } - - void Cubemap::Init(Camera* t_Camera, uint32 t_CurrentImage, size_t t_NumFramesInFlight, Multisampler* t_Sampler) + std::shared_ptr Cubemap::Create(Guid t_ID) { - // Initialize uniform buffers - m_numsFrameInFlight = t_NumFramesInFlight; - m_UniformBuffers.resize(m_numsFrameInFlight); - for (size_t i = 0; i < m_numsFrameInFlight; ++i) - { - m_UniformBuffers[i] = std::make_unique( - sizeof(UboSkyboxVS), - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - - m_UniformBuffers[i]->MapMemory(); - } - - UpdateUniformBuffer(t_CurrentImage, t_Camera->GetProjectionMatrix(), t_Camera->GetViewMatrix()); - SetupDescriptors(); - PreparePipeline(t_Sampler); + return ResourceManager::LoadResource(t_ID); } - void Cubemap::PreparePipeline(Multisampler* t_Sampler) + Cubemap::Cubemap(Guid t_ID) : Resource(t_ID) { - std::vector shaders = + // Under --null-render there is no GPU device, so there is no image to make + if (!VulkanApp::Get().HasDevice()) { - Shader::Create(m_VertexShader, m_Device).get(), - Shader::Create(m_FragShader, m_Device).get(), - }; - - m_GraphicsPipeline = new GraphicsPipeline( - shaders, - m_Device->GetVkDevice(), - VK_POLYGON_MODE_FILL, - GraphicsPipeline::Depth::ReadWrite, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - VK_CULL_MODE_FRONT_BIT, - VK_FRONT_FACE_COUNTER_CLOCKWISE); - - m_GraphicsPipeline->CreateGraphicsPipeline(m_RenderPass, t_Sampler); - } - - void Cubemap::LoadCubeMapImage(Guid t_CubeMap_ID) - { - std::shared_ptr image = ResourceManager::LoadResource(t_CubeMap_ID, m_Device); - - m_ImageSize = image->GetImageSize(); - m_NumChannels = image->GetChannels(); - m_LayerSize = m_ImageSize / 6; - m_MipLevels = image->GetMipLevels(); - //m_MipLevels = 1.0f; - m_Format = image->GetVkImageFormat(); - - std::unique_ptr stagingBuffer = std::make_unique(); - stagingBuffer->CreateBuffer(m_ImageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, true); - stagingBuffer->MapMemory(); - - float* pixelDst = (float*)(stagingBuffer->m_MappedMem); - const float* pixels = image->GetPixelData(); - memcpy(pixelDst, pixels, m_ImageSize); - - stagingBuffer->UnmapMemory(); - - - GraphicsHelpers::CreateVkImage( - m_Device->GetVkDevice(), - image->GetWidth(), - image->GetHeight(), - m_MipLevels, // MipLevels - 1, // Depth - 6, // Array layers - m_Format, - VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT, - m_Image, - m_ImageMemory); - - VkCommandBuffer copyCmd = GraphicsHelpers::BeginSingleTimeCommands(); - - std::vector< VkBufferImageCopy> bufferCopyRegions; - VkDeviceSize offset = 0; - - for (size_t face = 0; face < 6; face++) - { - VkBufferImageCopy bufferCopyRegion = {}; - bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - bufferCopyRegion.imageSubresource.mipLevel = 0; - bufferCopyRegion.imageSubresource.baseArrayLayer = face; - bufferCopyRegion.imageSubresource.layerCount = 1; - bufferCopyRegion.imageExtent.width = image->GetWidth(); - bufferCopyRegion.imageExtent.height = image->GetHeight(); - bufferCopyRegion.imageExtent.depth = 1; - bufferCopyRegion.bufferOffset = offset; - - bufferCopyRegions.push_back(bufferCopyRegion); - - // Increase offset into staging buffer for next level / face - offset += image->GetImageSize(); + return; } - // Image barrier for optimal image (target) - // Set initial layout for all array layers (faces) of the optimal (target) tiled texture - VkImageSubresourceRange subresourceRange = {}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = m_MipLevels; - subresourceRange.layerCount = 6; - - GraphicsHelpers::SetImageLayout( - copyCmd, - m_Image, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - subresourceRange, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - - // Copy the cube map faces from the staging buffer to the optimal tiled image - vkCmdCopyBufferToImage( - copyCmd, - stagingBuffer->GetVkBuffer(), - m_Image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - static_cast(bufferCopyRegions.size()), - bufferCopyRegions.data() - ); - - // Change texture image layout to shader read after all faces have been copied - m_ImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - GraphicsHelpers::SetImageLayout( - copyCmd, - m_Image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - m_ImageLayout, - subresourceRange, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - - GraphicsHelpers::EndSingleTimeCommands(copyCmd); - - // Create sampler - VkSamplerCreateInfo sampler = Initializers::SamplerCreateInfo(); - sampler.magFilter = VK_FILTER_LINEAR; - sampler.minFilter = VK_FILTER_LINEAR; - sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - sampler.mipLodBias = 0.0f; - sampler.compareOp = VK_COMPARE_OP_NEVER; - sampler.minLod = 0.0f; - sampler.maxLod = static_cast(m_MipLevels); - sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - sampler.maxAnisotropy = 1.0f; - - // Handle anisotropy - const PhysicalDevice* Device = VulkanApp::Get().GetPhysicalDevice(); - if (Device->GetDeivceFeatures().samplerAnisotropy) + LoadVulkanImage(); + if (m_Image == VK_NULL_HANDLE) { - sampler.maxAnisotropy = Device->GetDeviceProps().limits.maxSamplerAnisotropy; - sampler.anisotropyEnable = VK_TRUE; + return; } - if (vkCreateSampler(m_Device->GetVkDevice(), &sampler, nullptr, &m_Sampler) != VK_SUCCESS) - { - F_LOG_ERROR("Cube failed to create sampler"); - } + CreateImageView(); + CreateSampler(); - VkImageViewCreateInfo view = Initializers::ImageViewCreateInfo(); - view.viewType = VK_IMAGE_VIEW_TYPE_CUBE; - view.format = m_Format; - view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; - view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - view.subresourceRange.layerCount = 6; - view.subresourceRange.levelCount = m_MipLevels; - view.image = m_Image; - if (vkCreateImageView(m_Device->GetVkDevice(), &view, nullptr, &m_Imageview) != VK_SUCCESS) - { - F_LOG_ERROR("Cube failed to create image view"); - } + m_ImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + m_ImageInfo.imageView = m_ImageView; + m_ImageInfo.sampler = m_Sampler; } - void Cubemap::LoadCubemapImages( - Guid t_PosX_ID, - Guid t_NegX_ID, - Guid t_PosY_ID, - Guid t_NegY_ID, - Guid t_PosZ_ID, - Guid t_NegZ_ID) + void Cubemap::LoadVulkanImage() { - std::array, 6> images = + const std::string Folder = GetFilepathReleativeToAssets(); + + // Load every face first, so that a missing or bad face makes no GPU objects + std::array Pixels = {}; + auto FreePixels = [&Pixels]() { - ResourceManager::LoadResource(t_PosX_ID), - ResourceManager::LoadResource(t_NegX_ID), - ResourceManager::LoadResource(t_PosY_ID), - ResourceManager::LoadResource(t_NegY_ID), - ResourceManager::LoadResource(t_PosZ_ID), - ResourceManager::LoadResource(t_NegZ_ID), + for (stbi_uc*& Face : Pixels) + { + stbi_image_free(Face); + Face = nullptr; + } }; - m_ImageSize = images[0]->GetImageSize() * 6; - m_NumChannels = images[0]->GetChannels(); - m_LayerSize = m_ImageSize / 6; - //TODO: add mip levels to image - m_MipLevels = images[0]->GetMipLevels(); - m_Format = images[0]->GetVkImageFormat(); - - - std::unique_ptr stagingBuffer = std::make_unique(); - stagingBuffer->CreateBuffer(m_ImageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, true); - stagingBuffer->MapMemory(); - - stbi_uc* pixelDst = (stbi_uc*)(stagingBuffer->m_MappedMem); - for (size_t i = 0; i < 6; i++) + for (uint32 Face = 0; Face < CubemapFaceCount; ++Face) { - const stbi_uc* pixels = images[i]->GetPixelData(); - memcpy(pixelDst, pixels, m_LayerSize); - pixelDst += m_LayerSize; - } + const std::string Path = FindFaceFile(Folder, FaceNames[Face]); + if (Path.empty()) + { + F_LOG_ERROR("Cubemap {}: cannot find face '{}'", Folder, FaceNames[Face]); + FreePixels(); + return; + } + + int Width = 0; + int Height = 0; + int Channels = 0; + Pixels[Face] = stbi_load(Path.c_str(), &Width, &Height, &Channels, STBI_rgb_alpha); + if (!Pixels[Face]) + { + F_LOG_ERROR("Cubemap {}: failed to load face {}", Folder, Path); + FreePixels(); + return; + } - stagingBuffer->UnmapMemory(); - - GraphicsHelpers::CreateVkImage( - m_Device->GetVkDevice(), - images[0]->GetWidth(), - images[0]->GetHeight(), - m_MipLevels, // MipLevels - 1, // Depth - 6, // Array layers - m_Format, - VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT, - m_Image, - m_ImageMemory); - - VkCommandBuffer copyCmd = GraphicsHelpers::BeginSingleTimeCommands(); - - std::vector< VkBufferImageCopy> bufferCopyRegions; - VkDeviceSize offset = 0; - - for (size_t face = 0; face < 6; face++) - { - VkBufferImageCopy bufferCopyRegion = {}; - bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - bufferCopyRegion.imageSubresource.mipLevel = 0; - bufferCopyRegion.imageSubresource.baseArrayLayer = face; - bufferCopyRegion.imageSubresource.layerCount = 1; - bufferCopyRegion.imageExtent.width = images[face]->GetWidth(); - bufferCopyRegion.imageExtent.height = images[face]->GetHeight(); - bufferCopyRegion.imageExtent.depth = 1; - bufferCopyRegion.bufferOffset = offset; - - bufferCopyRegions.push_back(bufferCopyRegion); - - // Increase offset into staging buffer for next level / face - offset += images[face]->GetImageSize(); + if (Width != Height || (Face > 0 && static_cast(Width) != m_FaceSize)) + { + F_LOG_ERROR("Cubemap {}: face {} is {}x{}. All faces must be square and the same size.", + Folder, + Path, + Width, + Height); + FreePixels(); + return; + } + m_FaceSize = static_cast(Width); } - // Image barrier for optimal image (target) - // Set initial layout for all array layers (faces) of the optimal (target) tiled texture - VkImageSubresourceRange subresourceRange = {}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = m_MipLevels; - subresourceRange.layerCount = 6; - - GraphicsHelpers::SetImageLayout( - copyCmd, - m_Image, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - subresourceRange, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - - // Copy the cube map faces from the staging buffer to the optimal tiled image - vkCmdCopyBufferToImage( - copyCmd, - stagingBuffer->GetVkBuffer(), - m_Image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - static_cast(bufferCopyRegions.size()), - bufferCopyRegions.data() - ); - - // Change texture image layout to shader read after all faces have been copied - m_ImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - GraphicsHelpers::SetImageLayout( - copyCmd, - m_Image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - m_ImageLayout, - subresourceRange, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - - GraphicsHelpers::EndSingleTimeCommands(copyCmd); - - // Create sampler - VkSamplerCreateInfo sampler = Initializers::SamplerCreateInfo(); - sampler.magFilter = VK_FILTER_LINEAR; - sampler.minFilter = VK_FILTER_LINEAR; - sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - sampler.addressModeV = sampler.addressModeU; - sampler.addressModeW = sampler.addressModeU; - sampler.mipLodBias = 0.0f; - sampler.compareOp = VK_COMPARE_OP_NEVER; - sampler.minLod = 0.0f; - sampler.maxLod = static_cast(m_MipLevels); - sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - - // Handle anisotropy - const PhysicalDevice* Device = VulkanApp::Get().GetPhysicalDevice(); - if (Device->GetDeivceFeatures().samplerAnisotropy) + // Put all six faces one after the other in a staging buffer + const VkDeviceSize FaceBytes = static_cast(m_FaceSize) * m_FaceSize * 4; + Buffer StagingBuffer(FaceBytes * CubemapFaceCount, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + StagingBuffer.MapMemory(); + for (uint32 Face = 0; Face < CubemapFaceCount; ++Face) { - sampler.maxAnisotropy = Device->GetDeviceProps().limits.maxSamplerAnisotropy; - sampler.anisotropyEnable = VK_TRUE; + std::memcpy(static_cast(StagingBuffer.m_MappedMem) + FaceBytes * Face, Pixels[Face], FaceBytes); } - - if (vkCreateSampler(m_Device->GetVkDevice(), &sampler, nullptr, &m_Sampler) != VK_SUCCESS) + StagingBuffer.UnmapMemory(); + FreePixels(); + + VkDevice Device = VulkanApp::Get().GetLogicalDevice()->GetVkDevice(); + GraphicsHelpers::CreateVkImage(Device, + m_FaceSize, + m_FaceSize, + /* Mip levels */ 1, + /* Depth */ 1, + /* Array layers */ CubemapFaceCount, + CubemapFormat, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT, + m_Image, + m_ImageMemory); + + std::array Regions = {}; + for (uint32 Face = 0; Face < CubemapFaceCount; ++Face) { - F_LOG_ERROR("Cube failed to create sampler"); + VkBufferImageCopy& Region = Regions[Face]; + Region.bufferOffset = FaceBytes * Face; + Region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + Region.imageSubresource.mipLevel = 0; + Region.imageSubresource.baseArrayLayer = Face; + Region.imageSubresource.layerCount = 1; + Region.imageExtent = {m_FaceSize, m_FaceSize, 1}; } - VkImageViewCreateInfo view = Initializers::ImageViewCreateInfo(); - view.viewType = VK_IMAGE_VIEW_TYPE_CUBE; - view.format = m_Format; - view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; - view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - view.subresourceRange.layerCount = 6; - view.subresourceRange.levelCount = m_MipLevels; - view.image = m_Image; - if (vkCreateImageView(m_Device->GetVkDevice(), &view, nullptr, &m_Imageview) != VK_SUCCESS) - { - F_LOG_ERROR("Cube failed to create image view"); - } + VkImageSubresourceRange Range = {}; + Range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + Range.baseMipLevel = 0; + Range.levelCount = 1; + Range.baseArrayLayer = 0; + Range.layerCount = CubemapFaceCount; + + VkCommandBuffer CmdBuf = GraphicsHelpers::BeginSingleTimeCommands(); + + GraphicsHelpers::SetImageLayout(CmdBuf, + m_Image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + Range, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + vkCmdCopyBufferToImage(CmdBuf, + StagingBuffer.GetVkBuffer(), + m_Image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + static_cast(Regions.size()), + Regions.data()); + + GraphicsHelpers::SetImageLayout(CmdBuf, + m_Image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + Range, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + + GraphicsHelpers::EndSingleTimeCommands(CmdBuf); } - void Cubemap::SetupDescriptors() + void Cubemap::CreateImageView() { - //Descriptor pools - std::vector poolSizes = - { - Initializers::DescriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), - Initializers::DescriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2) - }; + VkImageViewCreateInfo ViewInfo = Initializers::ImageViewCreateInfo(); + ViewInfo.image = m_Image; + ViewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + ViewInfo.format = CubemapFormat; + ViewInfo.components = { + VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; + ViewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, CubemapFaceCount}; + + VK_CHECK_RESULT( + vkCreateImageView(VulkanApp::Get().GetLogicalDevice()->GetVkDevice(), &ViewInfo, nullptr, &m_ImageView)); + } - VkDescriptorPoolCreateInfo descriptorPoolInfo = - Initializers::DescriptorPoolCreateInfo(poolSizes, 2); + void Cubemap::CreateSampler() + { + VkSamplerCreateInfo SamplerInfo = Initializers::SamplerCreateInfo(); + SamplerInfo.magFilter = VK_FILTER_LINEAR; + SamplerInfo.minFilter = VK_FILTER_LINEAR; + SamplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + // Clamp so that the edges between faces do not show seams + SamplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + SamplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + SamplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + SamplerInfo.compareOp = VK_COMPARE_OP_NEVER; + SamplerInfo.minLod = 0.0f; + SamplerInfo.maxLod = 0.0f; + SamplerInfo.maxAnisotropy = 1.0f; + SamplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + + VK_CHECK_RESULT( + vkCreateSampler(VulkanApp::Get().GetLogicalDevice()->GetVkDevice(), &SamplerInfo, nullptr, &m_Sampler)); + } - if (vkCreateDescriptorPool(m_Device->GetVkDevice(), &descriptorPoolInfo, nullptr, &m_DescriptorPool) != VK_SUCCESS) + void Cubemap::Release() + { + LogicalDevice* LogDevice = VulkanApp::Get().GetLogicalDevice(); + if (LogDevice == nullptr) { - F_LOG_ERROR("Cube map failed to create descriptor pool"); + // --null-render: the constructor made no GPU objects + return; } - //Descriptor SetLayout - std::vector setLayoutBinding = - { - //Binding 0 : vertex shader uniform buffer - Initializers::DescriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_VERTEX_BIT, - 0), - - Initializers::DescriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 1), - }; - - VkDescriptorSetLayoutCreateInfo descriptorLayout = - Initializers::DescriptorSetLayoutCreateInfo( - setLayoutBinding); + VkDevice Device = LogDevice->GetVkDevice(); - if (vkCreateDescriptorSetLayout(m_Device->GetVkDevice(), &descriptorLayout, nullptr, &m_DescriptorSetLayout) != VK_SUCCESS) + if (m_Sampler != VK_NULL_HANDLE) { - F_LOG_ERROR("Cube map failed to create descriptor set layout"); + vkDestroySampler(Device, m_Sampler, nullptr); + m_Sampler = VK_NULL_HANDLE; } - - //Descriptor Sets - m_DescriptorImageInfo = - Initializers::DescriptorImageInfo( - m_Sampler, - m_Imageview, - m_ImageLayout); - - VkDescriptorSetAllocateInfo allocInfo = - Initializers::DescriptorSetAllocateInfo( - m_DescriptorPool, - &m_DescriptorSetLayout, - 1); - - if (vkAllocateDescriptorSets(m_Device->GetVkDevice(), &allocInfo, &m_DescriptorSet) != VK_SUCCESS) + if (m_ImageView != VK_NULL_HANDLE) { - F_LOG_ERROR("Cube map failed to allocate descriptor sets") + vkDestroyImageView(Device, m_ImageView, nullptr); + m_ImageView = VK_NULL_HANDLE; } - - for (size_t i = 0; i < m_numsFrameInFlight; ++i) + if (m_Image != VK_NULL_HANDLE) { - m_UniformBufferDescriptor = {}; - m_UniformBufferDescriptor.buffer = m_UniformBuffers[i]->GetVkBuffer(); - m_UniformBufferDescriptor.offset = 0; - m_UniformBufferDescriptor.range = VK_WHOLE_SIZE; - - std::vector writeDescriptorSets = - { - // Binding 0 : Vertex shader uniform buffer - Initializers::WriteDescriptorSet( - m_DescriptorSet, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 0, - &m_UniformBufferDescriptor), - - // Binding 1 : Fragment shader cubemap sampler - Initializers::WriteDescriptorSet( - m_DescriptorSet, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 1, - &m_DescriptorImageInfo), - }; - - vkUpdateDescriptorSets(m_Device->GetVkDevice(), writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkDestroyImage(Device, m_Image, nullptr); + m_Image = VK_NULL_HANDLE; + } + if (m_ImageMemory != VK_NULL_HANDLE) + { + vkFreeMemory(Device, m_ImageMemory, nullptr); + m_ImageMemory = VK_NULL_HANDLE; } } - void Cubemap::UpdateUniformBuffer(uint32 t_CurrentImage, const glm::mat4& t_ProjectionMatrix, const glm::mat4& t_ViewMatrix) - { - m_UboVS.Projection = t_ProjectionMatrix; - m_UboVS.Projection[1][1] *= -1.0f; - - m_UboVS.ModelView = t_ViewMatrix; - m_UboVS.ModelView[3][0] = 0.0f; - m_UboVS.ModelView[3][1] = 0.0f; - m_UboVS.ModelView[3][2] = 0.0f; - - memcpy(m_UniformBuffers[t_CurrentImage]->m_MappedMem, &m_UboVS, sizeof(m_UboVS)); - } - - void Cubemap::BindCmdBuffer(VkCommandBuffer& t_CommandBuffer) + Cubemap::~Cubemap() { - vkCmdBindDescriptorSets( - t_CommandBuffer, - VK_PIPELINE_BIND_POINT_GRAPHICS, - m_GraphicsPipeline->GetPipelineLayout(), - 0, - 1, - &m_DescriptorSet, - 0, - NULL); - - VkDeviceSize offsets[1] = { 0 }; - - vkCmdBindVertexBuffers(t_CommandBuffer, 0, 1, &GetVertexBuffer()->GetVkBuffer(), offsets); - vkCmdBindIndexBuffer(t_CommandBuffer, GetIndexBuffer()->GetVkBuffer(), 0, GetIndexType()); - vkCmdBindPipeline(t_CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_GraphicsPipeline->GetPipeline()); - vkCmdDrawIndexed(t_CommandBuffer, GetIndexCount(), 1, 0, 0, 0); + Release(); } -} \ No newline at end of file +} // namespace Fling diff --git a/FlingEngine/Graphics/src/Cubemap.h b/FlingEngine/Graphics/src/Cubemap.h index 46304447..a5f36e86 100644 --- a/FlingEngine/Graphics/src/Cubemap.h +++ b/FlingEngine/Graphics/src/Cubemap.h @@ -1,132 +1,54 @@ #pragma once -#include "Model.h" -#include "Texture.h" +#include "Resource.h" #include "FlingVulkan.h" -#include "Buffer.h" -#include "FlingTypes.h" -#include "FlingMath.h" -#include "File.h" -#include "LogicalDevice.h" -#include "UniformBufferObject.h" -#include "Camera.h" -#include "GraphicsPipeline.h" -#include "MultiSampler.h" #include -#include namespace Fling { - class LogicalDevice; - - class Cubemap + /** + * A cube map image made from six face images in one folder. The Guid is the folder path + * relative to the assets directory, for example "Textures/Skybox/Garden". The folder must + * have the files posx, negx, posy, negy, posz and negz, each with an image extension + * (.png, .jpg, .jpeg, .tga or .bmp). All faces must be square and the same size. + */ + class Cubemap : public Resource { - public: - Cubemap( - Guid t_PosX_ID, - Guid t_NegX_ID, - Guid t_PosY_ID, - Guid t_NegY_ID, - Guid t_PosZ_ID, - Guid t_NegZ_ID, - Guid t_VertexShader, - Guid t_FragShader, - VkRenderPass t_RenderPass, - LogicalDevice* t_LogicalDevice); - - Cubemap( - Guid t_CubeMap_ID, - VkRenderPass t_Renderpass, - VkDevice t_LogicalDevice); - - ~Cubemap(); - - void Init(Camera* t_Camera, uint32 t_CurrentImage, size_t t_NumeFramesInFlight, Multisampler* t_Sampler); - - void UpdateUniformBuffer(uint32 t_CurrentImage, const glm::mat4& t_ProjectionMatrix, const glm::mat4& t_ViewMatrix); - - void BindCmdBuffer(VkCommandBuffer& t_CommandBuffer); - - std::unique_ptr GetGraphicsPipeline() const { return std::unique_ptr(m_GraphicsPipeline); } - - /** - * Get the Descriptor Sets object - * - * @return const VkDescriptorSet> - */ - VkDescriptorSet& GetDescriptorSet() { return m_DescriptorSet; } - - /** - * Get the Vertex Buffer object - * - * @return const Buffer* - */ - Buffer* GetVertexBuffer() const { return m_Cube->GetVertexBuffer(); } - - /** - * Get the Index Buffer object - * - * @return const Buffer* - */ - Buffer* GetIndexBuffer() const { return m_Cube->GetIndexBuffer(); } - - /** - * Get the Index Count object - */ - uint32 GetIndexCount() const { return m_Cube->GetIndexCount(); } - - VkIndexType GetIndexType() const { return m_Cube->GetIndexType(); } - - VkImage GetImage() const { return m_Image; } - VkDeviceMemory GetImageMemory() const{ return m_ImageMemory; } - VkDescriptorImageInfo& GetImageInfo() { return m_DescriptorImageInfo; } - - private: - - void PreparePipeline(Multisampler* t_Sampler); - - /** Loads cubemaps with file format .hdr for ibl **/ - void LoadCubeMapImage(Guid t_CubeMap_ID); - - void LoadCubemapImages( - Guid t_PosX_ID, - Guid t_NegX_ID, - Guid t_PosY_ID, - Guid t_NegY_ID, - Guid t_PosZ_ID, - Guid t_NegZ_ID); - - void SetupDescriptors(); - - std::vector m_cubeMapImages; - std::vector> m_UniformBuffers; - - VkImage m_Image; - VkImageView m_Imageview; - VkImageLayout m_ImageLayout; - VkDeviceMemory m_ImageMemory; - VkSampler m_Sampler; - - VkDescriptorSetLayout m_DescriptorSetLayout; - VkDescriptorImageInfo m_DescriptorImageInfo; - VkDescriptorSet m_DescriptorSet; - VkDescriptorPool m_DescriptorPool; - VkRenderPass m_RenderPass; - LogicalDevice* m_Device; - VkDescriptorBufferInfo m_UniformBufferDescriptor; - - VkDeviceSize m_ImageSize; - VkDeviceSize m_LayerSize; - VkFormat m_Format; - uint32 m_NumChannels; - uint32 m_MipLevels; - size_t m_numsFrameInFlight; - - UboSkyboxVS m_UboVS; - std::shared_ptr m_Cube; - GraphicsPipeline* m_GraphicsPipeline; - - Guid m_FragShader; - Guid m_VertexShader; + public: + static std::shared_ptr Create(Guid t_ID); + + explicit Cubemap(Guid t_ID); + virtual ~Cubemap(); + + FORCEINLINE uint32 GetFaceSize() const { return m_FaceSize; } + + /** True when the faces loaded and the GPU image exists */ + FORCEINLINE bool IsValid() const { return m_ImageView != VK_NULL_HANDLE; } + + FORCEINLINE const VkDescriptorImageInfo* GetDescriptorInfo() const { return &m_ImageInfo; } + + /** Release the Vulkan resources of this cube map */ + void Release(); + + private: + /** Load the six faces and upload them to one cube compatible image with six layers */ + void LoadVulkanImage(); + + void CreateImageView(); + + void CreateSampler(); + + /** Width and height of each face, in pixels */ + uint32 m_FaceSize = 0; + + VkImage m_Image = VK_NULL_HANDLE; + + VkDeviceMemory m_ImageMemory = VK_NULL_HANDLE; + + VkImageView m_ImageView = VK_NULL_HANDLE; + + VkSampler m_Sampler = VK_NULL_HANDLE; + + VkDescriptorImageInfo m_ImageInfo{}; }; -} \ No newline at end of file +} // namespace Fling diff --git a/FlingEngine/Graphics/src/RenderPipeline.cpp b/FlingEngine/Graphics/src/RenderPipeline.cpp index 746f0a0c..23c18da1 100644 --- a/FlingEngine/Graphics/src/RenderPipeline.cpp +++ b/FlingEngine/Graphics/src/RenderPipeline.cpp @@ -106,7 +106,8 @@ namespace Fling poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = SwapImageCount; + // Each subpass may allocate one set for each swap chain image + poolInfo.maxSets = SwapImageCount * static_cast(m_Subpasses.size()); VK_CHECK_RESULT(vkCreateDescriptorPool(m_Device->GetVkDevice(), &poolInfo, nullptr, &m_DescriptorPool)); diff --git a/FlingEngine/Graphics/src/SkyboxSubpass.cpp b/FlingEngine/Graphics/src/SkyboxSubpass.cpp new file mode 100644 index 00000000..1ac9bdbf --- /dev/null +++ b/FlingEngine/Graphics/src/SkyboxSubpass.cpp @@ -0,0 +1,135 @@ +#include "SkyboxSubpass.h" +#include "Buffer.h" +#include "CommandBuffer.h" +#include "Cubemap.h" +#include "FirstPersonCamera.h" +#include "GraphicsHelpers.h" +#include "GraphicsPipeline.h" +#include "LogicalDevice.h" +#include "SwapChain.h" +#include "VulkanApp.h" +#include "VulkanAppState.h" + +namespace Fling +{ + SkyboxSubpass::SkyboxSubpass(const LogicalDevice* t_Dev, + const Swapchain* t_Swap, + VkRenderPass t_GlobalRenderPass, + FirstPersonCamera* t_Cam, + std::shared_ptr t_Cubemap, + std::shared_ptr t_Vert, + std::shared_ptr t_Frag) + : Subpass(t_Dev, t_Swap, t_Vert, t_Frag), + m_GlobalRenderPass(t_GlobalRenderPass), + m_Camera(t_Cam), + m_Cubemap(std::move(t_Cubemap)) + { + assert(m_GlobalRenderPass != VK_NULL_HANDLE && m_Camera && m_Cubemap && m_Cubemap->IsValid()); + + const VkDeviceSize BufferSize = sizeof(SkyboxUbo); + m_UboBuffers.resize(m_SwapChain->GetImageCount()); + for (std::unique_ptr& Buf : m_UboBuffers) + { + Buf = std::make_unique(BufferSize, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + Buf->MapMemory(BufferSize); + } + } + + SkyboxSubpass::~SkyboxSubpass() = default; + + void + SkyboxSubpass::Draw(CommandBuffer& t_CmdBuf, uint32 t_ActiveFrameInFlight, entt::registry& t_reg, float DeltaTime) + { + // Invert the projection Y to match the Vulkan clip space, like the other passes + glm::mat4 Projection = m_Camera->GetProjectionMatrix(); + Projection[1][1] *= -1.0f; + + // Only the camera rotation moves the sky + const glm::mat4 ViewRotation = glm::mat4(glm::mat3(m_Camera->GetViewMatrix())); + + SkyboxUbo Ubo; + Ubo.InvViewProjection = glm::inverse(Projection * ViewRotation); + memcpy(m_UboBuffers[t_ActiveFrameInFlight]->m_MappedMem, &Ubo, sizeof(Ubo)); + + vkCmdBindDescriptorSets(t_CmdBuf.GetHandle(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + m_GraphicsPipeline->GetPipelineLayout(), + 0, + 1, + &m_DescriptorSets[t_ActiveFrameInFlight], + 0, + nullptr); + + vkCmdBindPipeline(t_CmdBuf.GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, m_GraphicsPipeline->GetPipeline()); + + // One full screen triangle. The vertex shader makes the positions from gl_VertexIndex. + vkCmdDraw(t_CmdBuf.GetHandle(), 3, 1, 0, 0); + } + + void SkyboxSubpass::CreateDescriptorSets(VkDescriptorPool t_Pool, entt::registry& t_reg) + { + // We only need to do the actual allocation of sets ONCE + if (m_DescPool == VK_NULL_HANDLE) + { + m_DescPool = t_Pool; + + const size_t ImageCount = m_SwapChain->GetImageCount(); + m_DescriptorSets.resize(ImageCount); + + std::vector Layouts(ImageCount, m_GraphicsPipeline->GetDescriptorSetLayout()); + VkDescriptorSetAllocateInfo AllocInfo = {}; + AllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + AllocInfo.descriptorPool = t_Pool; + AllocInfo.descriptorSetCount = static_cast(ImageCount); + AllocInfo.pSetLayouts = Layouts.data(); + + VK_CHECK_RESULT(vkAllocateDescriptorSets(m_Device->GetVkDevice(), &AllocInfo, m_DescriptorSets.data())); + } + + for (size_t i = 0; i < m_DescriptorSets.size(); ++i) + { + // The cube map info is const, but the Vulkan write struct wants a mutable pointer + VkDescriptorImageInfo CubemapInfo = *m_Cubemap->GetDescriptorInfo(); + + std::vector WriteDescriptorSets = { + // 0 : Camera UBO to the fragment shader + Initializers::WriteDescriptorSetUniform(m_UboBuffers[i].get(), m_DescriptorSets[i], 0), + // 1 : Cube map sampler + Initializers::WriteDescriptorSet( + m_DescriptorSets[i], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &CubemapInfo), + }; + + vkUpdateDescriptorSets(m_Device->GetVkDevice(), + static_cast(WriteDescriptorSets.size()), + WriteDescriptorSets.data(), + 0, + nullptr); + } + } + + void SkyboxSubpass::CreateGraphicsPipeline() + { + // The full screen triangle covers every pixel, so cull nothing and ignore depth + m_GraphicsPipeline->m_RasterizationState = Initializers::PipelineRasterizationStateCreateInfo( + VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); + + m_GraphicsPipeline->m_DepthStencilState.depthTestEnable = VK_FALSE; + m_GraphicsPipeline->m_DepthStencilState.depthWriteEnable = VK_FALSE; + + m_GraphicsPipeline->CreateGraphicsPipeline(m_GlobalRenderPass, nullptr); + } + + void SkyboxSubpass::OnSwapchainResized(entt::registry& t_reg) + { + m_GlobalRenderPass = VulkanApp::Get().GetState().m_RenderPass; + CreateGraphicsPipeline(); + + if (m_DescPool != VK_NULL_HANDLE) + { + CreateDescriptorSets(m_DescPool, t_reg); + } + } +} // namespace Fling diff --git a/FlingEngine/Graphics/src/SkyboxSubpass.h b/FlingEngine/Graphics/src/SkyboxSubpass.h new file mode 100644 index 00000000..6620ec64 --- /dev/null +++ b/FlingEngine/Graphics/src/SkyboxSubpass.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Subpass.h" +#include "FlingMath.h" +#include +#include + +namespace Fling +{ + class Buffer; + class CommandBuffer; + class Cubemap; + class FirstPersonCamera; + class LogicalDevice; + class Swapchain; + + /** + * Draws a cube map behind the scene. It draws one full screen triangle with no depth test + * in the global render pass, before the deferred composite. The composite discards pixels + * that have no geometry, so the sky shows through there. + * + * @see GeometrySubpass + */ + class SkyboxSubpass : public Subpass + { + public: + SkyboxSubpass(const LogicalDevice* t_Dev, + const Swapchain* t_Swap, + VkRenderPass t_GlobalRenderPass, + FirstPersonCamera* t_Cam, + std::shared_ptr t_Cubemap, + std::shared_ptr t_Vert, + std::shared_ptr t_Frag); + + virtual ~SkyboxSubpass(); + + void + Draw(CommandBuffer& t_CmdBuf, uint32 t_ActiveFrameInFlight, entt::registry& t_reg, float DeltaTime) override; + + void CreateDescriptorSets(VkDescriptorPool t_Pool, entt::registry& t_reg) override; + + void CreateGraphicsPipeline() override; + + void OnSwapchainResized(entt::registry& t_reg) override; + + private: + /** Matches the UBO at binding 0 of skybox.frag */ + struct SkyboxUbo + { + /** Inverse of projection * view, with the camera translation removed */ + glm::mat4 InvViewProjection; + }; + + VkRenderPass m_GlobalRenderPass = VK_NULL_HANDLE; + + const FirstPersonCamera* m_Camera = nullptr; + + std::shared_ptr m_Cubemap; + + /** One UBO for each swap chain image */ + std::vector> m_UboBuffers; + + std::vector m_DescriptorSets; + + VkDescriptorPool m_DescPool = VK_NULL_HANDLE; + }; +} // namespace Fling diff --git a/FlingEngine/Graphics/src/UniformBufferObject.h b/FlingEngine/Graphics/src/UniformBufferObject.h index 60b6a0f6..982c9f83 100644 --- a/FlingEngine/Graphics/src/UniformBufferObject.h +++ b/FlingEngine/Graphics/src/UniformBufferObject.h @@ -16,10 +16,4 @@ namespace Fling // The world position of this object glm::vec3 ObjPos; }; - - struct UboSkyboxVS - { - glm::mat4 Projection; - glm::mat4 ModelView; - }; } // namespace Fling \ No newline at end of file diff --git a/FlingEngine/Graphics/src/VulkanApp.cpp b/FlingEngine/Graphics/src/VulkanApp.cpp index b8fd926d..1ff54d0d 100644 --- a/FlingEngine/Graphics/src/VulkanApp.cpp +++ b/FlingEngine/Graphics/src/VulkanApp.cpp @@ -3,6 +3,7 @@ #include "RenderPipeline.h" #include "GeometrySubpass.h" +#include "SkyboxSubpass.h" #include "OffscreenSubpass.h" #include "ImGuiSubpass.h" #include "DebugSubpass.h" @@ -17,6 +18,7 @@ #include "FirstPersonCamera.h" #include "GraphicsHelpers.h" #include "DepthBuffer.h" +#include "Cubemap.h" #include "Logger.h" #include "Version.h" #include "BuildConfig.h" @@ -420,6 +422,28 @@ namespace Fling assert(Offscreen); FrameBuffer* OffscreenBuf = Offscreen->GetOffscreenFrameBuffer(); assert(OffscreenBuf); + + // Skybox pass ------ + // Draws before the geometry pass, which discards the pixels that have no geometry + if (t_Conf & PipelineFlags::CUBEMAP) + { + const std::string SkyboxPath = FlingConfig::GetString("Graphics", "Skybox", "Textures/Skybox/Garden"); + std::shared_ptr Sky = SkyboxPath.empty() ? nullptr : Cubemap::Create(HS(SkyboxPath.c_str())); + if (Sky && Sky->IsValid()) + { + F_LOG_TRACE("Add skybox {} to the DEFERRED render pipeline", SkyboxPath); + std::shared_ptr SkyboxVert = + Shader::Create(HS("skybox/skybox.vert.spv"), m_LogicalDevice); + std::shared_ptr SkyboxFrag = + Shader::Create(HS("skybox/skybox.frag.spv"), m_LogicalDevice); + Subpasses.emplace_back(std::make_unique( + m_LogicalDevice, m_SwapChain, m_State->m_RenderPass, m_Camera, Sky, SkyboxVert, SkyboxFrag)); + } + else if (!SkyboxPath.empty()) + { + F_LOG_ERROR("Skybox {} did not load, so the DEFERRED render pipeline has no skybox", SkyboxPath); + } + } std::shared_ptr GeomVert = Shader::Create(HS("Deferred/deferred.vert.spv"), m_LogicalDevice); std::shared_ptr GeomFrag = Shader::Create(HS("Deferred/deferred.frag.spv"), m_LogicalDevice); Subpasses.emplace_back(std::make_unique(m_LogicalDevice, @@ -439,12 +463,6 @@ namespace Fling F_LOG_WARN("Bulid REFLECTIONS render pipeline! (NOT YET IMPL)"); } - if (t_Conf & PipelineFlags::CUBEMAP) - { - F_LOG_WARN("Build CUBEMAP render pipeline!"); - // Add a cubemap - } - /*if (t_Conf & PipelineFlags::DEBUG) { F_LOG_WARN("Build DEBUG render pipeline!"); diff --git a/FlingEngine/Graphics/src/VulkanFunctions.inl b/FlingEngine/Graphics/src/VulkanFunctions.inl index a00ee373..fedc9158 100644 --- a/FlingEngine/Graphics/src/VulkanFunctions.inl +++ b/FlingEngine/Graphics/src/VulkanFunctions.inl @@ -83,6 +83,7 @@ FLING_VK_DEVICE(vkCmdBindVertexBuffers) FLING_VK_DEVICE(vkCmdBlitImage) FLING_VK_DEVICE(vkCmdCopyBuffer) FLING_VK_DEVICE(vkCmdCopyBufferToImage) +FLING_VK_DEVICE(vkCmdDraw) FLING_VK_DEVICE(vkCmdDrawIndexed) FLING_VK_DEVICE(vkCmdEndRenderPass) FLING_VK_DEVICE(vkCmdNextSubpass) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index dc07a869..8c6954d9 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -40,6 +40,16 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. ``` +## Garden skybox by Humus + +The six face images in +[`Assets/Textures/Skybox/Garden/`](Assets/Textures/Skybox/Garden/) are the work +of Emil Persson, aka [Humus](http://www.humus.name). The default skybox uses them +(see the `[Graphics] Skybox` value in `Config/EngineConf.ini`). They are +licensed under the +[Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). +The author's `readme.txt` is in the same folder. + ## Libraries in `external/` These are git submodules. Each one keeps its own license file, listed here.