Split vulkan_window.cpp by responsibility and render() into phases

VulkanWindow's 3000-line TU becomes sdl_window.cpp (window, events,
cursor, screenshot BMP), vk_device.cpp (device, swapchain, MSAA,
framebuffers), vk_pipelines.cpp (render passes, pipelines),
vk_resources.cpp (buffers, render targets, geometry), vk_textures.cpp
(textures, samplers, descriptors), vk_frame.cpp (render) and
ui_batches.cpp; vulkan_window.cpp keeps construction and timing stats.
Function bodies move verbatim.

render() now reads fence/acquire -> prepare_draws -> prepare_ui ->
write_draw_states -> record_passes -> record_readbacks -> submit ->
finish_readbacks -> present, with FrameDraws / Readback carrying the
locals that used to live across 500 lines.

Verified: login-screen screenshot byte-identical to HEAD; game-world
draw/vertex/index/UI counts identical; ctest, port_gate macos+android,
Android build pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-29 21:54:36 +09:00
co-authored by Claude Opus 5.5
parent eab5938072
commit 22d534c5eb
13 changed files with 2584 additions and 2429 deletions
+422
View File
@@ -0,0 +1,422 @@
// 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) {
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 = 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 = ui_sampler_ ? ui_sampler_ : (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;
}
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 {
std::vector<std::uint8_t> pack_bytes;
if (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) {
const auto& tex0 = get_gpu_texture(texture0_name, capture_textures);
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);
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})
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;
}
}
} // namespace mt_host