[vulkan] 3rd Vulkan Global Maintenance (#4189)

Following the philosophy on the previous Vulkan maintenance PR's, this one is reviewing the video_core to resolve VUID's related to the missing handling/ safechecks (UBO's case on missing StorageBufferAccess 8/16 bits on QCOM drivers, resulting on bad compute pipeline compiled), wiring topology representative to EDS2 configuration (following #4117), refactored the sampled image access via new memeber function for type "typeless" which adds a handling on the integer mistmatch (uint to sint, float to uint) + fixing bugs on current texture sampling (int) including the depth/stencil path resolve, maintenance to previous changes on query cache (resolving bugs from #3853), reduced the amount of binding BindVertexBufferEXT2 when a new tick/ frame is presented (with this being a reason for performance reduction on games where vertex polutes the stage, like BOTW/TOTK where grass is draw with vertex); implemented color border swizzle and color write enable for future improvements on EDS3, added initial implementation on Synchronization2 starting a path to ensure an access to VK 1.3 features safely and other smaller fixes along the road.

Special Thanks:

-> Mr. Smolio Gidolard (@gidoly)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4189
This commit is contained in:
CamilleLaVey
2026-07-09 04:22:43 +02:00
committed by crueter
parent 41762940d6
commit 8225151a44
34 changed files with 739 additions and 133 deletions
@@ -39,6 +39,55 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
POLYGON, // Patches
};
constexpr std::array TOPOLOGY_CLASS_REPRESENTATIVE_LUT = {
Maxwell::PrimitiveTopology::Points, // Points
Maxwell::PrimitiveTopology::Lines, // Lines
Maxwell::PrimitiveTopology::LineLoop, // LineLoop
Maxwell::PrimitiveTopology::LineStrip, // LineStrip
Maxwell::PrimitiveTopology::Triangles, // Triangles
Maxwell::PrimitiveTopology::Triangles, // TriangleStrip
Maxwell::PrimitiveTopology::Triangles, // TriangleFan
Maxwell::PrimitiveTopology::Triangles, // Quads
Maxwell::PrimitiveTopology::Triangles, // QuadStrip
Maxwell::PrimitiveTopology::Triangles, // Polygon
Maxwell::PrimitiveTopology::LinesAdjacency, // LinesAdjacency
Maxwell::PrimitiveTopology::LinesAdjacency, // LineStripAdjacency
Maxwell::PrimitiveTopology::TrianglesAdjacency, // TrianglesAdjacency
Maxwell::PrimitiveTopology::TrianglesAdjacency, // TriangleStripAdjacency
Maxwell::PrimitiveTopology::Patches, // Patches
};
bool IsDualSourceBlendFactor(Maxwell::Blend::Factor factor) {
using F = Maxwell::Blend::Factor;
switch (factor) {
case F::Source1Color_D3D:
case F::OneMinusSource1Color_D3D:
case F::Source1Alpha_D3D:
case F::OneMinusSource1Alpha_D3D:
case F::Source1Color_GL:
case F::OneMinusSource1Color_GL:
case F::Source1Alpha_GL:
case F::OneMinusSource1Alpha_GL:
return true;
default:
return false;
}
}
bool ComputeAttachment0DualSourceBlend(const Maxwell& regs) {
if (!regs.blend.enable[0]) {
return false;
}
const auto uses_dual_source = [](const auto& blend) {
return IsDualSourceBlendFactor(blend.color_source) ||
IsDualSourceBlendFactor(blend.color_dest) ||
IsDualSourceBlendFactor(blend.alpha_source) ||
IsDualSourceBlendFactor(blend.alpha_dest);
};
return regs.blend_per_target_enabled ? uses_dual_source(regs.blend_per_target[0])
: uses_dual_source(regs.blend);
}
void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) {
std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(),
[](const auto& layout) {
@@ -65,6 +114,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0);
extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0);
extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0);
color_write_enable_dynamic.Assign(features.has_color_write_enable ? 1 : 0);
dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0);
xfb_enabled.Assign(regs.transform_feedback_enabled != 0);
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
@@ -74,8 +124,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() ==
Maxwell::Tessellation::OutputPrimitives::Triangles_CW);
patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
topology.Assign(topology_);
const bool can_collapse_topology_class =
features.has_extended_dynamic_state && features.has_extended_dynamic_state_2;
topology.Assign(can_collapse_topology_class
? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast<size_t>(topology_)]
: topology_);
msaa_mode.Assign(regs.anti_alias_samples_mode);
attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0);
raw2 = 0;
@@ -190,6 +245,15 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
maxwell3d.dirty.flags[Dirty::Blending] = false;
for (size_t index = 0; index < attachments.size(); ++index) {
attachments[index].Refresh(regs, index);
auto& attachment = attachments[index];
if (color_write_enable_dynamic && attachment.mask_r == 0 &&
attachment.mask_g == 0 && attachment.mask_b == 0 &&
attachment.mask_a == 0) {
attachment.mask_r.Assign(1);
attachment.mask_g.Assign(1);
attachment.mask_b.Assign(1);
attachment.mask_a.Assign(1);
}
}
}
}
@@ -33,6 +33,7 @@ struct DynamicFeatures {
bool has_dynamic_state3_logic_op_enable;
bool has_dynamic_state3_line_stipple_enable;
bool has_dynamic_vertex_input;
bool has_color_write_enable;
bool has_provoking_vertex;
bool has_provoking_vertex_first_mode;
bool has_provoking_vertex_last_mode;
@@ -210,6 +211,9 @@ struct FixedPipelineState {
BitField<12, 2, u32> tessellation_spacing;
BitField<14, 1, u32> tessellation_clockwise;
BitField<15, 5, u32> patch_control_points_minus_one;
BitField<20, 1, u32> color_write_enable_dynamic;
BitField<21, 1, u32> attachment0_dual_source_blend;
BitField<24, 4, Maxwell::PrimitiveTopology> topology;
BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode;
@@ -223,6 +223,10 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_UNORM_BLOCK, 0, EAC_R11_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_SNORM_BLOCK, 0, EAC_R11_SNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_UNORM_BLOCK, 0, EAC_R11G11_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_SNORM_BLOCK, 0, EAC_R11G11_SNORM) \
/* Depth formats */ \
SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \
@@ -278,7 +282,17 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
}
} else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) {
// Transcode on hardware that doesn't support ETC2 natively
tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
if (pixel_format == PixelFormat::EAC_R11_SNORM) {
tuple.format = VK_FORMAT_R8_SNORM;
} else if (pixel_format == PixelFormat::EAC_R11_UNORM) {
tuple.format = VK_FORMAT_R8_UNORM;
} else if (pixel_format == PixelFormat::EAC_R11G11_SNORM) {
tuple.format = VK_FORMAT_R8G8_SNORM;
} else if (pixel_format == PixelFormat::EAC_R11G11_UNORM) {
tuple.format = VK_FORMAT_R8G8_UNORM;
} else {
tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
}
}
bool const attachable = (tuple.usage & usage_attachable) != 0;
bool const storage = (tuple.usage & usage_storage) != 0;
@@ -7,6 +7,7 @@
#pragma once
#include <cstddef>
#include <optional>
#include <boost/container/small_vector.hpp>
@@ -15,6 +16,7 @@
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/types.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -22,6 +24,29 @@ namespace Vulkan {
using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS;
[[nodiscard]] inline std::optional<PixelFormat> PixelFormatFromImageFormat(
Shader::ImageFormat format) {
switch (format) {
case Shader::ImageFormat::Typeless:
return std::nullopt;
case Shader::ImageFormat::R8_UINT:
return PixelFormat::R8_UINT;
case Shader::ImageFormat::R8_SINT:
return PixelFormat::R8_SINT;
case Shader::ImageFormat::R16_UINT:
return PixelFormat::R16_UINT;
case Shader::ImageFormat::R16_SINT:
return PixelFormat::R16_SINT;
case Shader::ImageFormat::R32_UINT:
return PixelFormat::R32_UINT;
case Shader::ImageFormat::R32G32_UINT:
return PixelFormat::R32G32_UINT;
case Shader::ImageFormat::R32G32B32A32_UINT:
return PixelFormat::R32G32B32A32_UINT;
}
return std::nullopt;
}
[[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) {
return Shader::NumDescriptors(info.constant_buffer_descriptors) +
Shader::NumDescriptors(info.storage_buffers_descriptors) +
@@ -211,8 +236,12 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()};
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
if (sampler.HasLinearFiltering() &&
VideoCore::Surface::IsPixelFormatInteger(image_view.format)) {
vk_sampler = sampler.HandleWithNearestFilter();
}
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
is_rescaled |= element_rescaled;
@@ -84,7 +84,7 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
} // Anonymous namespace
Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params)
: VideoCommon::BufferBase(null_params), tracker{4096} {
: VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096} {
if (runtime.device.HasNullDescriptor()) {
return;
}
@@ -95,12 +95,18 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device},
scheduler{&runtime.scheduler},
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} {
if (runtime.device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
}
}
void Buffer::MarkUsage(u64 offset, u64 size) noexcept {
tracker.Track(offset, size);
last_usage_tick = scheduler->CurrentTick();
}
VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) {
if (!device) {
// Null buffer supported, return a null descriptor
@@ -384,7 +390,9 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept {
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
it->ResetUsageTracking();
if (scheduler.IsFree(it->LastUsageTick())) {
it->ResetUsageTracking();
}
}
}
@@ -556,7 +564,7 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
if (index >= device.GetMaxVertexInputBindings()) {
return;
}
if (device.IsExtExtendedDynamicStateSupported()) {
if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) {
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
@@ -596,7 +604,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
if (binding_count == 0) {
return;
}
if (device.IsExtExtendedDynamicStateSupported()) {
if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) {
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data());
});
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -43,14 +43,16 @@ public:
return tracker.IsUsed(offset, size);
}
void MarkUsage(u64 offset, u64 size) noexcept {
tracker.Track(offset, size);
}
void MarkUsage(u64 offset, u64 size) noexcept;
void ResetUsageTracking() noexcept {
tracker.Reset();
}
[[nodiscard]] u64 LastUsageTick() const noexcept {
return last_usage_tick;
}
operator VkBuffer() const noexcept {
return *buffer;
}
@@ -64,9 +66,11 @@ private:
};
const Device* device{};
Scheduler* scheduler{};
vk::Buffer buffer;
std::vector<BufferView> views;
VideoCommon::UsageTracker tracker;
u64 last_usage_tick{};
bool is_null{};
};
@@ -127,6 +131,10 @@ public:
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
void SetVertexInputDynamicState(bool is_active) {
vertex_input_dynamic_state_active = is_active;
}
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
@@ -163,7 +171,11 @@ public:
private:
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
guest_descriptor_queue.AddBuffer(buffer, offset, size);
if (buffer == VK_NULL_HANDLE) {
guest_descriptor_queue.AddBuffer(buffer, 0, VK_WHOLE_SIZE);
} else {
guest_descriptor_queue.AddBuffer(buffer, offset, size);
}
}
void ReserveNullBuffer();
@@ -185,6 +197,8 @@ private:
bool limit_dynamic_storage_buffers = false;
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
bool vertex_input_dynamic_state_active = false;
};
struct BufferCacheParams {
@@ -193,8 +193,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
is_written = desc.is_written;
}
ImageView& image_view = texture_cache.GetImageView(views[index].id);
PixelFormat format{image_view.format};
if constexpr (is_image) {
if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) {
format = *explicit_format;
}
}
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
image_view.BufferSize(), image_view.format,
image_view.BufferSize(), format,
is_written, is_image);
++index;
}
@@ -426,8 +426,14 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
is_written = desc.is_written;
}
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
PixelFormat format{image_view.format};
if constexpr (is_image) {
if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) {
format = *explicit_format;
}
}
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
image_view.BufferSize(), image_view.format,
image_view.BufferSize(), format,
is_written, is_image);
++index;
++texture_buffer_it;
@@ -472,6 +478,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
}
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.runtime.SetVertexInputDynamicState(HasDynamicVertexInput());
buffer_cache.BindHostGeometryBuffers(is_indexed);
guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries);
@@ -865,18 +872,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
};
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1
// Only use it if VIDS is not active (VIDS replaces it with full vertex input control)
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT
if (!key.state.dynamic_vertex_input) {
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
}
}
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS
// Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT
if (key.state.dynamic_vertex_input) {
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
}
@@ -906,6 +912,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
}
// VK_EXT_color_write_enable fallback for fully on/off render targets when EDS3 blending is not available.
if (!key.state.extended_dynamic_state_3_blend && key.state.color_write_enable_dynamic) {
dynamic_states.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT);
}
// EDS3 - Enables (composite: per-feature)
if (key.state.extended_dynamic_state_3_enables) {
if (device.SupportsDynamicState3DepthClampEnable()) {
@@ -130,6 +130,70 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
VkSemaphore wait_semaphore, u64 host_tick) {
const VkSemaphore timeline_semaphore = *semaphore;
if (device.HasSynchronization2()) {
const std::array<VkCommandBufferSubmitInfo, 2> cmdbuffer_infos{{
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *upload_cmdbuf,
.deviceMask = 0,
},
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *cmdbuf,
.deviceMask = 0,
},
}};
std::array<VkSemaphoreSubmitInfo, 2> signal_infos{{
{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = timeline_semaphore,
.value = host_tick,
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.deviceIndex = 0,
},
{},
}};
u32 num_signal_semaphores = 1;
if (signal_semaphore) {
signal_infos[1] = VkSemaphoreSubmitInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = signal_semaphore,
.value = 0,
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.deviceIndex = 0,
};
num_signal_semaphores = 2;
}
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
const VkSemaphoreSubmitInfo wait_info{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = wait_semaphore,
.value = 0,
.stageMask = static_cast<VkPipelineStageFlags2>(wait_stage_mask),
.deviceIndex = 0,
};
const VkSubmitInfo2 submit_info2{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
.pNext = nullptr,
.flags = 0,
.waitSemaphoreInfoCount = num_wait_semaphores,
.pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr,
.commandBufferInfoCount = static_cast<u32>(cmdbuffer_infos.size()),
.pCommandBufferInfos = cmdbuffer_infos.data(),
.signalSemaphoreInfoCount = num_signal_semaphores,
.pSignalSemaphoreInfos = signal_infos.data(),
};
return device.GetGraphicsQueue().Submit2(submit_info2);
}
const u32 num_signal_semaphores = signal_semaphore ? 2 : 1;
const std::array signal_values{host_tick, u64(0)};
const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
@@ -172,6 +236,66 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
vk::CommandBuffer& upload_cmdbuf,
VkSemaphore signal_semaphore, VkSemaphore wait_semaphore,
u64 host_tick) {
if (device.HasSynchronization2()) {
const std::array<VkCommandBufferSubmitInfo, 2> cmdbuffer_infos{{
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *upload_cmdbuf,
.deviceMask = 0,
},
{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
.pNext = nullptr,
.commandBuffer = *cmdbuf,
.deviceMask = 0,
},
}};
const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
const VkSemaphoreSubmitInfo signal_info{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = signal_semaphore,
.value = 0,
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.deviceIndex = 0,
};
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
const VkSemaphoreSubmitInfo wait_info{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.semaphore = wait_semaphore,
.value = 0,
.stageMask = static_cast<VkPipelineStageFlags2>(wait_stage_mask),
.deviceIndex = 0,
};
const VkSubmitInfo2 submit_info2{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
.pNext = nullptr,
.flags = 0,
.waitSemaphoreInfoCount = num_wait_semaphores,
.pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr,
.commandBufferInfoCount = static_cast<u32>(cmdbuffer_infos.size()),
.pCommandBufferInfos = cmdbuffer_infos.data(),
.signalSemaphoreInfoCount = num_signal_semaphores,
.pSignalSemaphoreInfos = num_signal_semaphores ? &signal_info : nullptr,
};
auto fence = GetFreeFence();
auto result = device.GetGraphicsQueue().Submit2(submit_info2, *fence);
if (result == VK_SUCCESS) {
std::scoped_lock lock{wait_mutex};
wait_queue.emplace(host_tick, std::move(fence));
wait_cv.notify_one();
}
return result;
}
const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
@@ -246,32 +246,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
// Check for dual source blending
const auto& blend0 = key.state.attachments[0];
if (blend0.enable != 0) {
using F = Maxwell::Blend::Factor;
const auto src_rgb = blend0.SourceRGBFactor();
const auto dst_rgb = blend0.DestRGBFactor();
const auto src_a = blend0.SourceAlphaFactor();
const auto dst_a = blend0.DestAlphaFactor();
info.dual_source_blend =
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
}
info.dual_source_blend = key.state.attachment0_dual_source_blend != 0;
if (device.IsMoltenVK()) {
for (size_t i = 0; i < 8; ++i) {
@@ -383,7 +358,11 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.unified_descriptor_binding = true,
.support_descriptor_aliasing = device.IsDescriptorAliasingSupported(),
.support_int8 = device.IsInt8Supported(),
.support_uniform_and_storage_buffer_8bit =
device.IsUniformAndStorageBuffer8BitAccessSupported(),
.support_int16 = device.IsShaderInt16Supported(),
.support_uniform_and_storage_buffer_16bit =
device.IsUniformAndStorageBuffer16BitAccessSupported(),
.support_int64 = device.IsShaderInt64Supported(),
.support_vertex_instance_id = false,
.support_float_controls = device.IsKhrShaderFloatControlsSupported(),
@@ -502,6 +481,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
device.IsExtExtendedDynamicState3BlendingSupported();
dynamic_features.has_extended_dynamic_state_3_enables =
device.IsExtExtendedDynamicState3EnablesSupported();
dynamic_features.has_color_write_enable =
device.IsExtColorWriteEnableSupported();
dynamic_features.has_dynamic_state3_depth_clamp_enable =
dynamic_features.has_extended_dynamic_state_3_enables &&
device.SupportsDynamicState3DepthClampEnable();
@@ -634,7 +615,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
dynamic_features.has_extended_dynamic_state_3_blend ||
(key.state.extended_dynamic_state_3_enables != 0) !=
dynamic_features.has_extended_dynamic_state_3_enables ||
(key.state.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input) {
(key.state.color_write_enable_dynamic != 0) !=
dynamic_features.has_color_write_enable ||
(key.state.dynamic_vertex_input != 0) !=
dynamic_features.has_dynamic_vertex_input) {
return;
}
@@ -41,8 +41,8 @@ class SamplesQueryBank : public VideoCommon::BankBase {
public:
static constexpr size_t BANK_SIZE = 256;
static constexpr size_t QUERY_SIZE = 8;
explicit SamplesQueryBank(const Device& device_, size_t index_)
: BankBase(BANK_SIZE), device{device_}, index{index_} {
explicit SamplesQueryBank(const Device& device_, Scheduler& scheduler_, size_t index_)
: BankBase(BANK_SIZE), device{device_}, scheduler{scheduler_}, index{index_} {
const auto& dev = device.GetLogical();
query_pool = dev.CreateQueryPool({
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
@@ -60,12 +60,28 @@ public:
void Reset() override {
ASSERT(references == 0);
VideoCommon::BankBase::Reset();
const auto& dev = device.GetLogical();
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
if (device.IsHostQueryResetSupported()) {
const auto& dev = device.GetLogical();
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
} else {
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([pool = *query_pool](vk::CommandBuffer cmdbuf) {
cmdbuf.ResetQueryPool(pool, 0, BANK_SIZE);
});
}
host_results.fill(0ULL);
next_bank = 0;
}
void AddReference(size_t how_many = 1) {
BankBase::AddReference(how_many);
last_used_tick = scheduler.CurrentTick();
}
[[nodiscard]] bool IsDead() const {
return BankBase::IsDead() && scheduler.IsFree(last_used_tick);
}
void Sync(size_t start, size_t size) {
const auto& dev = device.GetLogical();
const VkResult query_result = dev.GetQueryResults(
@@ -98,9 +114,11 @@ public:
private:
const Device& device;
Scheduler& scheduler;
const size_t index;
vk::QueryPool query_pool;
std::array<u64, BANK_SIZE> host_results;
u64 last_used_tick{};
};
using BaseStreamer = VideoCommon::SimpleStreamer<VideoCommon::HostQueryBase>;
@@ -431,7 +449,7 @@ private:
void ReserveBank() {
current_bank_id =
bank_pool.ReserveBank([this](std::deque<SamplesQueryBank>& queue, size_t index) {
queue.emplace_back(device, index);
queue.emplace_back(device, scheduler, index);
});
if (current_bank) {
current_bank->next_bank = current_bank_id + 1;
@@ -620,6 +638,15 @@ public:
VideoCommon::BankBase::Reset();
}
void AddReference(size_t how_many = 1) {
BankBase::AddReference(how_many);
last_used_tick = scheduler.CurrentTick();
}
[[nodiscard]] bool IsDead() const {
return BankBase::IsDead() && scheduler.IsFree(last_used_tick);
}
void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) {
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start,
@@ -645,6 +672,7 @@ private:
Scheduler& scheduler;
const size_t index;
vk::Buffer buffer;
u64 last_used_tick{};
};
class PrimitivesSucceededStreamer;
@@ -922,7 +950,7 @@ private:
return;
}
has_flushed_end_pending = false;
// Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date.
UpdateBuffers();
if (buffers_count == 0) {
@@ -223,7 +223,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
scheduler.SetQueryCache(query_cache);
}
RasterizerVulkan::~RasterizerVulkan() = default;
RasterizerVulkan::~RasterizerVulkan() {
scheduler.WaitWorker();
scheduler.Finish();
}
template <typename Func>
void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
@@ -1011,12 +1014,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
auto& regs = maxwell3d->regs;
auto& flags = maxwell3d->dirty.flags;
const auto topology = maxwell3d->draw_manager.draw_state.topology;
if (state_tracker.ChangePrimitiveTopology(topology)) {
const bool topology_changed = state_tracker.ChangePrimitiveTopology(topology);
if (topology_changed) {
flags[Dirty::DepthBiasEnable] = true;
flags[Dirty::PrimitiveRestartEnable] = true;
}
// Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
@@ -1025,7 +1028,6 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateStencilFaces(regs);
UpdateLineWidth(regs);
// EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest
if (device.IsExtExtendedDynamicStateSupported()) {
UpdateCullMode(regs);
UpdateDepthCompareOp(regs);
@@ -1037,21 +1039,24 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthWriteEnable(regs);
UpdateStencilTestEnable(regs);
}
if (topology_changed) {
scheduler.Record([topology_vk = MaxwellToVK::PrimitiveTopology(device, topology)](
vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveTopologyEXT(topology_vk);
});
}
}
// EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable
if (device.IsExtExtendedDynamicState2Supported()) {
UpdatePrimitiveRestartEnable(regs);
UpdateRasterizerDiscardEnable(regs);
UpdateDepthBiasEnable(regs);
}
// EDS2 Extras: LogicOp operation selection
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
UpdateLogicOp(regs);
}
// EDS3 Enables: LogicOpEnable, DepthClamp, LineStipple, ConservativeRaster
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
using namespace Tegra::Engines;
// AMD Workaround: LogicOp incompatible with float render targets
@@ -1076,12 +1081,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateAlphaToOneEnable(regs);
}
// EDS3 Blending: ColorBlendEnable, ColorBlendEquation, ColorWriteMask
if (device.IsExtExtendedDynamicState3BlendingSupported()) {
UpdateBlending(regs);
} else if (device.IsExtColorWriteEnableSupported()) {
UpdateColorWriteEnable(regs);
}
// Vertex Input Dynamic State: Independent from EDS levels
if (device.IsExtVertexInputDynamicStateSupported()) {
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) {
UpdateVertexInput(regs);
@@ -1094,7 +1099,6 @@ void RasterizerVulkan::HandleTransformFeedback() {
const auto& regs = maxwell3d->regs;
if (!device.IsExtTransformFeedbackSupported()) {
// If the guest enabled transform feedback, warn once that the device lacks support.
if (regs.transform_feedback_enabled != 0) {
std::call_once(warn_unsupported, [&] {
LOG_WARNING(Render_Vulkan, "Transform feedback requested by guest but VK_EXT_transform_feedback is unavailable; queries disabled");
@@ -1723,9 +1727,16 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) {
if (state_tracker.TouchBlendEnable()) {
std::array<VkBool32, Maxwell::NumRenderTargets> setup_enables{};
std::ranges::transform(
regs.blend.enable, setup_enables.begin(),
[&](const auto& is_enabled) { return is_enabled != 0 ? VK_TRUE : VK_FALSE; });
for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) {
bool is_integer = false;
if (regs.rt[index].format != Tegra::RenderTargetFormat::NONE) {
const auto format =
VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format);
is_integer = IsPixelFormatInteger(format);
}
setup_enables[index] =
(!is_integer && regs.blend.enable[index] != 0) ? VK_TRUE : VK_FALSE;
}
scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) {
cmdbuf.SetColorBlendEnableEXT(0, setup_enables);
});
@@ -1774,6 +1785,20 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) {
}
}
void RasterizerVulkan::UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchColorMask()) {
return;
}
std::array<VkBool32, Maxwell::NumRenderTargets> setup_enables{};
for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) {
const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];
setup_enables[index] = (mask.R || mask.G || mask.B || mask.A) ? VK_TRUE : VK_FALSE;
}
scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) {
cmdbuf.SetColorWriteEnableEXT(setup_enables);
});
}
void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchStencilTestEnable()) {
return;
@@ -191,6 +191,7 @@ private:
void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateLogicOp(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -344,7 +344,9 @@ void Scheduler::EndRenderPass()
Record([num_images = num_renderpass_images,
images = renderpass_images,
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
ranges = renderpass_image_ranges,
has_transform_feedback = device.IsExtTransformFeedbackSupported()](
vk::CommandBuffer cmdbuf) {
std::array<VkImageMemoryBarrier, 9> barriers;
for (size_t i = 0; i < num_images; ++i) {
const VkImageSubresourceRange& range = ranges[i];
@@ -384,6 +386,17 @@ void Scheduler::EndRenderPass()
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
if (has_transform_feedback) {
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
0, XFB_OUTPUT_BARRIER);
}
});
state.renderpass = VkRenderPass{};
@@ -1299,7 +1299,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
case PixelFormat::R32G32_FLOAT:
case PixelFormat::R32G32_SINT:
case PixelFormat::R32_FLOAT:
if ((src_view.format == PixelFormat::D32_FLOAT) && Settings::values.fix_bloom_effects.GetValue()) {
if (src_view.format == PixelFormat::D32_FLOAT &&
(dst_view.format == PixelFormat::B5G6R5_UNORM ||
Settings::values.fix_bloom_effects.GetValue())) {
const Region2D region{
.start = {0, 0},
.end = {static_cast<s32>(dst->RenderArea().width),
@@ -2242,7 +2244,12 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type,
Shader::ImageFormat image_format) {
if (image_handle) {
if (image_format == Shader::ImageFormat::Typeless) {
return Handle(texture_type);
if (!typeless_storage_view) {
const auto& info =
MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT);
}
return *typeless_storage_view;
}
const bool is_signed = image_format == Shader::ImageFormat::R8_SINT
|| image_format == Shader::ImageFormat::R16_SINT;
@@ -2297,12 +2304,15 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
const void* pnext = nullptr;
if (has_custom_border_colors) {
pnext = &border_ci;
// Log extension usage for custom border color
if (GPU::Logging::IsActive()) {
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
"VK_EXT_custom_border_color", "Sampler::Sampler");
}
}
if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) {
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
"VK_EXT_border_color_swizzle", "Sampler::Sampler");
}
const VkSamplerReductionModeCreateInfoEXT reduction_ci{
.sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT,
.pNext = pnext,
@@ -2316,20 +2326,28 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
// Some games have samplers with garbage. Sanitize them here.
const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f);
const auto create_sampler = [&](const f32 anisotropy) {
const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)};
const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)};
const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)};
const bool has_linear_filtering{mag_filter == VK_FILTER_LINEAR ||
min_filter == VK_FILTER_LINEAR ||
mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR};
const auto create_sampler = [&](const f32 anisotropy, bool force_nearest) {
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = pnext,
.flags = 0,
.magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
.minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
.mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
.magFilter = force_nearest ? VK_FILTER_NEAREST : mag_filter,
.minFilter = force_nearest ? VK_FILTER_NEAREST : min_filter,
.mipmapMode = force_nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : mipmap_mode,
.addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
.addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
.addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
.mipLodBias = tsc.LodBias(),
.anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
.maxAnisotropy = anisotropy,
.anisotropyEnable =
static_cast<VkBool32>(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
.maxAnisotropy = force_nearest ? 1.0f : anisotropy,
.compareEnable = tsc.depth_compare_enabled,
.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
@@ -2340,11 +2358,14 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
});
};
sampler = create_sampler(max_anisotropy);
sampler = create_sampler(max_anisotropy, false);
const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy);
if (max_anisotropy > max_anisotropy_default) {
sampler_default_anisotropy = create_sampler(max_anisotropy_default);
sampler_default_anisotropy = create_sampler(max_anisotropy_default, false);
}
if (has_linear_filtering) {
sampler_nearest = create_sampler(1.0f, true);
}
}
@@ -377,6 +377,7 @@ private:
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views;
std::optional<StorageViews> storage_views;
vk::ImageView typeless_storage_view;
vk::ImageView depth_view;
vk::ImageView stencil_view;
vk::ImageView color_view;
@@ -405,9 +406,18 @@ public:
return static_cast<bool>(sampler_default_anisotropy);
}
[[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept {
return *sampler_nearest;
}
[[nodiscard]] bool HasLinearFiltering() const noexcept {
return static_cast<bool>(sampler_nearest);
}
private:
vk::Sampler sampler;
vk::Sampler sampler_default_anisotropy;
vk::Sampler sampler_nearest;
};
struct TextureCacheParams {