Files
metin2-client/src/host/vk_resources.cpp
T
shenleiandClaude Opus 5.5 84404cf1f1 Model preview: AVA2 render-target window on a Vulkan colour target
Port of AVA2's CRenderTarget / GrpRenderTargetTexture / renderTarget module.
The host gains an RGBA8 offscreen colour pass for D3D colour render targets;
mt_preview shows wearable items on every allowed race and monsters with
motions, reached by X+click in the inventory and shop and from the wiki.
Player races are created as TYPE_PC so their parts exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 04:48:07 +09:00

470 lines
25 KiB
C++

// 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 <algorithm>
#include <cstring>
#include <stdexcept>
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<VkDeviceSize>(
1, properties.limits.minUniformBufferOffsetAlignment);
state_stride_ = ((sizeof(FixedFunctionState) + alignment - 1) / alignment) * alignment;
staging_alignment_ = std::max<VkDeviceSize>(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<float*>(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<std::uint8_t*>(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<std::uint32_t>(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<std::uint8_t*>(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<std::uint8_t*>(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<float*>(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<std::uint8_t*>(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<std::uint8_t*>(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 &target;
}
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<Vertex> 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<std::uint8_t*>(mapped) + geometry.index_offset,
draw.indices.data(), draw.indices.size() * sizeof(std::uint32_t));
vkUnmapMemory(device_, geometry.memory);
geometry.vertex_count = static_cast<std::uint32_t>(count);
geometry.index_count = static_cast<std::uint32_t>(draw.indices.size());
++upload_count_;
uploaded_bytes_ += bytes;
}
} // namespace mt_host