Fixed Civilization VII - added TIR mixed samples support

This commit is contained in:
PavelBARABANOV
2026-10-22 00:01:15 +03:00
parent aee3785897
commit 720c7d227c
4 changed files with 185 additions and 7 deletions
@@ -129,7 +129,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
topology.Assign(can_collapse_topology_class
? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast<size_t>(topology_)]
: topology_);
msaa_mode.Assign(regs.anti_alias_samples_mode);
// Target-independent rasterization rasterizes (and depth tests) at the raster sample count
if (regs.tir_mode == Maxwell::TIRMode::RasterNTargetM &&
regs.anti_alias_raster != Maxwell::AntiAliasRaster::Mode1x1) {
msaa_mode.Assign(static_cast<Tegra::Texture::MsaaMode>(regs.anti_alias_raster));
} else {
msaa_mode.Assign(regs.anti_alias_samples_mode);
}
attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0);
raw2 = 0;
@@ -1089,6 +1089,58 @@ VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, Vk
return *shadow.view;
}
VkImageView TextureCacheRuntime::GetOrCreateTirColorScratch(VkImage color_image, VkFormat format,
VkExtent2D extent, u32 layers,
VkSampleCountFlagBits samples) {
TirColorScratch& scratch = tir_color_scratch[color_image];
if (scratch.image && scratch.format == format && scratch.extent.width == extent.width &&
scratch.extent.height == extent.height && scratch.layers == layers &&
scratch.samples == samples) {
return *scratch.view;
}
if (scratch.image) {
scheduler.Finish();
}
scratch.image = memory_allocator.CreateImage(VkImageCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {extent.width, extent.height, 1},
.mipLevels = 1,
.arrayLayers = layers,
.samples = samples,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
});
scratch.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = *scratch.image,
.viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.components{},
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = layers,
},
});
scratch.format = format;
scratch.extent = extent;
scratch.layers = layers;
scratch.samples = samples;
return *scratch.view;
}
const TextureCacheRuntime::ResolveShadow* TextureCacheRuntime::GetValidResolveShadow(
VkImage msaa_image) const {
const auto it = resolve_shadows.find(msaa_image);
@@ -2970,6 +3022,26 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, ImageView* color_buffer,
Framebuffer::~Framebuffer() = default;
/// Single-sampled color targets with a multisampled depth buffer, as used by target-independent
/// rasterization. Vulkan requires matching sample counts for these attachments.
static bool IsMixedSamplesTarget(std::span<ImageView*, NUM_RT> color_buffers,
const ImageView* depth_buffer) {
if (depth_buffer == nullptr || depth_buffer->Samples() == VK_SAMPLE_COUNT_1_BIT) {
return false;
}
bool has_color = false;
for (const ImageView* const color_buffer : color_buffers) {
if (color_buffer == nullptr) {
continue;
}
if (color_buffer->Samples() != VK_SAMPLE_COUNT_1_BIT) {
return false;
}
has_color = true;
}
return has_color;
}
void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
ImageView* depth_buffer, bool is_rescaled_) {
@@ -3024,6 +3096,13 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
} else {
renderpass_key.depth_format = PixelFormat::Invalid;
}
if (IsMixedSamplesTarget(color_buffers, depth_buffer)) {
const VkExtent2D extent{(std::min)(render_area.width, width),
(std::min)(render_area.height, height)};
CreateMixedSamplesFramebuffer(runtime, color_buffers, attachments, renderpass_key,
static_cast<u32>((std::max)(num_layers, 1)), extent);
return;
}
renderpass_key.samples = samples;
const bool do_resolve_color = ENABLE_MSAA_TILER_RESOLVE &&
samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler();
@@ -3040,10 +3119,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
discard_msaa_depth_stencil = ENABLE_MSAA_RESOLVE_CONSUME &&
ENABLE_MSAA_DEPTH_STENCIL_DISCARD && do_resolve_depth_stencil;
renderpass = runtime.render_pass_cache.Get(renderpass_key);
render_pass_key = renderpass_key;
render_pass_cache = &runtime.render_pass_cache;
runtime_ptr = &runtime;
UseRenderPass(runtime, renderpass_key);
render_area.width = (std::min)(render_area.width, width);
render_area.height = (std::min)(render_area.height, height);
@@ -3075,6 +3151,53 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
}
num_color_buffers = static_cast<u32>(num_colors);
CreateVkFramebuffer(runtime, attachments, static_cast<u32>((std::max)(num_layers, 1)));
}
void Framebuffer::CreateMixedSamplesFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
std::span<const VkImageView> attachments,
RenderPassKey renderpass_key, u32 layers,
VkExtent2D extent) {
// attachments holds the real color targets followed by the depth buffer
const size_t num_colors = attachments.size() - 1;
boost::container::small_vector<VkImageView, NUM_RT * 2 + 2> mixed_attachments;
for (const ImageView* const color_buffer : color_buffers) {
if (!color_buffer) {
continue;
}
const VkFormat vk_format = MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal,
true, color_buffer->format)
.format;
mixed_attachments.push_back(runtime.GetOrCreateTirColorScratch(
color_buffer->ImageHandle(), vk_format, extent, layers, samples));
}
mixed_attachments.push_back(attachments[num_colors]);
for (size_t index = 0; index < num_colors; ++index) {
mixed_attachments.push_back(attachments[index]);
}
// Multisampled scratch color + depth, resolving color into the real single-sampled targets
renderpass_key.samples = samples;
renderpass_key.resolve_color = true;
renderpass_key.resolve_depth_stencil = false;
discard_msaa_color = false;
discard_msaa_depth_stencil = false;
UseRenderPass(runtime, renderpass_key);
render_area = extent;
num_color_buffers = static_cast<u32>(num_colors);
CreateVkFramebuffer(runtime, mixed_attachments, layers);
}
void Framebuffer::UseRenderPass(TextureCacheRuntime& runtime,
const RenderPassKey& renderpass_key) {
renderpass = runtime.render_pass_cache.Get(renderpass_key);
render_pass_key = renderpass_key;
render_pass_cache = &runtime.render_pass_cache;
runtime_ptr = &runtime;
}
void Framebuffer::CreateVkFramebuffer(TextureCacheRuntime& runtime,
std::span<const VkImageView> attachments, u32 layers) {
framebuffer = runtime.device.GetLogical().CreateFramebuffer({
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
@@ -3084,7 +3207,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
.pAttachments = attachments.data(),
.width = render_area.width,
.height = render_area.height,
.layers = static_cast<u32>((std::max)(num_layers, 1)),
.layers = layers,
});
}
@@ -133,6 +133,11 @@ public:
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
/// Multisampled stand-in for a single-sampled color target rendered with a TIR depth buffer
[[nodiscard]] VkImageView GetOrCreateTirColorScratch(VkImage color_image, VkFormat format,
VkExtent2D extent, u32 layers,
VkSampleCountFlagBits samples);
void InvalidateResolveShadow(VkImage msaa_image);
void MarkResolveShadowUpToDate(VkImage msaa_image);
@@ -187,6 +192,15 @@ public:
std::vector<MsaaScratchImage> msaa_scratch_images;
::Common::unordered_map<VkImage, ResolveShadow> resolve_shadows;
struct TirColorScratch {
vk::Image image;
vk::ImageView view;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{};
u32 layers = 0;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
};
::Common::unordered_map<VkImage, TirColorScratch> tir_color_scratch;
std::vector<std::pair<u64, ResolveShadow>> pending_resolve_shadows;
};
@@ -279,6 +293,22 @@ public:
void MarkResolveShadowsUpToDate() const;
private:
/// Builds a framebuffer for single-sampled color targets drawn with a multisampled depth
/// buffer (target-independent rasterization). Color is rendered into multisampled scratch
/// images that resolve into the real targets at the end of the pass.
void CreateMixedSamplesFramebuffer(TextureCacheRuntime& runtime,
std::span<ImageView*, NUM_RT> color_buffers,
std::span<const VkImageView> attachments,
RenderPassKey renderpass_key, u32 layers,
VkExtent2D extent);
/// Fetches the render pass for the key from the cache and remembers it for variants.
void UseRenderPass(TextureCacheRuntime& runtime, const RenderPassKey& renderpass_key);
/// Creates the Vulkan framebuffer for the current render pass and render area.
void CreateVkFramebuffer(TextureCacheRuntime& runtime,
std::span<const VkImageView> attachments, u32 layers);
static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8;
vk::Framebuffer framebuffer;
+20 -1
View File
@@ -1928,7 +1928,26 @@ ImageViewId TextureCache<P>::FindDepthBuffer() {
if (gpu_addr == 0) {
return ImageViewId{};
}
const ImageInfo info(regs.zeta, regs.zeta_size, regs.anti_alias_samples_mode);
// Target-independent rasterization: the depth buffer keeps the raster sample count while
// the color targets use anti_alias_samples_mode.
Tegra::Texture::MsaaMode zeta_msaa_mode = regs.anti_alias_samples_mode;
if (regs.anti_alias_raster != Tegra::Engines::Maxwell3D::Regs::AntiAliasRaster::Mode1x1) {
const auto raster_mode = static_cast<Tegra::Texture::MsaaMode>(regs.anti_alias_raster);
bool use_raster = regs.tir_mode == Tegra::Engines::Maxwell3D::Regs::TIRMode::RasterNTargetM;
if (!use_raster && regs.anti_alias_samples_mode == Tegra::Texture::MsaaMode::Msaa1x1) {
// Clears can be issued before tir_mode is enabled. Only a depth buffer sized for the
// raster samples relative to the color target is treated as multisampled, otherwise
// it ping-pongs between a 1x and a multisampled image every frame.
const auto& rt0 = regs.rt[0];
use_raster = rt0.Address() != 0 && rt0.format != Tegra::RenderTargetFormat::NONE &&
regs.zeta_size.width == rt0.width * NumSamplesX(raster_mode) &&
regs.zeta_size.height == rt0.height * NumSamplesY(raster_mode);
}
if (use_raster) {
zeta_msaa_mode = raster_mode;
}
}
const ImageInfo info(regs.zeta, regs.zeta_size, zeta_msaa_mode);
return FindRenderTargetView(info, gpu_addr);
}