diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 0137a3be66..736dc890b5 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -40,6 +40,12 @@ 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 T GetValue(std::span buffer) { T t{}; @@ -655,6 +661,9 @@ std::pair BSD_USA::AcceptImpl(s32 fd, std::vector& 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}; @@ -714,8 +723,8 @@ Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { } const SockAddrIn guest_addrin = Translate(addr_in); - ASSERT(write_buffer.size() >= sizeof(guest_addrin)); - write_buffer.resize(sizeof(guest_addrin)); + // The reported length must be that of a real sockaddr_in, not of the padded guest struct. + write_buffer.resize(GuestSockAddrInSize); PutValue(write_buffer, guest_addrin); return Translate(bsd_errno); } @@ -736,8 +745,8 @@ Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector& write_buffer) { } const SockAddrIn guest_addrin = Translate(addr_in); - ASSERT(write_buffer.size() >= sizeof(guest_addrin)); - write_buffer.resize(sizeof(guest_addrin)); + // The reported length must be that of a real sockaddr_in, not of the padded guest struct. + write_buffer.resize(GuestSockAddrInSize); PutValue(write_buffer, guest_addrin); return Translate(bsd_errno); } @@ -949,8 +958,9 @@ std::pair BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector& if (ret < 0) { addr.clear(); } else { - ASSERT(addr.size() >= 16); + ASSERT(addr.size() >= GuestSockAddrInSize); const SockAddrIn result = Translate(addr_in); + addr.resize(GuestSockAddrInSize); PutValue(addr, result); } } diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index ddd52e9426..e5eb672c37 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -563,10 +563,8 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view) }); } -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(); +void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass, + VkFramebuffer framebuffer_handle, VkExtent2D render_area) { const VkRenderPassBeginInfo renderpass_bi{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .pNext = nullptr, @@ -679,27 +677,67 @@ 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 Extent3D& src_size, + const std::optional& scissor) { 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 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(x0), static_cast(y0)}, + .extent = {static_cast(x1 - x0), static_cast(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, 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); + 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); 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, diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 62e6743a23..d058560f4a 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include "video_core/engines/fermi_2d.h" @@ -78,9 +79,12 @@ 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 Region2D& src_region, const Extent3D& src_size, + const std::optional& scissor = std::nullopt); void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, const Region2D& dst_region, const Region2D& src_region); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index f43bea1220..182cc6d8ea 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -393,8 +393,35 @@ void RasterizerVulkan::DrawTexture() { .y = ScaleSrc(draw_texture_state.src_y1)}}; Extent3D src_size = {static_cast(ScaleSrc(texture.size.width)), static_cast(ScaleSrc(texture.size.height)), texture.size.depth}; + // DrawTexture rectangles go through the fragment pipeline, scissor included. + std::optional 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); + sampler->Handle(), dst_region, src_region, src_size, scissor); } void RasterizerVulkan::Clear(u32 layer_count) {