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>
470 lines
25 KiB
C++
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 ⌖
|
|
}
|
|
|
|
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
|