From e38d27157e3e683da4341bf530bdbbd41eb9239c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 8 Oct 2026 21:13:31 +0300 Subject: [PATCH] rsx/vk: Implement rendering using no attachments --- rpcs3/Emu/RSX/RSXThread.cpp | 25 ++++++++++++++++++++---- rpcs3/Emu/RSX/VK/VKDraw.cpp | 7 ++++--- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 34 ++++++++++++++++++++++++++++----- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 2e03962921..d1eba1e99b 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -1477,7 +1477,8 @@ namespace rsx layout.width = rsx::method_registers.surface_clip_width(); layout.height = rsx::method_registers.surface_clip_height(); - m_graphics_state.clear(rsx::rtt_config_contested | rsx::rtt_config_valid | rsx::rtt_config_no_attachments); + // NOTE: rtt_config_no_attachments is intentionally not reset. + m_graphics_state.clear(rsx::rtt_config_contested | rsx::rtt_config_valid); m_current_framebuffer_context = context; if (layout.width == 0 || layout.height == 0) @@ -1801,7 +1802,22 @@ namespace rsx } // Context must be draw now... - m_graphics_state.set(rsx::rtt_config_no_attachments); + } + + if (framebufferless != m_graphics_state.test(rsx::rtt_config_no_attachments)) + { + // Programmable blending reads back from the color attachments and is disabled when there are none. + // Force the blend configuration to be re-evaluated when moving in or out of this state. + if (framebufferless) + { + m_graphics_state.set(rsx::rtt_config_no_attachments); + } + else + { + m_graphics_state.clear(rsx::rtt_config_no_attachments); + } + + m_graphics_state.set(rsx::pipeline_config_dirty); } // At least one attachment exists @@ -2145,8 +2161,9 @@ namespace rsx { u32 expected_ctrl = 0; - // Programmable blending - if (backend_config.supports_programmable_blending) + // Programmable blending. Requires color attachments to read from. + if (backend_config.supports_programmable_blending && + !m_graphics_state.test(rsx::rtt_config_no_attachments)) { expected_ctrl = current_fragment_program.ctrl & RSX_SHADER_CONTROL_PROGRAMMABLE_BLENDING; diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 5bc90963f1..21e035935e 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -219,7 +219,9 @@ void VKGSRender::update_draw_state() const auto polygon_offset_scale = rsx::method_registers.poly_offset_scale(); auto polygon_offset_bias = rsx::method_registers.poly_offset_bias(); - if (m_draw_fbo->depth_format() == VK_FORMAT_D24_UNORM_S8_UINT && is_NVIDIA(vk::get_chip_family())) + if (!m_draw_fbo->attachments.empty() && + m_draw_fbo->depth_format() == VK_FORMAT_D24_UNORM_S8_UINT && + is_NVIDIA(vk::get_chip_family())) { // Empirically derived to be 0.5 * (2^24 - 1) for fixed type on Pascal. The same seems to apply for other NVIDIA GPUs. // RSX seems to be using 2^24 - 1 instead making the biases twice as large when using fixed type Z-buffer on NVIDIA. @@ -1261,7 +1263,6 @@ void VKGSRender::begin() rsx::thread::begin(); if (skip_current_frame || - swapchain_unavailable || cond_render_ctrl.disable_rendering()) { return; @@ -1279,7 +1280,7 @@ void VKGSRender::begin() void VKGSRender::end() { - if (skip_current_frame || !m_graphics_state.test(rsx::rtt_config_valid) || swapchain_unavailable || cond_render_ctrl.disable_rendering()) + if (should_skip_draw()) { execute_nop_draw(); rsx::thread::end(); diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 8c046ecaf7..b483cfa257 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -677,6 +677,21 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) backend_config.supports_hw_a2one = m_device->get_alpha_to_one_support(); } + // Framebufferless rendering. Zero-attachment subpasses can only rasterize at the sample counts reported for them. + const VkSampleCountFlags required_sample_counts = (g_cfg.video.antialiasing_level == msaa_level::_auto) + ? VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_2_BIT | VK_SAMPLE_COUNT_4_BIT + : VK_SAMPLE_COUNT_1_BIT; + + if (const auto supported_sample_counts = m_device->gpu().get_limits().framebufferNoAttachmentsSampleCounts & required_sample_counts; + supported_sample_counts == required_sample_counts) + { + backend_config.supports_framebufferless_rendering = true; + } + else + { + rsx_log.warning("Framebufferless rendering is not supported with the current MSAA configuration. Some occlusion queries may return incorrect results."); + } + // NOTE: On NVIDIA cards going back decades (including the PS3) there is a slight normalization inaccuracy in compressed formats. // Confirmed in BLES01916 (The Evil Within) which uses RGB565 for some virtual texturing data. backend_config.supports_hw_renormalization = vk::is_NVIDIA(vk::get_driver_vendor()); @@ -1299,7 +1314,7 @@ void VKGSRender::on_exit() void VKGSRender::clear_surface(u32 mask) { - if (skip_current_frame || swapchain_unavailable) return; + if (skip_current_frame) return; // If stencil write mask is disabled, remove clear_stencil bit if (!rsx::method_registers.stencil_mask()) mask &= ~RSX_GCM_CLEAR_STENCIL_BIT; @@ -2709,7 +2724,19 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context) std::iota(input_attachments.begin(), input_attachments.end(), 0); } - m_current_renderpass_key = vk::get_renderpass_key(m_fbo_images, input_attachments); + if (m_graphics_state.test(rsx::rtt_config_no_attachments)) + { + // Framebufferless rendering. The raster sample count must match what real surfaces would have used. + ensure(m_fbo_images.empty() && input_attachments.empty()); + + const u8 raster_samples = (g_cfg.video.antialiasing_level == msaa_level::_auto) ? samples : 1; + m_current_renderpass_key = vk::get_renderpass_key_no_attachments(raster_samples); + } + else + { + m_current_renderpass_key = vk::get_renderpass_key(m_fbo_images, input_attachments); + } + m_cached_renderpass = vk::get_renderpass(*m_device, m_current_renderpass_key); // Search old framebuffers for this same configuration @@ -2749,9 +2776,6 @@ void VKGSRender::renderctl(u32 request_code, void* args) bool VKGSRender::scaled_image_from_memory(const rsx::blit_src_info& src, const rsx::blit_dst_info& dst, bool interpolate) { - if (swapchain_unavailable) - return false; - if (m_texture_cache.blit(src, dst, interpolate, m_rtts, *m_current_command_buffer)) { m_samplers_dirty.store(true);