Compare commits

..

1 Commits

Author SHA1 Message Date
wildcard 589ea324e6 [vulkan] Fix TOTK dark squares
Qualcomm and Turnip use different subpixel rounding for Vulkan texture
gather coordinates than the Switch's Maxwell GPU. This can select the
wrong texels near texture boundaries, producing large black squares in TOTK
2026-10-21 18:26:37 +02:00
5 changed files with 19 additions and 96 deletions
+5 -15
View File
@@ -40,12 +40,6 @@ bool IsConnectionBased(Type type) {
}
}
// Size of a real BSD sockaddr_in. The guest SockAddrIn struct is padded to 0x100 so that longer
// input addresses can be read, but an address handed back to the game is always this long.
// Reporting 0x100 lets a game that trusts the length copy 240 bytes past its 16-byte sockaddr
// (suspected cause of Crysis Remastered crashing ~30 s after its getsockname() call).
constexpr size_t GuestSockAddrInSize = 16;
template <typename T>
T GetValue(std::span<const u8> buffer) {
T t{};
@@ -661,9 +655,6 @@ std::pair<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& write_buffer)
new_descriptor.is_connection_based = descriptor.is_connection_based;
const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
if (write_buffer.size() > GuestSockAddrInSize) {
write_buffer.resize(GuestSockAddrInSize);
}
PutValue(write_buffer, guest_addr_in);
return {new_fd, Errno::SUCCESS};
@@ -723,8 +714,8 @@ Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
}
const SockAddrIn guest_addrin = Translate(addr_in);
// The reported length must be that of a real sockaddr_in, not of the padded guest struct.
write_buffer.resize(GuestSockAddrInSize);
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
write_buffer.resize(sizeof(guest_addrin));
PutValue(write_buffer, guest_addrin);
return Translate(bsd_errno);
}
@@ -745,8 +736,8 @@ Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
}
const SockAddrIn guest_addrin = Translate(addr_in);
// The reported length must be that of a real sockaddr_in, not of the padded guest struct.
write_buffer.resize(GuestSockAddrInSize);
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
write_buffer.resize(sizeof(guest_addrin));
PutValue(write_buffer, guest_addrin);
return Translate(bsd_errno);
}
@@ -958,9 +949,8 @@ std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>&
if (ret < 0) {
addr.clear();
} else {
ASSERT(addr.size() >= GuestSockAddrInSize);
ASSERT(addr.size() >= 16);
const SockAddrIn result = Translate(addr_in);
addr.resize(GuestSockAddrInSize);
PutValue(addr, result);
}
}
+9 -47
View File
@@ -563,8 +563,10 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
});
}
void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass,
VkFramebuffer framebuffer_handle, VkExtent2D render_area) {
void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) {
const VkRenderPass render_pass = framebuffer->RenderPass();
const VkFramebuffer framebuffer_handle = framebuffer->Handle();
const VkExtent2D render_area = framebuffer->RenderArea();
const VkRenderPassBeginInfo renderpass_bi{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
@@ -677,67 +679,27 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageV
void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
VkImage src_image, VkSampler src_sampler,
const Region2D& dst_region, const Region2D& src_region,
const Extent3D& src_size,
const std::optional<VkRect2D>& scissor) {
const Extent3D& src_size) {
const BlitImagePipelineKey key{
.renderpass = dst_framebuffer->RenderPass(),
.operation = Tegra::Engines::Fermi2D::Operation::SrcCopy,
};
const VkPipelineLayout layout = *one_texture_pipeline_layout;
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
// Clip the destination rectangle to the scissor; the viewport keeps the full rectangle so
// the texture coordinates are unchanged.
std::optional<VkRect2D> clip;
if (scissor) {
const s64 x0 = (std::max)(s64{(std::min)(dst_region.start.x, dst_region.end.x)},
s64{scissor->offset.x});
const s64 y0 = (std::max)(s64{(std::min)(dst_region.start.y, dst_region.end.y)},
s64{scissor->offset.y});
const s64 x1 = (std::min)(s64{(std::max)(dst_region.start.x, dst_region.end.x)},
s64{scissor->offset.x} + scissor->extent.width);
const s64 y1 = (std::min)(s64{(std::max)(dst_region.start.y, dst_region.end.y)},
s64{scissor->offset.y} + scissor->extent.height);
if (x1 <= x0 || y1 <= y0) {
return; // Fully scissored out.
}
clip = VkRect2D{
.offset = {static_cast<s32>(x0), static_cast<s32>(y0)},
.extent = {static_cast<u32>(x1 - x0), static_cast<u32>(y1 - y0)},
};
}
// Read the framebuffer now: by the time the worker runs this, the texture cache may have
// retired it (moved into its delayed-destruction ring), leaving a null handle behind - Turnip
// then crashed in vkCmdBeginRenderPass (Crysis Remastered, DrawTexture).
const VkRenderPass render_pass = dst_framebuffer->RenderPass();
const VkFramebuffer framebuffer_handle = dst_framebuffer->Handle();
const VkExtent2D render_area = dst_framebuffer->RenderArea();
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, render_pass, framebuffer_handle, render_area, src_image_view,
src_image, src_sampler, dst_region, src_region, src_size, pipeline, layout,
clip](vk::CommandBuffer cmdbuf) {
// Images stay in GENERAL everywhere (the descriptor below says so too): only make the
// earlier writes visible. Moving the source to SHADER_READ_ONLY_OPTIMAL left it there
// for good, so every later render pass used it in the wrong layout.
TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_GENERAL);
BeginRenderPass(cmdbuf, render_pass, framebuffer_handle, render_area);
scheduler.Record([this, dst_framebuffer, src_image_view, src_image, src_sampler, dst_region,
src_region, src_size, pipeline, layout](vk::CommandBuffer cmdbuf) {
TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
BeginRenderPass(cmdbuf, dst_framebuffer);
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, src_sampler, src_image_view);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
BindBlitState(cmdbuf, layout, dst_region, src_region, src_size);
if (clip) {
cmdbuf.SetScissor(0, *clip);
}
cmdbuf.Draw(3, 1, 0, 0);
cmdbuf.EndRenderPass();
});
// The blit set its own pipeline, viewport and scissor: make the next draw set its own again,
// as every other helper here does. Without this the next draw kept the blit's viewport and
// scissor: Crysis Remastered drew its 256x256 minimap map over the whole screen.
scheduler.InvalidateState();
}
void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
+1 -5
View File
@@ -7,7 +7,6 @@
#pragma once
#include <deque>
#include <optional>
#include <span>
#include "video_core/engines/fermi_2d.h"
@@ -79,12 +78,9 @@ public:
Tegra::Engines::Fermi2D::Filter filter,
Tegra::Engines::Fermi2D::Operation operation);
/// @param scissor Optional clip rectangle in framebuffer pixels (DrawTexture honours the
/// guest scissor like any other draw).
void BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
VkImage src_image, VkSampler src_sampler, const Region2D& dst_region,
const Region2D& src_region, const Extent3D& src_size,
const std::optional<VkRect2D>& scissor = std::nullopt);
const Region2D& src_region, const Extent3D& src_size);
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region);
@@ -447,7 +447,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE ||
driver_id == VK_DRIVER_ID_MESA_RADV ||
driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS ||
driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA,
driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_MESA_TURNIP,
.has_broken_spirv_clamp = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS,
.has_broken_spirv_position_input = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
@@ -393,35 +393,8 @@ void RasterizerVulkan::DrawTexture() {
.y = ScaleSrc(draw_texture_state.src_y1)}};
Extent3D src_size = {static_cast<u32>(ScaleSrc(texture.size.width)),
static_cast<u32>(ScaleSrc(texture.size.height)), texture.size.depth};
// DrawTexture rectangles go through the fragment pipeline, scissor included.
std::optional<VkRect2D> scissor;
if (maxwell3d->regs.scissor_test[0].enable) {
u32 up_scale = 1;
u32 down_shift = 0;
if (dst_rescaling) {
up_scale = Settings::values.resolution_info.up_scale;
down_shift = Settings::values.resolution_info.down_shift;
}
scissor = GetScissorState(maxwell3d->regs, 0, up_scale, down_shift);
}
// Drawing a texture 1:1 onto itself without blending changes nothing on hardware, but here
// it samples the image the render pass is writing to: undefined in Vulkan, and on tiled GPUs
// it reads whatever the image memory last held. Crysis Remastered does this every frame for
// its post-processing buffers (suspected cause of its map covering the whole screen).
const auto& fb_images = framebuffer->Images();
const bool src_is_target =
std::find(fb_images.begin(), fb_images.begin() + framebuffer->NumImages(),
texture.ImageHandle()) != fb_images.begin() + framebuffer->NumImages();
if (src_is_target && src_rescaling == dst_rescaling &&
src_region.start.x == dst_region.start.x && src_region.start.y == dst_region.start.y &&
src_region.end.x == dst_region.end.x && src_region.end.y == dst_region.end.y &&
maxwell3d->regs.blend.enable[0] == 0) {
return;
}
blit_image.BlitColor(framebuffer, texture.RenderTarget(), texture.ImageHandle(),
sampler->Handle(), dst_region, src_region, src_size, scissor);
sampler->Handle(), dst_region, src_region, src_size);
}
void RasterizerVulkan::Clear(u32 layer_count) {