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>
449 lines
23 KiB
C++
449 lines
23 KiB
C++
// VulkanWindow: texture decode/upload, samplers per D3D8 sampler state, texture descriptor sets and eviction.
|
|
#include "vulkan_window.h"
|
|
|
|
#include "draw_capture.h"
|
|
#include "host_util.h"
|
|
#include "texture_decode.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
namespace mt_host {
|
|
|
|
VkDescriptorSet VulkanWindow::allocate_texture_descriptor_set(VkImageView view0, VkImageView view1,
|
|
VkSampler sampler0,
|
|
VkSampler sampler1) {
|
|
VkDescriptorSet set = VK_NULL_HANDLE;
|
|
VkDescriptorSetAllocateInfo desc_alloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
|
desc_alloc.descriptorPool = descriptor_pool_;
|
|
desc_alloc.descriptorSetCount = 1;
|
|
desc_alloc.pSetLayouts = &descriptor_layout_;
|
|
check(vkAllocateDescriptorSets(device_, &desc_alloc, &set), "vkAllocateDescriptorSets");
|
|
|
|
VkDescriptorImageInfo image_infos[2]{};
|
|
image_infos[0].sampler = sampler0 ? sampler0 : sampler_;
|
|
image_infos[0].imageView = view0;
|
|
image_infos[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
image_infos[1].sampler = sampler1 ? sampler1 : (clamp_sampler_ ? clamp_sampler_ : sampler_);
|
|
image_infos[1].imageView = view1;
|
|
image_infos[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
VkWriteDescriptorSet writes[2]{};
|
|
for (int b = 0; b < 2; ++b) {
|
|
writes[b].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
writes[b].dstSet = set;
|
|
writes[b].dstBinding = static_cast<std::uint32_t>(b);
|
|
writes[b].descriptorCount = 1;
|
|
writes[b].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
writes[b].pImageInfo = &image_infos[b];
|
|
}
|
|
vkUpdateDescriptorSets(device_, 2, writes, 0, nullptr);
|
|
return set;
|
|
}
|
|
|
|
VkDescriptorSet VulkanWindow::allocate_ui_texture_descriptor_set(VkImageView view0, VkImageView view1, bool point) {
|
|
VkDescriptorSet set = VK_NULL_HANDLE;
|
|
VkDescriptorSetAllocateInfo desc_alloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
|
desc_alloc.descriptorPool = descriptor_pool_;
|
|
desc_alloc.descriptorSetCount = 1;
|
|
desc_alloc.pSetLayouts = &descriptor_layout_;
|
|
check(vkAllocateDescriptorSets(device_, &desc_alloc, &set), "vkAllocateDescriptorSets ui");
|
|
|
|
VkDescriptorImageInfo image_infos[2]{};
|
|
image_infos[0].sampler = point ? ui_point_sampler_ : (ui_sampler_ ? ui_sampler_ : sampler_);
|
|
image_infos[0].imageView = view0;
|
|
image_infos[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
image_infos[1].sampler = point ? ui_point_sampler_ : (ui_sampler_ ? ui_sampler_ : sampler_);
|
|
image_infos[1].imageView = view1;
|
|
image_infos[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
VkWriteDescriptorSet writes[2]{};
|
|
for (int b = 0; b < 2; ++b) {
|
|
writes[b].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
writes[b].dstSet = set;
|
|
writes[b].dstBinding = static_cast<std::uint32_t>(b);
|
|
writes[b].descriptorCount = 1;
|
|
writes[b].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
writes[b].pImageInfo = &image_infos[b];
|
|
}
|
|
vkUpdateDescriptorSets(device_, 2, writes, 0, nullptr);
|
|
return set;
|
|
}
|
|
|
|
VulkanWindow::GpuTexture& VulkanWindow::get_gpu_texture(
|
|
const std::string& texture_name,
|
|
const std::unordered_map<std::string, std::vector<std::uint8_t>>& capture_textures) {
|
|
if (texture_name.empty()) return fallback_texture_;
|
|
if (texture_name.rfind("rt:", 0) == 0) {
|
|
RenderTarget* target = get_render_target(texture_name);
|
|
return target ? target->texture : fallback_texture_;
|
|
}
|
|
auto [it, inserted] = textures_.try_emplace(texture_name);
|
|
it->second.last_used_frame = frame_number_;
|
|
if (!inserted && (it->second.view || frame_number_ - it->second.last_decode_attempt_frame < 60))
|
|
return it->second;
|
|
if (inserted) {
|
|
it->second.descriptor = fallback_texture_.descriptor;
|
|
it->second.ui_descriptor = fallback_texture_.ui_descriptor;
|
|
}
|
|
it->second.last_decode_attempt_frame = frame_number_;
|
|
|
|
mtimage::Image decoded;
|
|
if (auto raw = capture_textures.find(texture_name); raw != capture_textures.end() && !raw->second.empty()) {
|
|
decoded = decode_texture_bytes(raw->second.data(), raw->second.size());
|
|
}
|
|
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
|
if (!decoded.ok()) {
|
|
if (texture_name.rfind("mem:", 0) == 0) {
|
|
UIMemoryTexture mem_tex;
|
|
if (UIRenderMemoryTexture(texture_name, &mem_tex) && mem_tex.width > 0 && mem_tex.height > 0) {
|
|
const auto mtra = native_draw_capture::encode_raw_argb_as_mtra(
|
|
static_cast<std::uint32_t>(mem_tex.width),
|
|
static_cast<std::uint32_t>(mem_tex.height),
|
|
mem_tex.argb.data());
|
|
decoded = decode_texture_bytes(mtra.data(), mtra.size());
|
|
}
|
|
} else {
|
|
// PORT: the phone magnifies the 600-high 40250 UI about 1.8x, so the 1x window art and icons
|
|
// look soft. A UI texture found in hd_ui.pak (scripts/build_hd_ui.py) is uploaded at 2x in
|
|
// place of the pack file. The engine keeps the 1x size from its own decode for layout and
|
|
// quads, and the UVs are normalized, so only the texel density changes.
|
|
std::vector<std::uint8_t> pack_bytes;
|
|
if (read_hd_ui_texture(texture_name, pack_bytes))
|
|
decoded = decode_texture_bytes(pack_bytes.data(), pack_bytes.size());
|
|
if (!decoded.ok() && read_live_pack_texture(texture_name, pack_bytes))
|
|
decoded = decode_texture_bytes(pack_bytes.data(), pack_bytes.size());
|
|
}
|
|
}
|
|
#endif
|
|
if (!decoded.ok()) return it->second;
|
|
// 40250 CGraphicImageTexture: DXT DDS -> CreateDDSTexture with the file's levels;
|
|
// other files -> D3DXCreateTextureFromFileInMemoryEx(D3DX_DEFAULT) = full chain;
|
|
// memory images (mem:, CreateTexture(w, h, 1, ...)) -> one level.
|
|
const bool is_memory_tex = texture_name.rfind("mem:", 0) == 0;
|
|
last_texture_upload_name_ = texture_name;
|
|
upload_texture(
|
|
decoded.w, decoded.h, decoded.rgba.data(), it->second, true,
|
|
!is_memory_tex && decoded.file_mip_levels == 0, &decoded.mips);
|
|
it->second.descriptor = allocate_texture_descriptor_set(it->second.view, fallback_texture_.view);
|
|
it->second.ui_descriptor = allocate_ui_texture_descriptor_set(it->second.view, fallback_texture_.view);
|
|
return it->second;
|
|
}
|
|
|
|
std::uint64_t VulkanWindow::sampler_state_key(const Render3DDraw& draw, int stage) const {
|
|
return (std::uint64_t(draw.address_u[stage] & 15u)) |
|
|
(std::uint64_t(draw.address_v[stage] & 15u) << 4) |
|
|
(std::uint64_t(draw.min_filter[stage] & 15u) << 8) |
|
|
(std::uint64_t(draw.mag_filter[stage] & 15u) << 12) |
|
|
(std::uint64_t(draw.mip_filter[stage] & 15u) << 16);
|
|
}
|
|
|
|
VkSampler VulkanWindow::sampler_for_draw(const Render3DDraw& draw, int stage) {
|
|
const auto key = sampler_state_key(draw, stage);
|
|
if (key == 0) return stage == 0 ? sampler_ : clamp_sampler_;
|
|
if (auto it = state_samplers_.find(key); it != state_samplers_.end()) return it->second;
|
|
auto address_mode = [](std::uint32_t mode) {
|
|
switch (mode) {
|
|
case 2: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
|
case 3: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
case 4: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
default: return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
}
|
|
};
|
|
VkSamplerCreateInfo info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
|
info.addressModeU = address_mode(draw.address_u[stage]);
|
|
info.addressModeV = address_mode(draw.address_v[stage]);
|
|
info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
|
info.minFilter = draw.min_filter[stage] == 1 ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
|
info.magFilter = draw.mag_filter[stage] == 1 ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
|
info.mipmapMode = draw.mip_filter[stage] == 1 ? VK_SAMPLER_MIPMAP_MODE_NEAREST : VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
info.minLod = 0.0f;
|
|
info.maxLod = draw.mip_filter[stage] == 0 ? 0.0f : VK_LOD_CLAMP_NONE;
|
|
if ((draw.min_filter[stage] == 3 || draw.mag_filter[stage] == 3) && max_anisotropy_ > 1.0f) {
|
|
info.anisotropyEnable = VK_TRUE;
|
|
info.maxAnisotropy = max_anisotropy_;
|
|
}
|
|
VkSampler sampler = VK_NULL_HANDLE;
|
|
check(vkCreateSampler(device_, &info, nullptr, &sampler), "vkCreateSampler D3D state");
|
|
state_samplers_.emplace(key, sampler);
|
|
return sampler;
|
|
}
|
|
|
|
VkDescriptorSet VulkanWindow::get_texture_descriptor(
|
|
const std::string& texture0_name,
|
|
const std::string& mask_name,
|
|
const std::unordered_map<std::string, std::vector<std::uint8_t>>& capture_textures,
|
|
const Render3DDraw& draw) {
|
|
const auto& tex0 = get_gpu_texture(texture0_name, capture_textures);
|
|
const auto& tex1 = mask_name.empty() ? fallback_texture_ : get_gpu_texture(mask_name, capture_textures);
|
|
const std::string pair_key = "3d\n" + texture0_name + "\n" + mask_name + "\n" +
|
|
std::to_string(sampler_state_key(draw, 0)) + "\n" + std::to_string(sampler_state_key(draw, 1));
|
|
if (auto it = paired_descriptors_.find(pair_key); it != paired_descriptors_.end()) {
|
|
it->second.last_used_frame = frame_number_;
|
|
return it->second.set;
|
|
}
|
|
const VkDescriptorSet set = allocate_texture_descriptor_set(
|
|
tex0.view ? tex0.view : fallback_texture_.view,
|
|
tex1.view ? tex1.view : fallback_texture_.view,
|
|
sampler_for_draw(draw, 0), sampler_for_draw(draw, 1));
|
|
paired_descriptors_.emplace(pair_key, PairedDescriptor{set, frame_number_});
|
|
return set;
|
|
}
|
|
|
|
VkDescriptorSet VulkanWindow::get_ui_texture_descriptor(
|
|
const std::string& texture0_name,
|
|
const std::string& mask_name,
|
|
const std::unordered_map<std::string, std::vector<std::uint8_t>>& capture_textures, bool point) {
|
|
auto& tex0 = get_gpu_texture(texture0_name, capture_textures);
|
|
if (point && mask_name.empty() && tex0.view) {
|
|
if (!tex0.ui_point_descriptor)
|
|
tex0.ui_point_descriptor = allocate_ui_texture_descriptor_set(tex0.view, fallback_texture_.view, true);
|
|
return tex0.ui_point_descriptor;
|
|
}
|
|
if (mask_name.empty()) return tex0.ui_descriptor ? tex0.ui_descriptor : tex0.descriptor;
|
|
const auto& tex1 = get_gpu_texture(mask_name, capture_textures);
|
|
if (!tex0.view || !tex1.view) return tex0.ui_descriptor ? tex0.ui_descriptor : tex0.descriptor;
|
|
const std::string pair_key = "ui\n" + texture0_name + "\n" + mask_name;
|
|
if (auto it = paired_descriptors_.find(pair_key); it != paired_descriptors_.end()) {
|
|
it->second.last_used_frame = frame_number_;
|
|
return it->second.set;
|
|
}
|
|
const VkDescriptorSet set = allocate_ui_texture_descriptor_set(
|
|
tex0.view ? tex0.view : fallback_texture_.view,
|
|
tex1.view ? tex1.view : fallback_texture_.view);
|
|
paired_descriptors_.emplace(pair_key, PairedDescriptor{set, frame_number_});
|
|
return set;
|
|
}
|
|
|
|
void VulkanWindow::upload_texture(
|
|
std::uint32_t width,
|
|
std::uint32_t height,
|
|
const std::uint8_t* rgba,
|
|
GpuTexture& texture,
|
|
bool count_upload,
|
|
bool generate_mips,
|
|
const std::vector<std::vector<std::uint8_t>>* file_mips) {
|
|
const ScopedMs timer(timings_.texture_upload_ms);
|
|
texture.width = width;
|
|
texture.height = height;
|
|
const bool has_file_mips = !generate_mips && file_mips && !file_mips->empty();
|
|
VkDeviceSize byte_size = VkDeviceSize(width) * height * 4;
|
|
if (has_file_mips)
|
|
for (const auto& level : *file_mips) byte_size += level.size();
|
|
const std::uint32_t mip_levels = generate_mips
|
|
? (static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1u)
|
|
: has_file_mips ? 1u + static_cast<std::uint32_t>(file_mips->size()) : 1u;
|
|
// Inside a frame the copy is recorded ahead of that frame's render pass and reads the slot's
|
|
// staging ring; the GPU runs it with the frame, no queue wait. Outside a frame (the fallback
|
|
// texture at startup) it is a one-off submit that waits.
|
|
const bool in_frame = f_->recording;
|
|
VkBuffer staging_buffer = VK_NULL_HANDLE;
|
|
VkDeviceMemory staging_memory = VK_NULL_HANDLE;
|
|
VkDeviceSize staging_offset = 0;
|
|
std::uint8_t* mapped = nullptr;
|
|
const VkDeviceSize aligned_offset =
|
|
(f_->staging_used + staging_alignment_ - 1) / staging_alignment_ * staging_alignment_;
|
|
if (in_frame && f_->staging_mapped && aligned_offset + byte_size <= f_->staging_capacity) {
|
|
staging_buffer = f_->staging_buffer;
|
|
staging_offset = aligned_offset;
|
|
mapped = f_->staging_mapped + staging_offset;
|
|
f_->staging_used = aligned_offset + byte_size;
|
|
} else {
|
|
if (in_frame) f_->staging_wanted = std::max(f_->staging_wanted, aligned_offset + byte_size);
|
|
void* raw = nullptr;
|
|
create_host_buffer(byte_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging_buffer, staging_memory, &raw);
|
|
mapped = static_cast<std::uint8_t*>(raw);
|
|
}
|
|
std::memcpy(mapped, rgba, VkDeviceSize(width) * height * 4);
|
|
if (has_file_mips) {
|
|
auto* dst = mapped + VkDeviceSize(width) * height * 4;
|
|
for (const auto& level : *file_mips) { std::memcpy(dst, level.data(), level.size()); dst += level.size(); }
|
|
}
|
|
if (staging_memory) vkUnmapMemory(device_, staging_memory);
|
|
|
|
VkImageCreateInfo image_info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
image_info.extent = {width, height, 1};
|
|
image_info.mipLevels = mip_levels;
|
|
image_info.arrayLayers = 1;
|
|
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
image_info.usage =
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
check(vkCreateImage(device_, &image_info, nullptr, &texture.image), "vkCreateImage texture");
|
|
VkMemoryRequirements image_reqs{};
|
|
vkGetImageMemoryRequirements(device_, texture.image, &image_reqs);
|
|
VkMemoryAllocateInfo image_alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
image_alloc.allocationSize = image_reqs.size;
|
|
image_alloc.memoryTypeIndex = find_memory_type(image_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
check(vkAllocateMemory(device_, &image_alloc, nullptr, &texture.memory), "vkAllocateMemory texture");
|
|
check(vkBindImageMemory(device_, texture.image, texture.memory, 0), "vkBindImageMemory texture");
|
|
|
|
VkCommandBuffer upload_cmd = f_->command;
|
|
if (!in_frame) {
|
|
VkCommandBufferAllocateInfo cmd_alloc{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
cmd_alloc.commandPool = pool_; cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmd_alloc.commandBufferCount = 1;
|
|
check(vkAllocateCommandBuffers(device_, &cmd_alloc, &upload_cmd), "vkAllocateCommandBuffers texture");
|
|
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
check(vkBeginCommandBuffer(upload_cmd, &begin), "vkBeginCommandBuffer texture");
|
|
}
|
|
|
|
VkImageMemoryBarrier to_transfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
to_transfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
to_transfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
to_transfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
to_transfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
to_transfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
to_transfer.image = texture.image;
|
|
to_transfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, mip_levels, 0, 1};
|
|
vkCmdPipelineBarrier(
|
|
upload_cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
0, 0, nullptr, 0, nullptr, 1, &to_transfer);
|
|
|
|
std::vector<VkBufferImageCopy> regions(has_file_mips ? mip_levels : 1u);
|
|
VkDeviceSize region_offset = 0;
|
|
for (std::uint32_t i = 0; i < regions.size(); ++i) {
|
|
const std::uint32_t lw = std::max(1u, width >> i), lh = std::max(1u, height >> i);
|
|
regions[i].bufferOffset = staging_offset + region_offset;
|
|
regions[i].imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, i, 0, 1};
|
|
regions[i].imageExtent = {lw, lh, 1};
|
|
region_offset += VkDeviceSize(lw) * lh * 4;
|
|
}
|
|
vkCmdCopyBufferToImage(
|
|
upload_cmd, staging_buffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
static_cast<std::uint32_t>(regions.size()), regions.data());
|
|
|
|
std::int32_t mip_w = static_cast<std::int32_t>(width);
|
|
std::int32_t mip_h = static_cast<std::int32_t>(height);
|
|
for (std::uint32_t i = 1; generate_mips && i < mip_levels; ++i) {
|
|
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
barrier.image = texture.image;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, i - 1, 1, 0, 1};
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
vkCmdPipelineBarrier(
|
|
upload_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
0, 0, nullptr, 0, nullptr, 1, &barrier);
|
|
|
|
const std::int32_t next_w = std::max(1, mip_w / 2);
|
|
const std::int32_t next_h = std::max(1, mip_h / 2);
|
|
VkImageBlit blit{};
|
|
blit.srcOffsets[0] = {0, 0, 0};
|
|
blit.srcOffsets[1] = {mip_w, mip_h, 1};
|
|
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, i - 1, 0, 1};
|
|
blit.dstOffsets[0] = {0, 0, 0};
|
|
blit.dstOffsets[1] = {next_w, next_h, 1};
|
|
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, i, 0, 1};
|
|
vkCmdBlitImage(
|
|
upload_cmd,
|
|
texture.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
1, &blit, VK_FILTER_LINEAR);
|
|
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
vkCmdPipelineBarrier(
|
|
upload_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
0, 0, nullptr, 0, nullptr, 1, &barrier);
|
|
|
|
mip_w = next_w;
|
|
mip_h = next_h;
|
|
}
|
|
|
|
VkImageMemoryBarrier to_shader{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
to_shader.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
to_shader.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
to_shader.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
to_shader.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
to_shader.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
to_shader.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
to_shader.image = texture.image;
|
|
to_shader.subresourceRange = generate_mips
|
|
? VkImageSubresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, mip_levels - 1, 1, 0, 1}
|
|
: VkImageSubresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, mip_levels, 0, 1};
|
|
vkCmdPipelineBarrier(
|
|
upload_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
0, 0, nullptr, 0, nullptr, 1, &to_shader);
|
|
|
|
if (in_frame) {
|
|
if (staging_memory) f_->retired_buffers.emplace_back(staging_buffer, staging_memory);
|
|
} else {
|
|
check(vkEndCommandBuffer(upload_cmd), "vkEndCommandBuffer texture");
|
|
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
submit.commandBufferCount = 1; submit.pCommandBuffers = &upload_cmd;
|
|
check(vkQueueSubmit(queue_, 1, &submit, VK_NULL_HANDLE), "vkQueueSubmit texture");
|
|
check(vkQueueWaitIdle(queue_), "vkQueueWaitIdle texture");
|
|
vkFreeCommandBuffers(device_, pool_, 1, &upload_cmd);
|
|
vkDestroyBuffer(device_, staging_buffer, nullptr);
|
|
vkFreeMemory(device_, staging_memory, nullptr);
|
|
}
|
|
|
|
VkImageViewCreateInfo view_info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
view_info.image = texture.image; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
view_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, mip_levels, 0, 1};
|
|
check(vkCreateImageView(device_, &view_info, nullptr, &texture.view), "vkCreateImageView texture");
|
|
|
|
if (count_upload) {
|
|
++texture_upload_count_;
|
|
texture_uploaded_bytes_ += byte_size;
|
|
}
|
|
}
|
|
|
|
void VulkanWindow::release_texture(GpuTexture& texture) {
|
|
for (VkDescriptorSet set : {texture.descriptor, texture.ui_descriptor, texture.ui_point_descriptor})
|
|
if (set && set != fallback_texture_.descriptor && set != fallback_texture_.ui_descriptor)
|
|
vkFreeDescriptorSets(device_, descriptor_pool_, 1, &set);
|
|
if (texture.view) vkDestroyImageView(device_, texture.view, nullptr);
|
|
if (texture.image) vkDestroyImage(device_, texture.image, nullptr);
|
|
if (texture.memory) vkFreeMemory(device_, texture.memory, nullptr);
|
|
texture = {};
|
|
}
|
|
|
|
void VulkanWindow::prune_textures() {
|
|
constexpr std::uint64_t static_retention_frames = 120;
|
|
constexpr std::uint64_t memory_retention_frames = 2;
|
|
for (auto it = paired_descriptors_.begin(); it != paired_descriptors_.end();) {
|
|
const auto retention = it->first.find("mem:") != std::string::npos
|
|
? memory_retention_frames : static_retention_frames;
|
|
if (frame_number_ - it->second.last_used_frame > retention) {
|
|
vkFreeDescriptorSets(device_, descriptor_pool_, 1, &it->second.set);
|
|
it = paired_descriptors_.erase(it);
|
|
} else ++it;
|
|
}
|
|
for (auto it = textures_.begin(); it != textures_.end();) {
|
|
const auto retention = it->first.rfind("mem:", 0) == 0
|
|
? memory_retention_frames : static_retention_frames;
|
|
if (frame_number_ - it->second.last_used_frame > retention) {
|
|
release_texture(it->second);
|
|
it = textures_.erase(it);
|
|
} else ++it;
|
|
}
|
|
// PORT: render targets too (a closed model preview); a later use recreates and clears one.
|
|
for (auto it = render_targets_.begin(); it != render_targets_.end();) {
|
|
if (frame_number_ - it->second.last_used_frame > static_retention_frames) {
|
|
for (auto pair = paired_descriptors_.begin(); pair != paired_descriptors_.end();) {
|
|
if (pair->first.find("\n" + it->first + "\n") != std::string::npos) {
|
|
vkFreeDescriptorSets(device_, descriptor_pool_, 1, &pair->second.set);
|
|
pair = paired_descriptors_.erase(pair);
|
|
} else ++pair;
|
|
}
|
|
release_render_target(it->second);
|
|
it = render_targets_.erase(it);
|
|
} else ++it;
|
|
}
|
|
}
|
|
|
|
} // namespace mt_host
|