// VulkanWindow: per-frame host buffers (bones, fixed-function state, UI, staging), descriptor pool, offscreen render targets and geometry buffers. #include "vulkan_window.h" #include "host_util.h" #include #include #include namespace mt_host { void VulkanWindow::create_host_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer, VkDeviceMemory& memory, void** mapped) { VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; info.size = size; info.usage = usage; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; check(vkCreateBuffer(device_, &info, nullptr, &buffer), "vkCreateBuffer host"); VkMemoryRequirements reqs{}; vkGetBufferMemoryRequirements(device_, buffer, &reqs); VkMemoryAllocateInfo alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; alloc.allocationSize = reqs.size; alloc.memoryTypeIndex = find_memory_type( reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); check(vkAllocateMemory(device_, &alloc, nullptr, &memory), "vkAllocateMemory host"); check(vkBindBufferMemory(device_, buffer, memory, 0), "vkBindBufferMemory host"); check(vkMapMemory(device_, memory, 0, size, 0, mapped), "vkMapMemory host"); } void VulkanWindow::create_descriptors_and_buffers() { VkSamplerCreateInfo sampler_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; sampler_info.magFilter = VK_FILTER_LINEAR; sampler_info.minFilter = VK_FILTER_LINEAR; sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.minLod = 0.0f; sampler_info.maxLod = VK_LOD_CLAMP_NONE; if (max_anisotropy_ > 1.0f) { sampler_info.anisotropyEnable = VK_TRUE; sampler_info.maxAnisotropy = max_anisotropy_; } check(vkCreateSampler(device_, &sampler_info, nullptr, &sampler_), "vkCreateSampler"); VkSamplerCreateInfo clamp_info = sampler_info; clamp_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; clamp_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; clamp_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; check(vkCreateSampler(device_, &clamp_info, nullptr, &clamp_sampler_), "vkCreateSampler clamp"); VkSamplerCreateInfo ui_sampler_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; ui_sampler_info.magFilter = VK_FILTER_LINEAR; ui_sampler_info.minFilter = VK_FILTER_LINEAR; ui_sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; ui_sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; ui_sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; ui_sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; ui_sampler_info.minLod = 0.0f; ui_sampler_info.maxLod = 0.0f; ui_sampler_info.anisotropyEnable = VK_FALSE; ui_sampler_info.maxAnisotropy = 1.0f; check(vkCreateSampler(device_, &ui_sampler_info, nullptr, &ui_sampler_), "vkCreateSampler ui"); ui_sampler_info.magFilter = VK_FILTER_NEAREST; ui_sampler_info.minFilter = VK_FILTER_NEAREST; check(vkCreateSampler(device_, &ui_sampler_info, nullptr, &ui_point_sampler_), "vkCreateSampler ui point"); VkDescriptorSetLayoutBinding tex_bindings[2]{}; tex_bindings[0].binding = 0; tex_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; tex_bindings[0].descriptorCount = 1; tex_bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; tex_bindings[1].binding = 1; tex_bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; tex_bindings[1].descriptorCount = 1; tex_bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo layout_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; layout_info.bindingCount = 2; layout_info.pBindings = tex_bindings; check(vkCreateDescriptorSetLayout(device_, &layout_info, nullptr, &descriptor_layout_), "vkCreateDescriptorSetLayout tex"); VkDescriptorSetLayoutBinding bone_bindings[2]{}; bone_bindings[0].binding = 0; bone_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bone_bindings[0].descriptorCount = 1; bone_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; bone_bindings[1].binding = 1; // Draw state is constant across a draw. Uniform memory lets mobile GPUs broadcast // these values instead of fetching them as general storage data for each fragment. bone_bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; bone_bindings[1].descriptorCount = 1; bone_bindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo bone_layout_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; bone_layout_info.bindingCount = 2; bone_layout_info.pBindings = bone_bindings; check(vkCreateDescriptorSetLayout(device_, &bone_layout_info, nullptr, &bone_descriptor_layout_), "vkCreateDescriptorSetLayout bone"); VkDescriptorPoolSize pool_sizes[3] = { {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 16384}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4 * kMaxFramesInFlight}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 4 * kMaxFramesInFlight}}; VkDescriptorPoolCreateInfo pool_info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; pool_info.maxSets = 8192; pool_info.poolSizeCount = 3; pool_info.pPoolSizes = pool_sizes; check(vkCreateDescriptorPool(device_, &pool_info, nullptr, &descriptor_pool_), "vkCreateDescriptorPool"); const std::uint8_t white_pixel[4] = {255, 255, 255, 255}; upload_texture(1, 1, white_pixel, fallback_texture_, false, false); fallback_texture_.descriptor = allocate_texture_descriptor_set(fallback_texture_.view, fallback_texture_.view); fallback_texture_.ui_descriptor = allocate_ui_texture_descriptor_set(fallback_texture_.view, fallback_texture_.view); VkPhysicalDeviceProperties properties{}; vkGetPhysicalDeviceProperties(physical_, &properties); const auto alignment = std::max( 1, properties.limits.minUniformBufferOffsetAlignment); state_stride_ = ((sizeof(FixedFunctionState) + alignment - 1) / alignment) * alignment; staging_alignment_ = std::max(16, properties.limits.optimalBufferCopyOffsetAlignment); for (auto& slot : frames_) { void* bone_raw = nullptr; create_host_buffer(kBoneBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, slot.bone_buffer, slot.bone_memory, &bone_raw); slot.bone_mapped = static_cast(bone_raw); slot.bone_capacity = kMaxBonesPerFrame; for (std::size_t i = 0; i < 16; ++i) slot.bone_mapped[i] = kIdentityMatrix[i]; slot.state_capacity = 256; void* state_raw = nullptr; create_host_buffer(slot.state_capacity * state_stride_, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, slot.state_buffer, slot.state_memory, &state_raw); slot.state_mapped = static_cast(state_raw); VkDescriptorSetAllocateInfo bone_alloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; bone_alloc.descriptorPool = descriptor_pool_; bone_alloc.descriptorSetCount = 1; bone_alloc.pSetLayouts = &bone_descriptor_layout_; check(vkAllocateDescriptorSets(device_, &bone_alloc, &slot.bone_descriptor_set), "vkAllocateDescriptorSets bone"); VkDescriptorBufferInfo buffer_infos[2] = { {slot.bone_buffer, 0, kBoneBufferBytes}, {slot.state_buffer, 0, sizeof(FixedFunctionState)}}; VkWriteDescriptorSet writes[2]{}; for (int binding = 0; binding < 2; ++binding) { writes[binding].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writes[binding].dstSet = slot.bone_descriptor_set; writes[binding].dstBinding = static_cast(binding); writes[binding].descriptorCount = 1; writes[binding].descriptorType = binding == 0 ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; writes[binding].pBufferInfo = &buffer_infos[binding]; } vkUpdateDescriptorSets(device_, 2, writes, 0, nullptr); void* ui_raw = nullptr; create_host_buffer( kUiBufferBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, slot.ui_buffer, slot.ui_memory, &ui_raw); slot.ui_mapped = static_cast(ui_raw); slot.ui_vertex_capacity = kMaxUiVerticesPerFrame; slot.ui_index_capacity = kMaxUiIndicesPerFrame; slot.ui_vertex_bytes = kUiVertexBytes; slot.staging_wanted = kStagingRingBytes; } } void VulkanWindow::release_retired_buffers(FrameSlot& slot) { for (auto [buffer, memory] : slot.retired_buffers) { vkDestroyBuffer(device_, buffer, nullptr); vkFreeMemory(device_, memory, nullptr); } slot.retired_buffers.clear(); } void VulkanWindow::reset_staging(FrameSlot& slot) { release_retired_buffers(slot); slot.staging_used = 0; if (slot.staging_wanted <= slot.staging_capacity) return; if (slot.staging_mapped) vkUnmapMemory(device_, slot.staging_memory); if (slot.staging_buffer) vkDestroyBuffer(device_, slot.staging_buffer, nullptr); if (slot.staging_memory) vkFreeMemory(device_, slot.staging_memory, nullptr); void* raw = nullptr; slot.staging_capacity = std::min(slot.staging_wanted, kStagingRingMaxBytes); create_host_buffer(slot.staging_capacity, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, slot.staging_buffer, slot.staging_memory, &raw); slot.staging_mapped = static_cast(raw); slot.staging_wanted = slot.staging_capacity; } void VulkanWindow::ensure_bone_capacity(std::size_t required) { if (required <= f_->bone_capacity) return; VkPhysicalDeviceProperties properties{}; vkGetPhysicalDeviceProperties(physical_, &properties); const std::size_t max_bones = properties.limits.maxStorageBufferRange / (16 * sizeof(float)); if (required > max_bones || required > UINT32_MAX) throw std::runtime_error("bone palette exceeds device storage-buffer limit"); std::size_t next = std::min(max_bones, std::max(required, f_->bone_capacity * 2)); VkBuffer buffer = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; void* mapped = nullptr; create_host_buffer(next * 16 * sizeof(float), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, buffer, memory, &mapped); vkUnmapMemory(device_, f_->bone_memory); vkDestroyBuffer(device_, f_->bone_buffer, nullptr); vkFreeMemory(device_, f_->bone_memory, nullptr); f_->bone_buffer = buffer; f_->bone_memory = memory; f_->bone_mapped = static_cast(mapped); f_->bone_capacity = next; VkDescriptorBufferInfo info{f_->bone_buffer, 0, next * 16 * sizeof(float)}; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; write.dstSet = f_->bone_descriptor_set; write.dstBinding = 0; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; write.pBufferInfo = &info; vkUpdateDescriptorSets(device_, 1, &write, 0, nullptr); } void VulkanWindow::ensure_state_capacity(std::size_t required) { if (required <= f_->state_capacity) return; const auto next = std::max(required, f_->state_capacity * 2); if (next > UINT32_MAX / state_stride_) throw std::runtime_error("fixed-function state buffer exceeds dynamic offset range"); VkBuffer buffer = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; void* mapped = nullptr; create_host_buffer(next * state_stride_, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, buffer, memory, &mapped); vkUnmapMemory(device_, f_->state_memory); vkDestroyBuffer(device_, f_->state_buffer, nullptr); vkFreeMemory(device_, f_->state_memory, nullptr); f_->state_buffer = buffer; f_->state_memory = memory; f_->state_mapped = static_cast(mapped); f_->state_capacity = next; VkDescriptorBufferInfo info{f_->state_buffer, 0, sizeof(FixedFunctionState)}; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; write.dstSet = f_->bone_descriptor_set; write.dstBinding = 1; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; write.pBufferInfo = &info; vkUpdateDescriptorSets(device_, 1, &write, 0, nullptr); } void VulkanWindow::ensure_ui_capacity(std::size_t command_count) { if (command_count <= f_->ui_vertex_capacity / 4 && command_count <= f_->ui_index_capacity / 6) return; if (command_count > 100'000) throw std::runtime_error("UI command count exceeds safety limit"); const std::size_t quads = std::max(command_count, f_->ui_vertex_capacity / 2); const VkDeviceSize vertex_bytes = VkDeviceSize(quads) * 4 * sizeof(Vertex); const VkDeviceSize index_bytes = VkDeviceSize(quads) * 6 * sizeof(std::uint32_t); if (vertex_bytes + index_bytes > 128ull * 1024ull * 1024ull) throw std::runtime_error("UI geometry exceeds 128 MiB safety limit"); VkBuffer buffer = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; void* mapped = nullptr; create_host_buffer(vertex_bytes + index_bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, buffer, memory, &mapped); vkUnmapMemory(device_, f_->ui_memory); vkDestroyBuffer(device_, f_->ui_buffer, nullptr); vkFreeMemory(device_, f_->ui_memory, nullptr); f_->ui_buffer = buffer; f_->ui_memory = memory; f_->ui_mapped = static_cast(mapped); f_->ui_vertex_capacity = quads * 4; f_->ui_index_capacity = quads * 6; f_->ui_vertex_bytes = vertex_bytes; } VulkanWindow::RenderTarget* VulkanWindow::get_render_target(const std::string& name) { if (auto it = render_targets_.find(name); it != render_targets_.end()) { it->second.last_used_frame = frame_number_; return &it->second; } unsigned id = 0, w = 0, h = 0; int end = 0; if (std::sscanf(name.c_str(), "rt:%u:%ux%u%n", &id, &w, &h, &end) != 3 || !w || !h || w > 4096 || h > 4096) return nullptr; const bool color = name.compare(std::size_t(end), std::string::npos, ":c") == 0; if (!color && name[std::size_t(end)] != '\0') return nullptr; RenderTarget& target = render_targets_[name]; target.width = w; target.height = h; target.color = color; target.last_used_frame = frame_number_; auto make_image = [&](VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspect, VkImage& image, VkDeviceMemory& memory, VkImageView& view, bool opaque = false) { VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; info.imageType = VK_IMAGE_TYPE_2D; info.format = format; info.extent = {w, h, 1}; info.mipLevels = 1; info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = usage; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; check(vkCreateImage(device_, &info, nullptr, &image), "vkCreateImage render target"); VkMemoryRequirements reqs{}; vkGetImageMemoryRequirements(device_, image, &reqs); VkMemoryAllocateInfo alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; alloc.allocationSize = reqs.size; alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); check(vkAllocateMemory(device_, &alloc, nullptr, &memory), "vkAllocateMemory render target"); check(vkBindImageMemory(device_, image, memory, 0), "vkBindImageMemory render target"); VkImageViewCreateInfo view_info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; view_info.image = image; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_info.format = format; view_info.subresourceRange = {aspect, 0, 1, 0, 1}; // A colour target is D3DFMT_X8R8G8B8: sampled alpha is always 1. if (opaque) view_info.components.a = VK_COMPONENT_SWIZZLE_ONE; check(vkCreateImageView(device_, &view_info, nullptr, &view), "vkCreateImageView render target"); }; make_image(color ? VK_FORMAT_R8G8B8A8_UNORM : offscreen_format_, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT, target.texture.image, target.texture.memory, target.texture.view, color); make_image(VK_FORMAT_D32_SFLOAT, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, target.depth_image, target.depth_memory, target.depth_view); const VkImageView views[2] = {target.texture.view, target.depth_view}; VkFramebufferCreateInfo fb{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; fb.renderPass = color ? offscreen_color_pass_ : offscreen_pass_; fb.attachmentCount = 2; fb.pAttachments = views; fb.width = w; fb.height = h; fb.layers = 1; check(vkCreateFramebuffer(device_, &fb, nullptr, &target.framebuffer), "vkCreateFramebuffer render target"); target.texture.descriptor = allocate_texture_descriptor_set(target.texture.view, fallback_texture_.view); target.texture.ui_descriptor = allocate_ui_texture_descriptor_set(target.texture.view, fallback_texture_.view); return ⌖ } void VulkanWindow::release_render_target(RenderTarget& target) { if (target.framebuffer) vkDestroyFramebuffer(device_, target.framebuffer, nullptr); if (target.depth_view) vkDestroyImageView(device_, target.depth_view, nullptr); if (target.depth_image) vkDestroyImage(device_, target.depth_image, nullptr); if (target.depth_memory) vkFreeMemory(device_, target.depth_memory, nullptr); release_texture(target.texture); target = {}; } void VulkanWindow::initialize_render_target(RenderTarget& target) { VkImageMemoryBarrier barriers[2]{}; for (auto& b : barriers) { b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; } barriers[0].image = target.texture.image; barriers[0].subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; barriers[1].image = target.depth_image; barriers[1].subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; vkCmdPipelineBarrier(f_->command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 2, barriers); const float shade = target.color ? 0.0f : 1.0f; const VkClearColorValue white{{shade, shade, shade, 1.0f}}; const VkClearDepthStencilValue far{1.0f, 0}; vkCmdClearColorImage(f_->command, target.texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &white, 1, &barriers[0].subresourceRange); vkCmdClearDepthStencilImage(f_->command, target.depth_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &far, 1, &barriers[1].subresourceRange); for (auto& b : barriers) { b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; } barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; barriers[1].newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; barriers[1].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; vkCmdPipelineBarrier(f_->command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 2, barriers); target.initialized = true; } void VulkanWindow::release_geometry(Geometry& geometry) { if (geometry.buffer) vkDestroyBuffer(device_, geometry.buffer, nullptr); if (geometry.memory) vkFreeMemory(device_, geometry.memory, nullptr); geometry = {}; } void VulkanWindow::upload_geometry(const Render3DDraw& draw, Geometry& geometry) { const ScopedMs timer(timings_.geometry_upload_ms); if (draw.positions.size() % 3 || draw.indices.size() % (draw.lines ? 2 : 3)) throw std::runtime_error("invalid native geometry array size"); const auto count = draw.positions.size() / 3; if (count > UINT32_MAX || draw.indices.size() > UINT32_MAX) throw std::runtime_error("geometry exceeds Vulkan index range"); // D3D8 feeds opaque white for a vertex format without D3DFVF_DIFFUSE. const std::uint32_t default_argb = 0xffffffffu; const bool has_skin = draw.bone_indices.size() >= count * 4 && draw.bone_weights.size() >= count * 4; std::vector vertices(count); for (std::size_t i = 0; i < count; ++i) { vertices[i].position[0] = draw.positions[3*i]; vertices[i].position[1] = draw.positions[3*i+1]; vertices[i].position[2] = draw.positions[3*i+2]; vertices[i].rhw = i < draw.rhw.size() ? draw.rhw[i] : 1.0f; vertices[i].vertex_fog = i < draw.vertex_fog.size() ? draw.vertex_fog[i] : 1.0f; if (3*i + 2 < draw.normals.size()) { vertices[i].normal[0] = draw.normals[3*i]; vertices[i].normal[1] = draw.normals[3*i+1]; vertices[i].normal[2] = draw.normals[3*i+2]; } else { vertices[i].normal[0] = 0.0f; vertices[i].normal[1] = 0.0f; vertices[i].normal[2] = 1.0f; } if (2*i + 1 < draw.uv0.size()) { vertices[i].uv[0] = draw.uv0[2*i]; vertices[i].uv[1] = draw.uv0[2*i+1]; } else { vertices[i].uv[0] = 0.0f; vertices[i].uv[1] = 0.0f; } const auto argb = i < draw.diffuse.size() ? draw.diffuse[i] : default_argb; vertices[i].color[0] = float((argb >> 16) & 255) / 255.0f; vertices[i].color[1] = float((argb >> 8) & 255) / 255.0f; vertices[i].color[2] = float(argb & 255) / 255.0f; vertices[i].color[3] = float((argb >> 24) & 255) / 255.0f; if (has_skin) { for (int k = 0; k < 4; ++k) { vertices[i].joints[k] = draw.bone_indices[4*i + k]; vertices[i].weights[k] = draw.bone_weights[4*i + k]; } } else { vertices[i].joints[0] = vertices[i].joints[1] = vertices[i].joints[2] = vertices[i].joints[3] = 0; vertices[i].weights[0] = 1.0f; vertices[i].weights[1] = vertices[i].weights[2] = vertices[i].weights[3] = 0.0f; } if (2*i + 1 < draw.uv1.size()) { vertices[i].mask_uv[0] = draw.uv1[2*i]; vertices[i].mask_uv[1] = draw.uv1[2*i+1]; } else { vertices[i].mask_uv[0] = 0.0f; vertices[i].mask_uv[1] = 0.0f; } } for (auto index : draw.indices) if (index >= count) throw std::runtime_error("draw index exceeds vertex count"); geometry.index_offset = vertices.size() * sizeof(Vertex); const auto bytes = geometry.index_offset + draw.indices.size() * sizeof(std::uint32_t); if (bytes > 128 * 1024 * 1024) throw std::runtime_error("native geometry buffer exceeds 128 MiB"); VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; info.size = bytes; info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; check(vkCreateBuffer(device_, &info, nullptr, &geometry.buffer), "vkCreateBuffer"); VkMemoryRequirements requirements{}; vkGetBufferMemoryRequirements(device_, geometry.buffer, &requirements); VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = find_memory_type( requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); check(vkAllocateMemory(device_, &allocation, nullptr, &geometry.memory), "vkAllocateMemory"); check(vkBindBufferMemory(device_, geometry.buffer, geometry.memory, 0), "vkBindBufferMemory"); void* mapped = nullptr; check(vkMapMemory(device_, geometry.memory, 0, bytes, 0, &mapped), "vkMapMemory"); std::memcpy(mapped, vertices.data(), geometry.index_offset); std::memcpy(static_cast(mapped) + geometry.index_offset, draw.indices.data(), draw.indices.size() * sizeof(std::uint32_t)); vkUnmapMemory(device_, geometry.memory); geometry.vertex_count = static_cast(count); geometry.index_count = static_cast(draw.indices.size()); ++upload_count_; uploaded_bytes_ += bytes; } } // namespace mt_host