/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "rendering/vulkan14/vulkan14terrain.h" #include "rendering/vulkan14/vulkan14commands.h" #include "rendering/vulkan14/vulkan14device.h" #include "utilities/Logs.h" namespace vk14 { namespace { std::uint32_t mip_count(std::uint32_t const Size) { return static_cast(std::floor(std::log2(std::max(Size, 1u)))) + 1; } } // namespace terrain_materials::~terrain_materials() { destroy(); } void terrain_materials::init(resources const &Resources, copy_draw Draw) { m_resources = &Resources; m_draw = std::move(Draw); } void terrain_materials::destroy() { if (m_resources == nullptr) { return; } auto const device{m_resources->gpu->handle()}; for (auto &images : m_images) { for (auto *view : images.layers) { vkDestroyImageView(device, view, nullptr); } images = array_images{}; } m_resources = nullptr; } bool terrain_materials::gpu_init() { return (m_resources != nullptr) && bool(m_draw); } vulkan14_texture &terrain_materials::texture(texture_handle const Handle) { return static_cast(m_textures.texture(Handle)); } std::shared_ptr terrain_materials::make_image(VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, std::uint32_t const Mips, char const *Name) { image_desc desc; desc.format = Format; desc.extent = {Width, Height, 1}; desc.mips = Mips; desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; auto result{std::make_shared()}; if (false == result->create(*m_resources->memory, desc, Name)) { ErrorLog(std::string("heightmap terrain: no gpu image for ") + Name); return nullptr; } return result; } void terrain_materials::upload(texture_handle const Texture, VkFormat const Format, std::uint32_t const Width, std::uint32_t const Height, void const *Data, VkDeviceSize const Size, bool const Mips, VkSampler const Sampler) { auto &target{texture(Texture)}; auto map{target.adopted()}; auto const generate{Mips && m_resources->uploads->can_blit(Format)}; auto const mips{generate ? mip_count(std::max(Width, Height)) : 1u}; if ((map == nullptr) || (map->desc().format != Format) || (map->desc().extent.width != Width) || (map->desc().extent.height != Height) || (map->desc().mips != mips)) { map = make_image(Format, Width, Height, mips, target.name.c_str()); if (map == nullptr) { return; } } // the frames sent before are done with the content the upload replaces before it's written (the barrier of the upload) if (m_resources->uploads->upload(map, Data, Size, {{0, {Width, Height, 1}}}, generate)) { target.adopt(map, Sampler); } } void terrain_materials::gpu_white(texture_source &Texture) { std::uint8_t const texel[4]{255, 255, 255, 255}; auto &target{static_cast(Texture)}; auto map{make_image(VK_FORMAT_R8G8B8A8_UNORM, 1, 1, 1, target.name.c_str())}; if ((map != nullptr) && m_resources->uploads->upload(map, texel, sizeof(texel), {{0, {1, 1, 1}}}, false)) { target.adopt(map, m_resources->sampling->repeat_nearest); } } void terrain_materials::gpu_storage(texture_array const &Array, int const Layers, bool const Fresh) { auto const index{static_cast(&Array - arrays().data())}; auto &images{m_images[index]}; let_go(images); image_desc desc; desc.format = (Array.srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM); desc.extent = {static_cast(Array.size), static_cast(Array.size), 1}; desc.mips = mip_count(static_cast(Array.size)); desc.layers = static_cast(Layers); desc.view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; auto &target{texture(Array.handle)}; auto map{std::make_shared()}; if (false == map->create(*m_resources->memory, desc, target.name)) { ErrorLog("heightmap terrain: no gpu image for the layers of \"" + target.name + "\""); return; } auto const device{m_resources->gpu->handle()}; for (int layer = 0; layer < Layers; ++layer) { VkImageViewCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = map->handle(); info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = desc.format; info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, static_cast(layer), 1}; VkImageView view{VK_NULL_HANDLE}; if (vkCreateImageView(device, &info, nullptr, &view) != VK_SUCCESS) { ErrorLog("heightmap terrain: no view of a layer of \"" + target.name + "\""); images.layers.clear(); break; } images.layers.push_back(view); } images.map = map; // cleared by the next frame, before anything reads it images.clear = true; target.adopt(map, m_resources->sampling->repeat_aniso); } void terrain_materials::let_go(array_images &Images) { if (Images.layers.empty() && (Images.map == nullptr)) { return; } // the frames in flight may still read them m_resources->deletions->push(m_resources->uploads->current_value(), [device = m_resources->gpu->handle(), views = std::move(Images.layers), map = std::move(Images.map)]() { for (auto *view : views) { vkDestroyImageView(device, view, nullptr); } }); Images = array_images{}; } void terrain_materials::gpu_blank(slot const &Slot) { std::uint8_t const blank[4]{0, 0, 0, 0}; for (auto const handle : Slot.weights) { upload(handle, VK_FORMAT_R8G8B8A8_UNORM, 1, 1, blank, sizeof(blank), true, m_resources->sampling->clamp_linear); } std::array const zeros{}; upload(Slot.table, VK_FORMAT_R32G32B32A32_SFLOAT, static_cast(max_layers), 2, zeros.data(), sizeof(zeros), false, m_resources->sampling->clamp_nearest); // led between the samples, so the edges of the holes run smoothly across the cells std::uint8_t const none{0}; upload(Slot.holes, VK_FORMAT_R8_UNORM, 1, 1, &none, 1, false, m_resources->sampling->clamp_linear); } void terrain_materials::gpu_weights(texture_handle const Texture, int const Side, std::uint8_t const *Texels) { auto const side{static_cast(Side)}; upload(Texture, VK_FORMAT_R8G8B8A8_UNORM, side, side, Texels, static_cast(side) * side * 4, true, m_resources->sampling->clamp_linear); } void terrain_materials::gpu_table(texture_handle const Texture, float const *Table) { upload(Texture, VK_FORMAT_R32G32B32A32_SFLOAT, static_cast(max_layers), 2, Table, max_layers * 2 * 4 * sizeof(float), false, m_resources->sampling->clamp_nearest); } void terrain_materials::gpu_holes(texture_handle const Texture, int const Side, std::uint8_t const *Samples) { auto const side{static_cast(Side)}; upload(Texture, VK_FORMAT_R8_UNORM, side, side, Samples, static_cast(side) * side, false, m_resources->sampling->clamp_linear); } void terrain_materials::begin_frame(command_stream *const Commands, std::uint64_t const Value) { m_commands = Commands; m_value = Value; for (auto &images : m_images) { if ((false == images.clear) || (images.map == nullptr)) { continue; } images.clear = false; // the layers not copied yet read black, as the storage of opengl starts out on most drivers transition(Commands, *images.map, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT); VkClearColorValue const zero{}; VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}; Commands->clear_color_image(images.map->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &range); transition(Commands, *images.map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); } } void terrain_materials::gpu_begin_copies() { for (auto &images : m_images) { images.drawn = false; } } void terrain_materials::gpu_copy(texture_array const &Array, int const Layer, texture_source *Source, std::array const &Default) { auto const index{static_cast(&Array - arrays().data())}; auto &images{m_images[index]}; if ((m_commands == nullptr) || (images.map == nullptr) || (static_cast(Layer) >= images.layers.size())) { return; } if (false == images.drawn) { images.drawn = true; transition(m_commands, *images.map, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); } auto const view{images.layers[Layer]}; auto const format{images.map->desc().format}; VkExtent2D const extent{images.map->desc().extent.width, images.map->desc().extent.height}; if (Source != nullptr) { auto const *entry{m_resources->images->find(static_cast(*Source).get_id())}; if (entry != nullptr) { m_draw(m_commands, view, format, extent, entry->target->view(), entry->sampler); return; } } // a map the layer doesn't have; one which failed reads as the texture 0 of opengl VkRenderingAttachmentInfo attachment{}; attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; attachment.imageView = view; attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; if (Source == nullptr) { std::copy(Default.begin(), Default.end(), attachment.clearValue.color.float32); } else { attachment.clearValue.color = VkClearColorValue{{0.f, 0.f, 0.f, 1.f}}; } VkRenderingInfo rendering{}; rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; rendering.renderArea = {{0, 0}, extent}; rendering.layerCount = 1; rendering.colorAttachmentCount = 1; rendering.pColorAttachments = &attachment; m_commands->begin_rendering(&rendering); m_commands->end_rendering(); } void terrain_materials::gpu_end_copies(bool const Copied) { for (auto &images : m_images) { if ((false == images.drawn) || (images.map == nullptr)) { continue; } images.drawn = false; if (Copied && (images.map->desc().mips > 1) && m_resources->uploads->can_blit(images.map->desc().format)) { make_mipmaps(*images.map); } else { transition(m_commands, *images.map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); } } } // the other levels of every layer made by blits, each from the one before it (glGenerateMipmap of the opengl renderer) void terrain_materials::make_mipmaps(image &Map) { auto const commands{m_commands}; auto const layers{Map.desc().layers}; auto const mips{Map.desc().mips}; auto const extent{Map.desc().extent}; auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) { VkImageMemoryBarrier2 entry{}; entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; entry.srcStageMask = SrcStage; entry.srcAccessMask = SrcAccess; entry.dstStageMask = DstStage; entry.dstAccessMask = DstAccess; entry.oldLayout = From; entry.newLayout = To; entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; entry.image = Map.handle(); entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, Count, 0, layers}; VkDependencyInfo dependency{}; dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependency.imageMemoryBarrierCount = 1; dependency.pImageMemoryBarriers = &entry; commands->pipeline_barrier2(&dependency); }; // the first level drawn, the others written by the blits barrier(0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT); barrier(1, mips - 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT); for (std::uint32_t level = 1; level < mips; ++level) { if (level > 1) { barrier(level - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT); } VkImageBlit2 blit{}; blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2; blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, layers}; blit.srcOffsets[1] = {static_cast(std::max(extent.width >> (level - 1), 1u)), static_cast(std::max(extent.height >> (level - 1), 1u)), 1}; blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, layers}; blit.dstOffsets[1] = {static_cast(std::max(extent.width >> level, 1u)), static_cast(std::max(extent.height >> level, 1u)), 1}; VkBlitImageInfo2 info{}; info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2; info.srcImage = Map.handle(); info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; info.dstImage = Map.handle(); info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; info.regionCount = 1; info.pRegions = &blit; info.filter = VK_FILTER_LINEAR; commands->blit_image2(&info); } barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); Map.state = image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT}; } } // namespace vk14