summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/Renderer.cpp
diff options
context:
space:
mode:
authorMat M <mathew1800@gmail.com>2016-09-30 19:15:50 -0400
committerGitHub <noreply@github.com>2016-09-30 19:15:50 -0400
commit46b9383280a623753686ab6df6c479e784e416ae (patch)
tree9ffb2cbdba6d39a29d6fed9df4db03166755a20d /Source/Core/VideoBackends/Vulkan/Renderer.cpp
parentf3cdc6433cba253fa4485cd7e27025c31410ed0c (diff)
parenta8194cff3ca8f9b85a779da6b8214b58ba3fa7d0 (diff)
Merge pull request #3935 from stenzek/vulkan-pr
Vulkan Backend
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/Renderer.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/Renderer.cpp1557
1 files changed, 1557 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
new file mode 100644
index 0000000000..a8578d0b27
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
@@ -0,0 +1,1557 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <cstdio>
+#include <limits>
+
+#include "Core/ConfigManager.h"
+
+#include "VideoBackends/Vulkan/BoundingBox.h"
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/FramebufferManager.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/RasterFont.h"
+#include "VideoBackends/Vulkan/Renderer.h"
+#include "VideoBackends/Vulkan/StagingTexture2D.h"
+#include "VideoBackends/Vulkan/StateTracker.h"
+#include "VideoBackends/Vulkan/SwapChain.h"
+#include "VideoBackends/Vulkan/Util.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+#if defined(HAVE_LIBAV) || defined(_WIN32)
+#include "VideoCommon/AVIDump.h"
+#endif
+
+#include "VideoCommon/BPFunctions.h"
+#include "VideoCommon/BPMemory.h"
+#include "VideoCommon/ImageWrite.h"
+#include "VideoCommon/OnScreenDisplay.h"
+#include "VideoCommon/PixelEngine.h"
+#include "VideoCommon/PixelShaderManager.h"
+#include "VideoCommon/SamplerCommon.h"
+#include "VideoCommon/TextureCacheBase.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace Vulkan
+{
+Renderer::Renderer()
+{
+ // Set to something invalid, forcing all states to be re-initialized.
+ for (size_t i = 0; i < m_sampler_states.size(); i++)
+ m_sampler_states[i].bits = std::numeric_limits<decltype(m_sampler_states[i].bits)>::max();
+
+ // Set default size so a decent EFB is created initially.
+ s_backbuffer_width = MAX_XFB_WIDTH;
+ s_backbuffer_height = MAX_XFB_HEIGHT;
+ FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH);
+ FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT);
+ PixelShaderManager::SetEfbScaleChanged();
+ UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height);
+ CalculateTargetSize(s_backbuffer_width, s_backbuffer_height);
+}
+
+Renderer::~Renderer()
+{
+ g_Config.bRunning = false;
+ UpdateActiveConfig();
+ DestroySemaphores();
+}
+
+bool Renderer::Initialize(FramebufferManager* framebuffer_mgr, void* window_handle,
+ VkSurfaceKHR surface)
+{
+ m_framebuffer_mgr = framebuffer_mgr;
+ g_Config.bRunning = true;
+ UpdateActiveConfig();
+
+ // Create state tracker, doesn't require any resources
+ m_state_tracker = std::make_unique<StateTracker>();
+ BindEFBToStateTracker();
+
+ if (!CreateSemaphores())
+ {
+ PanicAlert("Failed to create semaphores.");
+ return false;
+ }
+
+ if (!CompileShaders())
+ {
+ PanicAlert("Failed to compile shaders.");
+ return false;
+ }
+
+ m_raster_font = std::make_unique<RasterFont>();
+ if (!m_raster_font->Initialize())
+ {
+ PanicAlert("Failed to initialize raster font.");
+ return false;
+ }
+
+ m_bounding_box = std::make_unique<BoundingBox>();
+ if (!m_bounding_box->Initialize())
+ {
+ PanicAlert("Failed to initialize bounding box.");
+ return false;
+ }
+
+ if (g_vulkan_context->SupportsBoundingBox())
+ {
+ // Bind bounding box to state tracker
+ m_state_tracker->SetBBoxBuffer(m_bounding_box->GetGPUBuffer(),
+ m_bounding_box->GetGPUBufferOffset(),
+ m_bounding_box->GetGPUBufferSize());
+ }
+
+ // Initialize annoying statics
+ s_last_efb_scale = g_ActiveConfig.iEFBScale;
+
+ // Create swap chain
+ if (surface)
+ {
+ // Update backbuffer dimensions
+ m_swap_chain = SwapChain::Create(window_handle, surface);
+ if (!m_swap_chain)
+ {
+ PanicAlert("Failed to create swap chain.");
+ return false;
+ }
+
+ // Update render rectangle etc.
+ OnSwapChainResized();
+ }
+
+ // Various initialization routines will have executed commands on the command buffer.
+ // Execute what we have done before beginning the first frame.
+ g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
+ g_command_buffer_mgr->SubmitCommandBuffer(false);
+ BeginFrame();
+
+ return true;
+}
+
+bool Renderer::CreateSemaphores()
+{
+ // Create two semaphores, one that is triggered when the swapchain buffer is ready, another after
+ // submit and before present
+ VkSemaphoreCreateInfo semaphore_info = {
+ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0 // VkSemaphoreCreateFlags flags
+ };
+
+ VkResult res;
+ if ((res = vkCreateSemaphore(g_vulkan_context->GetDevice(), &semaphore_info, nullptr,
+ &m_image_available_semaphore)) != VK_SUCCESS ||
+ (res = vkCreateSemaphore(g_vulkan_context->GetDevice(), &semaphore_info, nullptr,
+ &m_rendering_finished_semaphore)) != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
+ return false;
+ }
+
+ return true;
+}
+
+void Renderer::DestroySemaphores()
+{
+ if (m_image_available_semaphore)
+ {
+ vkDestroySemaphore(g_vulkan_context->GetDevice(), m_image_available_semaphore, nullptr);
+ m_image_available_semaphore = nullptr;
+ }
+
+ if (m_rendering_finished_semaphore)
+ {
+ vkDestroySemaphore(g_vulkan_context->GetDevice(), m_rendering_finished_semaphore, nullptr);
+ m_rendering_finished_semaphore = nullptr;
+ }
+}
+
+void Renderer::RenderText(const std::string& text, int left, int top, u32 color)
+{
+ u32 backbuffer_width = m_swap_chain->GetWidth();
+ u32 backbuffer_height = m_swap_chain->GetHeight();
+
+ m_raster_font->PrintMultiLineText(m_swap_chain->GetRenderPass(), text,
+ left * 2.0f / static_cast<float>(backbuffer_width) - 1,
+ 1 - top * 2.0f / static_cast<float>(backbuffer_height),
+ backbuffer_width, backbuffer_height, color);
+}
+
+u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data)
+{
+ if (type == PEEK_COLOR)
+ {
+ u32 color = m_framebuffer_mgr->PeekEFBColor(m_state_tracker.get(), x, y);
+
+ // a little-endian value is expected to be returned
+ color = ((color & 0xFF00FF00) | ((color >> 16) & 0xFF) | ((color << 16) & 0xFF0000));
+
+ // check what to do with the alpha channel (GX_PokeAlphaRead)
+ PixelEngine::UPEAlphaReadReg alpha_read_mode = PixelEngine::GetAlphaReadMode();
+
+ if (bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24)
+ {
+ color = RGBA8ToRGBA6ToRGBA8(color);
+ }
+ else if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16)
+ {
+ color = RGBA8ToRGB565ToRGBA8(color);
+ }
+ if (bpmem.zcontrol.pixel_format != PEControl::RGBA6_Z24)
+ {
+ color |= 0xFF000000;
+ }
+
+ if (alpha_read_mode.ReadMode == 2)
+ {
+ return color; // GX_READ_NONE
+ }
+ else if (alpha_read_mode.ReadMode == 1)
+ {
+ return color | 0xFF000000; // GX_READ_FF
+ }
+ else /*if(alpha_read_mode.ReadMode == 0)*/
+ {
+ return color & 0x00FFFFFF; // GX_READ_00
+ }
+ }
+ else // if (type == PEEK_Z)
+ {
+ // Depth buffer is inverted for improved precision near far plane
+ float depth = 1.0f - m_framebuffer_mgr->PeekEFBDepth(m_state_tracker.get(), x, y);
+ u32 ret = 0;
+
+ if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16)
+ {
+ // if Z is in 16 bit format you must return a 16 bit integer
+ ret = MathUtil::Clamp<u32>(static_cast<u32>(depth * 65536.0f), 0, 0xFFFF);
+ }
+ else
+ {
+ ret = MathUtil::Clamp<u32>(static_cast<u32>(depth * 16777216.0f), 0, 0xFFFFFF);
+ }
+
+ return ret;
+ }
+}
+
+void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points)
+{
+ if (type == POKE_COLOR)
+ {
+ for (size_t i = 0; i < num_points; i++)
+ {
+ // Convert to expected format (BGRA->RGBA)
+ // TODO: Check alpha, depending on mode?
+ const EfbPokeData& point = points[i];
+ u32 color = ((point.data & 0xFF00FF00) | ((point.data >> 16) & 0xFF) |
+ ((point.data << 16) & 0xFF0000));
+ m_framebuffer_mgr->PokeEFBColor(m_state_tracker.get(), point.x, point.y, color);
+ }
+ }
+ else // if (type == POKE_Z)
+ {
+ for (size_t i = 0; i < num_points; i++)
+ {
+ // Convert to floating-point depth.
+ const EfbPokeData& point = points[i];
+ float depth = (1.0f - float(point.data & 0xFFFFFF) / 16777216.0f);
+ m_framebuffer_mgr->PokeEFBDepth(m_state_tracker.get(), point.x, point.y, depth);
+ }
+ }
+}
+
+u16 Renderer::BBoxRead(int index)
+{
+ s32 value = m_bounding_box->Get(m_state_tracker.get(), static_cast<size_t>(index));
+
+ // Here we get the min/max value of the truncated position of the upscaled framebuffer.
+ // So we have to correct them to the unscaled EFB sizes.
+ if (index < 2)
+ {
+ // left/right
+ value = value * EFB_WIDTH / s_target_width;
+ }
+ else
+ {
+ // up/down
+ value = value * EFB_HEIGHT / s_target_height;
+ }
+
+ // fix max values to describe the outer border
+ if (index & 1)
+ value++;
+
+ return static_cast<u16>(value);
+}
+
+void Renderer::BBoxWrite(int index, u16 value)
+{
+ s32 scaled_value = static_cast<s32>(value);
+
+ // fix max values to describe the outer border
+ if (index & 1)
+ scaled_value--;
+
+ // scale to internal resolution
+ if (index < 2)
+ {
+ // left/right
+ scaled_value = scaled_value * s_target_width / EFB_WIDTH;
+ }
+ else
+ {
+ // up/down
+ scaled_value = scaled_value * s_target_height / EFB_HEIGHT;
+ }
+
+ m_bounding_box->Set(m_state_tracker.get(), static_cast<size_t>(index), scaled_value);
+}
+
+TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
+{
+ TargetRectangle result;
+ result.left = EFBToScaledX(rc.left);
+ result.top = EFBToScaledY(rc.top);
+ result.right = EFBToScaledX(rc.right);
+ result.bottom = EFBToScaledY(rc.bottom);
+ return result;
+}
+
+void Renderer::BeginFrame()
+{
+ // Activate a new command list, and restore state ready for the next draw
+ g_command_buffer_mgr->ActivateCommandBuffer();
+
+ // Ensure that the state tracker rebinds everything, and allocates a new set
+ // of descriptors out of the next pool.
+ m_state_tracker->InvalidateDescriptorSets();
+ m_state_tracker->SetPendingRebind();
+}
+
+void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha_enable,
+ bool z_enable, u32 color, u32 z)
+{
+ // Native -> EFB coordinates
+ TargetRectangle target_rc = Renderer::ConvertEFBRectangle(rc);
+ VkRect2D target_vk_rc = {
+ {target_rc.left, target_rc.top},
+ {static_cast<uint32_t>(target_rc.GetWidth()), static_cast<uint32_t>(target_rc.GetHeight())}};
+
+ // Convert RGBA8 -> floating-point values.
+ VkClearValue clear_color_value = {};
+ VkClearValue clear_depth_value = {};
+ clear_color_value.color.float32[0] = static_cast<float>((color >> 16) & 0xFF) / 255.0f;
+ clear_color_value.color.float32[1] = static_cast<float>((color >> 8) & 0xFF) / 255.0f;
+ clear_color_value.color.float32[2] = static_cast<float>((color >> 0) & 0xFF) / 255.0f;
+ clear_color_value.color.float32[3] = static_cast<float>((color >> 24) & 0xFF) / 255.0f;
+ clear_depth_value.depthStencil.depth = (1.0f - (static_cast<float>(z & 0xFFFFFF) / 16777216.0f));
+
+ // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha
+ // channel to 0xFF. This hopefully allows us to use the fast path in most cases.
+ if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16 ||
+ bpmem.zcontrol.pixel_format == PEControl::RGB8_Z24 ||
+ bpmem.zcontrol.pixel_format == PEControl::Z24)
+ {
+ // Force alpha writes, and set the color to 0xFF.
+ alpha_enable = true;
+ color |= 0xFF000000;
+ }
+
+ // If we're not in a render pass (start of the frame), we can use a clear render pass
+ // to discard the data, rather than loading and then clearing.
+ bool use_clear_render_pass = (color_enable && alpha_enable && z_enable);
+ if (m_state_tracker->InRenderPass())
+ {
+ // Prefer not to end a render pass just to do a clear.
+ use_clear_render_pass = false;
+ }
+
+ // Fastest path: Use a render pass to clear the buffers.
+ if (use_clear_render_pass)
+ {
+ VkClearValue clear_values[2] = {clear_color_value, clear_depth_value};
+ m_state_tracker->BeginClearRenderPass(target_vk_rc, clear_values);
+ return;
+ }
+
+ // Fast path: Use vkCmdClearAttachments to clear the buffers within a render path
+ // We can't use this when preserving alpha but clearing color.
+ {
+ VkClearAttachment clear_attachments[2];
+ uint32_t num_clear_attachments = 0;
+ if (color_enable && alpha_enable)
+ {
+ clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ clear_attachments[num_clear_attachments].colorAttachment = 0;
+ clear_attachments[num_clear_attachments].clearValue = clear_color_value;
+ num_clear_attachments++;
+ color_enable = false;
+ alpha_enable = false;
+ }
+ if (z_enable)
+ {
+ clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+ clear_attachments[num_clear_attachments].colorAttachment = 0;
+ clear_attachments[num_clear_attachments].clearValue = clear_depth_value;
+ num_clear_attachments++;
+ z_enable = false;
+ }
+ if (num_clear_attachments > 0)
+ {
+ VkClearRect clear_rect = {target_vk_rc, 0, m_framebuffer_mgr->GetEFBLayers()};
+ if (!m_state_tracker->IsWithinRenderArea(target_vk_rc.offset.x, target_vk_rc.offset.y,
+ target_vk_rc.extent.width,
+ target_vk_rc.extent.height))
+ {
+ m_state_tracker->EndClearRenderPass();
+ }
+ m_state_tracker->BeginRenderPass();
+
+ vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments,
+ clear_attachments, 1, &clear_rect);
+ }
+ }
+
+ // Anything left over for the slow path?
+ if (!color_enable && !alpha_enable && !z_enable)
+ return;
+
+ // Clearing must occur within a render pass.
+ if (!m_state_tracker->IsWithinRenderArea(target_vk_rc.offset.x, target_vk_rc.offset.y,
+ target_vk_rc.extent.width, target_vk_rc.extent.height))
+ {
+ m_state_tracker->EndClearRenderPass();
+ }
+ m_state_tracker->BeginRenderPass();
+ m_state_tracker->SetPendingRebind();
+
+ // Mask away the appropriate colors and use a shader
+ BlendState blend_state = Util::GetNoBlendingBlendState();
+ u32 write_mask = 0;
+ if (color_enable)
+ write_mask |= VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
+ if (alpha_enable)
+ write_mask |= VK_COLOR_COMPONENT_A_BIT;
+ blend_state.write_mask = write_mask;
+
+ DepthStencilState depth_state = Util::GetNoDepthTestingDepthStencilState();
+ depth_state.test_enable = z_enable ? VK_TRUE : VK_FALSE;
+ depth_state.write_enable = z_enable ? VK_TRUE : VK_FALSE;
+ depth_state.compare_op = VK_COMPARE_OP_ALWAYS;
+
+ RasterizationState rs_state = Util::GetNoCullRasterizationState();
+ rs_state.per_sample_shading = g_ActiveConfig.bSSAA ? VK_TRUE : VK_FALSE;
+ rs_state.samples = m_framebuffer_mgr->GetEFBSamples();
+
+ // No need to start a new render pass, but we do need to restore viewport state
+ UtilityShaderDraw draw(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetStandardPipelineLayout(),
+ m_framebuffer_mgr->GetEFBLoadRenderPass(), g_object_cache->GetPassthroughVertexShader(),
+ g_object_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader);
+
+ draw.SetRasterizationState(rs_state);
+ draw.SetDepthStencilState(depth_state);
+ draw.SetBlendState(blend_state);
+
+ draw.DrawColoredQuad(target_rc.left, target_rc.top, target_rc.GetWidth(), target_rc.GetHeight(),
+ clear_color_value.color.float32[0], clear_color_value.color.float32[1],
+ clear_color_value.color.float32[2], clear_color_value.color.float32[3],
+ clear_depth_value.depthStencil.depth);
+}
+
+void Renderer::ReinterpretPixelData(unsigned int convtype)
+{
+ m_state_tracker->EndRenderPass();
+ m_state_tracker->SetPendingRebind();
+ m_framebuffer_mgr->ReinterpretPixelData(convtype);
+
+ // EFB framebuffer has now changed, so update accordingly.
+ BindEFBToStateTracker();
+}
+
+void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height,
+ const EFBRectangle& rc, float gamma)
+{
+ // Flush any pending EFB pokes.
+ m_framebuffer_mgr->FlushEFBPokes(m_state_tracker.get());
+
+ // End the current render pass.
+ m_state_tracker->EndRenderPass();
+ m_state_tracker->OnEndFrame();
+
+ // Scale the source rectangle to the selected internal resolution.
+ TargetRectangle source_rc = Renderer::ConvertEFBRectangle(rc);
+
+ // Target rectangle can change if the VI aspect ratio changes.
+ UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height);
+
+ // Transition the EFB render target to a shader resource.
+ VkRect2D src_region = {{0, 0},
+ {m_framebuffer_mgr->GetEFBWidth(), m_framebuffer_mgr->GetEFBHeight()}};
+ Texture2D* efb_color_texture =
+ m_framebuffer_mgr->ResolveEFBColorTexture(m_state_tracker.get(), src_region);
+ efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ // Draw to the screenshot buffer if needed.
+ bool needs_screenshot = (s_bScreenshot || SConfig::GetInstance().m_DumpFrames);
+ if (needs_screenshot && DrawScreenshot(source_rc, efb_color_texture))
+ {
+ if (s_bScreenshot)
+ WriteScreenshot();
+
+ if (SConfig::GetInstance().m_DumpFrames)
+ WriteFrameDump();
+ }
+ else
+ {
+ // Stop frame dump if requested.
+ if (bAVIDumping)
+ StopFrameDump();
+ }
+
+ // Ensure the worker thread is not still submitting a previous command buffer.
+ // In other words, the last frame has been submitted (otherwise the next call would
+ // be a race, as the image may not have been consumed yet).
+ g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
+
+ // Draw to the screen if we have a swap chain.
+ if (m_swap_chain)
+ {
+ DrawScreen(source_rc, efb_color_texture);
+
+ // Submit the current command buffer, signaling rendering finished semaphore when it's done
+ // Because this final command buffer is rendering to the swap chain, we need to wait for
+ // the available semaphore to be signaled before executing the buffer. This final submission
+ // can happen off-thread in the background while we're preparing the next frame.
+ g_command_buffer_mgr->SubmitCommandBuffer(
+ true, m_image_available_semaphore, m_rendering_finished_semaphore,
+ m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex());
+ }
+ else
+ {
+ // No swap chain, just execute command buffer.
+ g_command_buffer_mgr->SubmitCommandBuffer(true);
+ }
+
+ // Prep for the next frame (get command buffer ready) before doing anything else.
+ BeginFrame();
+
+ // Restore the EFB color texture to color attachment ready for rendering.
+ m_framebuffer_mgr->GetEFBColorTexture()->TransitionToLayout(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+ // Clean up stale textures
+ TextureCacheBase::Cleanup(frameCount);
+
+ // Handle window resizes.
+ CheckForTargetResize(fb_width, fb_stride, fb_height);
+ CheckForSurfaceChange();
+
+ // Determine what has changed in the config.
+ CheckForConfigChanges();
+}
+
+void Renderer::DrawScreen(const TargetRectangle& src_rect, const Texture2D* src_tex)
+{
+ // Grab the next image from the swap chain in preparation for drawing the window.
+ VkResult res = m_swap_chain->AcquireNextImage(m_image_available_semaphore);
+ if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
+ {
+ // Window has been resized. Update the swap chain and try again.
+ ResizeSwapChain();
+ res = m_swap_chain->AcquireNextImage(m_image_available_semaphore);
+ }
+ if (res != VK_SUCCESS)
+ PanicAlert("Failed to grab image from swap chain");
+
+ // Transition from undefined (or present src, but it can be substituted) to
+ // color attachment ready for writing. These transitions must occur outside
+ // a render pass, unless the render pass declares a self-dependency.
+ Texture2D* backbuffer = m_swap_chain->GetCurrentTexture();
+ backbuffer->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
+ backbuffer->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+ // Blit the EFB to the back buffer (Swap chain)
+ UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ g_object_cache->GetStandardPipelineLayout(), m_swap_chain->GetRenderPass(),
+ g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE,
+ m_blit_fragment_shader);
+
+ // Begin the present render pass
+ VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
+ VkRect2D target_region = {{0, 0}, {backbuffer->GetWidth(), backbuffer->GetHeight()}};
+ draw.BeginRenderPass(m_swap_chain->GetCurrentFramebuffer(), target_region, &clear_value);
+
+ // Copy EFB -> backbuffer
+ const TargetRectangle& dst_rect = GetTargetRectangle();
+ BlitScreen(m_swap_chain->GetRenderPass(), dst_rect, src_rect, src_tex, true);
+
+ // OSD stuff
+ Util::SetViewportAndScissor(g_command_buffer_mgr->GetCurrentCommandBuffer(), 0, 0,
+ backbuffer->GetWidth(), backbuffer->GetHeight());
+ DrawDebugText();
+
+ // Do our OSD callbacks
+ OSD::DoCallbacks(OSD::CallbackType::OnFrame);
+ OSD::DrawMessages();
+
+ // End drawing to backbuffer
+ draw.EndRenderPass();
+
+ // Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
+ // to it have finished before present.
+ backbuffer->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
+}
+
+bool Renderer::DrawScreenshot(const TargetRectangle& src_rect, const Texture2D* src_tex)
+{
+ u32 width = std::max(1u, static_cast<u32>(s_backbuffer_width));
+ u32 height = std::max(1u, static_cast<u32>(s_backbuffer_height));
+ if (!ResizeScreenshotBuffer(width, height))
+ return false;
+
+ VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
+ VkClearRect clear_rect = {{{0, 0}, {width, height}}, 0, 1};
+ VkClearAttachment clear_attachment = {VK_IMAGE_ASPECT_COLOR_BIT, 0, clear_value};
+ VkRenderPassBeginInfo info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ nullptr,
+ m_framebuffer_mgr->GetColorCopyForReadbackRenderPass(),
+ m_screenshot_framebuffer,
+ {{0, 0}, {width, height}},
+ 1,
+ &clear_value};
+ vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &info,
+ VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), 1, &clear_attachment, 1,
+ &clear_rect);
+ BlitScreen(m_framebuffer_mgr->GetColorCopyForReadbackRenderPass(), GetTargetRectangle(), src_rect,
+ src_tex, true);
+ vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer());
+
+ // Copy to the readback texture.
+ m_screenshot_readback_texture->CopyFromImage(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), m_screenshot_render_texture->GetImage(),
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, width, height, 0, 0);
+
+ // Wait for the command buffer to complete.
+ g_command_buffer_mgr->ExecuteCommandBuffer(false, true);
+ return true;
+}
+
+void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
+ const TargetRectangle& src_rect, const Texture2D* src_tex,
+ bool linear_filter)
+{
+ // We could potentially use vkCmdBlitImage here.
+ VkSampler sampler =
+ linear_filter ? g_object_cache->GetLinearSampler() : g_object_cache->GetPointSampler();
+
+ // Set up common data
+ UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ g_object_cache->GetStandardPipelineLayout(), render_pass,
+ g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE,
+ m_blit_fragment_shader);
+
+ draw.SetPSSampler(0, src_tex->GetView(), sampler);
+
+ if (g_ActiveConfig.iStereoMode == STEREO_SBS || g_ActiveConfig.iStereoMode == STEREO_TAB)
+ {
+ TargetRectangle left_rect;
+ TargetRectangle right_rect;
+ if (g_ActiveConfig.iStereoMode == STEREO_TAB)
+ ConvertStereoRectangle(dst_rect, right_rect, left_rect);
+ else
+ ConvertStereoRectangle(dst_rect, left_rect, right_rect);
+
+ draw.DrawQuad(left_rect.left, left_rect.top, left_rect.GetWidth(), left_rect.GetHeight(),
+ src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(),
+ src_tex->GetWidth(), src_tex->GetHeight());
+
+ draw.DrawQuad(right_rect.left, right_rect.top, right_rect.GetWidth(), right_rect.GetHeight(),
+ src_rect.left, src_rect.top, 1, src_rect.GetWidth(), src_rect.GetHeight(),
+ src_tex->GetWidth(), src_tex->GetHeight());
+ }
+ else
+ {
+ draw.DrawQuad(dst_rect.left, dst_rect.top, dst_rect.GetWidth(), dst_rect.GetHeight(),
+ src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(),
+ src_tex->GetWidth(), src_tex->GetHeight());
+ }
+}
+
+bool Renderer::ResizeScreenshotBuffer(u32 new_width, u32 new_height)
+{
+ if (m_screenshot_render_texture && m_screenshot_render_texture->GetWidth() == new_width &&
+ m_screenshot_render_texture->GetHeight() == new_height)
+ {
+ return true;
+ }
+
+ if (m_screenshot_framebuffer != VK_NULL_HANDLE)
+ {
+ vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_screenshot_framebuffer, nullptr);
+ m_screenshot_framebuffer = VK_NULL_HANDLE;
+ }
+
+ m_screenshot_render_texture =
+ Texture2D::Create(new_width, new_height, 1, 1, EFB_COLOR_TEXTURE_FORMAT,
+ VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+
+ m_screenshot_readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, new_width,
+ new_height, EFB_COLOR_TEXTURE_FORMAT);
+ if (!m_screenshot_render_texture || !m_screenshot_readback_texture ||
+ !m_screenshot_readback_texture->Map())
+ {
+ WARN_LOG(VIDEO, "Failed to resize screenshot render texture");
+ m_screenshot_render_texture.reset();
+ m_screenshot_readback_texture.reset();
+ return false;
+ }
+
+ VkImageView attachment = m_screenshot_render_texture->GetView();
+ VkFramebufferCreateInfo info = {};
+ info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ info.renderPass = m_framebuffer_mgr->GetColorCopyForReadbackRenderPass();
+ info.attachmentCount = 1;
+ info.pAttachments = &attachment;
+ info.width = new_width;
+ info.height = new_height;
+ info.layers = 1;
+
+ VkResult res =
+ vkCreateFramebuffer(g_vulkan_context->GetDevice(), &info, nullptr, &m_screenshot_framebuffer);
+ if (res != VK_SUCCESS)
+ {
+ WARN_LOG(VIDEO, "Failed to resize screenshot framebuffer");
+ m_screenshot_render_texture.reset();
+ m_screenshot_readback_texture.reset();
+ return false;
+ }
+
+ // Render pass expects texture is in transfer src to start with.
+ m_screenshot_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+ return true;
+}
+
+void Renderer::DestroyScreenshotResources()
+{
+ if (m_screenshot_framebuffer != VK_NULL_HANDLE)
+ {
+ vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_screenshot_framebuffer, nullptr);
+ m_screenshot_framebuffer = VK_NULL_HANDLE;
+ }
+
+ m_screenshot_render_texture.reset();
+ m_screenshot_readback_texture.reset();
+}
+
+void Renderer::WriteScreenshot()
+{
+ std::lock_guard<std::mutex> guard(s_criticalScreenshot);
+
+ if (!TextureToPng(reinterpret_cast<u8*>(m_screenshot_readback_texture->GetMapPointer()),
+ static_cast<int>(m_screenshot_readback_texture->GetRowStride()),
+ s_sScreenshotName, static_cast<int>(m_screenshot_render_texture->GetWidth()),
+ static_cast<int>(m_screenshot_render_texture->GetHeight()), false))
+ {
+ WARN_LOG(VIDEO, "Failed to write screenshot to %s", s_sScreenshotName.c_str());
+ }
+
+ s_sScreenshotName.clear();
+ s_bScreenshot = false;
+ s_screenshotCompleted.Set();
+}
+
+void Renderer::WriteFrameDump()
+{
+#if defined(HAVE_LIBAV) || defined(_WIN32)
+ if (!bLastFrameDumped)
+ {
+ bLastFrameDumped = true;
+ bAVIDumping = AVIDump::Start(static_cast<int>(m_screenshot_render_texture->GetWidth()),
+ static_cast<int>(m_screenshot_render_texture->GetHeight()),
+ AVIDump::DumpFormat::FORMAT_RGBA);
+
+ if (!bAVIDumping)
+ {
+ OSD::AddMessage("Failed to start frame dumping.", 2000);
+ return;
+ }
+
+ OSD::AddMessage(StringFromFormat("Frame dumping started (%ux%u RGBA8).",
+ m_screenshot_render_texture->GetWidth(),
+ m_screenshot_render_texture->GetHeight()),
+ 2000);
+ }
+
+ if (bAVIDumping)
+ {
+ AVIDump::AddFrame(reinterpret_cast<const u8*>(m_screenshot_readback_texture->GetMapPointer()),
+ static_cast<int>(m_screenshot_render_texture->GetWidth()),
+ static_cast<int>(m_screenshot_render_texture->GetHeight()));
+ }
+#else
+ if (!bLastFrameDumped)
+ {
+ OSD::AddMessage("Dumping frames not supported", 2000);
+ bLastFrameDumped = true;
+ }
+#endif
+}
+
+void Renderer::StopFrameDump()
+{
+#if defined(HAVE_LIBAV) || defined(_WIN32)
+ if (bAVIDumping)
+ {
+ OSD::AddMessage("Frame dumping stopped.", 2000);
+ bAVIDumping = false;
+ bLastFrameDumped = false;
+ AVIDump::Stop();
+ }
+#endif
+}
+
+void Renderer::CheckForTargetResize(u32 fb_width, u32 fb_stride, u32 fb_height)
+{
+ if (FramebufferManagerBase::LastXfbWidth() != fb_stride ||
+ FramebufferManagerBase::LastXfbHeight() != fb_height)
+ {
+ u32 last_w = (fb_stride < 1 || fb_stride > MAX_XFB_WIDTH) ? MAX_XFB_WIDTH : fb_stride;
+ u32 last_h = (fb_height < 1 || fb_height > MAX_XFB_HEIGHT) ? MAX_XFB_HEIGHT : fb_height;
+ FramebufferManagerBase::SetLastXfbWidth(last_w);
+ FramebufferManagerBase::SetLastXfbHeight(last_h);
+ }
+
+ // This call is needed for auto-resizing to work.
+ SetWindowSize(static_cast<int>(fb_stride), static_cast<int>(fb_height));
+}
+
+void Renderer::CheckForSurfaceChange()
+{
+ if (!s_surface_needs_change.IsSet())
+ return;
+
+ u32 old_width = m_swap_chain ? m_swap_chain->GetWidth() : 0;
+ u32 old_height = m_swap_chain ? m_swap_chain->GetHeight() : 0;
+
+ // Wait for the GPU to catch up since we're going to destroy the swap chain.
+ g_command_buffer_mgr->WaitForGPUIdle();
+
+ // Fast path, if the surface handle is the same, the window has just been resized.
+ if (m_swap_chain && s_new_surface_handle == m_swap_chain->GetNativeHandle())
+ {
+ INFO_LOG(VIDEO, "Detected window resize.");
+ ResizeSwapChain();
+
+ // Notify the main thread we are done.
+ s_surface_needs_change.Clear();
+ s_new_surface_handle = nullptr;
+ s_surface_changed.Set();
+ }
+ else
+ {
+ // Did we previously have a swap chain?
+ if (m_swap_chain)
+ {
+ if (!s_new_surface_handle)
+ {
+ // If there is no surface now, destroy the swap chain.
+ m_swap_chain.reset();
+ }
+ else
+ {
+ // Recreate the surface. If this fails we're in trouble.
+ if (!m_swap_chain->RecreateSurface(s_new_surface_handle))
+ PanicAlert("Failed to recreate Vulkan surface. Cannot continue.");
+ }
+ }
+ else
+ {
+ // Previously had no swap chain. So create one.
+ VkSurfaceKHR surface = SwapChain::CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(),
+ s_new_surface_handle);
+ if (surface != VK_NULL_HANDLE)
+ {
+ m_swap_chain = SwapChain::Create(s_new_surface_handle, surface);
+ if (!m_swap_chain)
+ PanicAlert("Failed to create swap chain.");
+ }
+ else
+ {
+ PanicAlert("Failed to create surface.");
+ }
+ }
+
+ // Notify calling thread.
+ s_surface_needs_change.Clear();
+ s_new_surface_handle = nullptr;
+ s_surface_changed.Set();
+ }
+
+ if (m_swap_chain)
+ {
+ // Handle case where the dimensions are now different
+ if (old_width != m_swap_chain->GetWidth() || old_height != m_swap_chain->GetHeight())
+ OnSwapChainResized();
+ }
+}
+
+void Renderer::CheckForConfigChanges()
+{
+ // Compare g_Config to g_ActiveConfig to determine what has changed before copying.
+ bool vsync_changed = (g_Config.bVSync != g_ActiveConfig.bVSync);
+ bool msaa_changed = (g_Config.iMultisamples != g_ActiveConfig.iMultisamples);
+ bool ssaa_changed = (g_Config.bSSAA != g_ActiveConfig.bSSAA);
+ bool anisotropy_changed = (g_Config.iMaxAnisotropy != g_ActiveConfig.iMaxAnisotropy);
+ bool force_texture_filtering_changed =
+ (g_Config.bForceFiltering != g_ActiveConfig.bForceFiltering);
+ bool stereo_changed = (g_Config.iStereoMode != g_ActiveConfig.iStereoMode);
+
+ // Copy g_Config to g_ActiveConfig.
+ UpdateActiveConfig();
+
+ // MSAA samples changed, we need to recreate the EFB render pass, and all shaders.
+ if (msaa_changed)
+ {
+ m_framebuffer_mgr->RecreateRenderPass();
+ m_framebuffer_mgr->ResizeEFBTextures();
+ }
+
+ // SSAA changed on/off, we can leave the buffers/render pass, but have to recompile shaders.
+ if (msaa_changed || ssaa_changed)
+ {
+ BindEFBToStateTracker();
+ m_framebuffer_mgr->RecompileShaders();
+ g_object_cache->ClearPipelineCache();
+ }
+
+ // Handle internal resolution changes.
+ if (s_last_efb_scale != g_ActiveConfig.iEFBScale)
+ {
+ s_last_efb_scale = g_ActiveConfig.iEFBScale;
+ if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height))
+ ResizeEFBTextures();
+ }
+
+ // Handle stereoscopy mode changes.
+ if (stereo_changed)
+ {
+ ResizeEFBTextures();
+ BindEFBToStateTracker();
+ RecompileShaders();
+ }
+
+ // For vsync, we need to change the present mode, which means recreating the swap chain.
+ if (vsync_changed)
+ ResizeSwapChain();
+
+ // Wipe sampler cache if force texture filtering or anisotropy changes.
+ if (anisotropy_changed || force_texture_filtering_changed)
+ ResetSamplerStates();
+}
+
+void Renderer::OnSwapChainResized()
+{
+ s_backbuffer_width = m_swap_chain->GetWidth();
+ s_backbuffer_height = m_swap_chain->GetHeight();
+ FramebufferManagerBase::SetLastXfbWidth(MAX_XFB_WIDTH);
+ FramebufferManagerBase::SetLastXfbHeight(MAX_XFB_HEIGHT);
+ UpdateDrawRectangle(s_backbuffer_width, s_backbuffer_height);
+ if (CalculateTargetSize(s_backbuffer_width, s_backbuffer_height))
+ ResizeEFBTextures();
+
+ PixelShaderManager::SetEfbScaleChanged();
+}
+
+void Renderer::BindEFBToStateTracker()
+{
+ // Update framebuffer in state tracker
+ VkRect2D framebuffer_size = {
+ {0, 0}, {m_framebuffer_mgr->GetEFBWidth(), m_framebuffer_mgr->GetEFBHeight()}};
+ m_state_tracker->SetRenderPass(m_framebuffer_mgr->GetEFBLoadRenderPass(),
+ m_framebuffer_mgr->GetEFBClearRenderPass());
+ m_state_tracker->SetFramebuffer(m_framebuffer_mgr->GetEFBFramebuffer(), framebuffer_size);
+
+ // Update rasterization state with MSAA info
+ RasterizationState rs_state = {};
+ rs_state.bits = m_state_tracker->GetRasterizationState().bits;
+ rs_state.samples = m_framebuffer_mgr->GetEFBSamples();
+ rs_state.per_sample_shading = g_ActiveConfig.bSSAA ? VK_TRUE : VK_FALSE;
+ m_state_tracker->SetRasterizationState(rs_state);
+}
+
+void Renderer::ResizeEFBTextures()
+{
+ // Ensure the GPU is finished with the current EFB textures.
+ g_command_buffer_mgr->WaitForGPUIdle();
+
+ m_framebuffer_mgr->ResizeEFBTextures();
+ s_last_efb_scale = g_ActiveConfig.iEFBScale;
+
+ BindEFBToStateTracker();
+
+ // Viewport and scissor rect have to be reset since they will be scaled differently.
+ SetViewport();
+ BPFunctions::SetScissor();
+}
+
+void Renderer::ResizeSwapChain()
+{
+ // The worker thread may still be submitting a present on this swap chain.
+ g_command_buffer_mgr->WaitForGPUIdle();
+
+ // It's now safe to resize the swap chain.
+ if (!m_swap_chain->ResizeSwapChain())
+ PanicAlert("Failed to resize swap chain.");
+
+ OnSwapChainResized();
+}
+
+void Renderer::ApplyState(bool bUseDstAlpha)
+{
+}
+
+void Renderer::ResetAPIState()
+{
+ // End the EFB render pass if active
+ m_state_tracker->EndRenderPass();
+}
+
+void Renderer::RestoreAPIState()
+{
+ // Instruct the state tracker to re-bind everything before the next draw
+ m_state_tracker->SetPendingRebind();
+}
+
+void Renderer::SetGenerationMode()
+{
+ RasterizationState new_rs_state = {};
+ new_rs_state.bits = m_state_tracker->GetRasterizationState().bits;
+
+ switch (bpmem.genMode.cullmode)
+ {
+ case GenMode::CULL_NONE:
+ new_rs_state.cull_mode = VK_CULL_MODE_NONE;
+ break;
+ case GenMode::CULL_BACK:
+ new_rs_state.cull_mode = VK_CULL_MODE_BACK_BIT;
+ break;
+ case GenMode::CULL_FRONT:
+ new_rs_state.cull_mode = VK_CULL_MODE_FRONT_BIT;
+ break;
+ case GenMode::CULL_ALL:
+ new_rs_state.cull_mode = VK_CULL_MODE_FRONT_AND_BACK;
+ break;
+ default:
+ new_rs_state.cull_mode = VK_CULL_MODE_NONE;
+ break;
+ }
+
+ m_state_tracker->SetRasterizationState(new_rs_state);
+}
+
+void Renderer::SetDepthMode()
+{
+ DepthStencilState new_ds_state = {};
+ new_ds_state.test_enable = bpmem.zmode.testenable ? VK_TRUE : VK_FALSE;
+ new_ds_state.write_enable = bpmem.zmode.updateenable ? VK_TRUE : VK_FALSE;
+
+ // Inverted depth, hence these are swapped
+ switch (bpmem.zmode.func)
+ {
+ case ZMode::NEVER:
+ new_ds_state.compare_op = VK_COMPARE_OP_NEVER;
+ break;
+ case ZMode::LESS:
+ new_ds_state.compare_op = VK_COMPARE_OP_GREATER;
+ break;
+ case ZMode::EQUAL:
+ new_ds_state.compare_op = VK_COMPARE_OP_EQUAL;
+ break;
+ case ZMode::LEQUAL:
+ new_ds_state.compare_op = VK_COMPARE_OP_GREATER_OR_EQUAL;
+ break;
+ case ZMode::GREATER:
+ new_ds_state.compare_op = VK_COMPARE_OP_LESS;
+ break;
+ case ZMode::NEQUAL:
+ new_ds_state.compare_op = VK_COMPARE_OP_NOT_EQUAL;
+ break;
+ case ZMode::GEQUAL:
+ new_ds_state.compare_op = VK_COMPARE_OP_LESS_OR_EQUAL;
+ break;
+ case ZMode::ALWAYS:
+ new_ds_state.compare_op = VK_COMPARE_OP_ALWAYS;
+ break;
+ default:
+ new_ds_state.compare_op = VK_COMPARE_OP_ALWAYS;
+ break;
+ }
+
+ m_state_tracker->SetDepthStencilState(new_ds_state);
+}
+
+void Renderer::SetColorMask()
+{
+ u32 color_mask = 0;
+
+ if (bpmem.alpha_test.TestResult() != AlphaTest::FAIL)
+ {
+ if (bpmem.blendmode.alphaupdate && bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24)
+ color_mask |= VK_COLOR_COMPONENT_A_BIT;
+ if (bpmem.blendmode.colorupdate)
+ color_mask |= VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
+ }
+
+ BlendState new_blend_state = {};
+ new_blend_state.bits = m_state_tracker->GetBlendState().bits;
+ new_blend_state.write_mask = color_mask;
+
+ m_state_tracker->SetBlendState(new_blend_state);
+}
+
+void Renderer::SetBlendMode(bool force_update)
+{
+ BlendState new_blend_state = {};
+ new_blend_state.bits = m_state_tracker->GetBlendState().bits;
+
+ // Fast path for blending disabled
+ if (!bpmem.blendmode.blendenable)
+ {
+ new_blend_state.blend_enable = VK_FALSE;
+ new_blend_state.blend_op = VK_BLEND_OP_ADD;
+ new_blend_state.src_blend = VK_BLEND_FACTOR_ONE;
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_ZERO;
+ new_blend_state.alpha_blend_op = VK_BLEND_OP_ADD;
+ new_blend_state.src_alpha_blend = VK_BLEND_FACTOR_ONE;
+ new_blend_state.dst_alpha_blend = VK_BLEND_FACTOR_ZERO;
+ m_state_tracker->SetBlendState(new_blend_state);
+ return;
+ }
+ // Fast path for subtract blending
+ else if (bpmem.blendmode.subtract)
+ {
+ new_blend_state.blend_enable = VK_TRUE;
+ new_blend_state.blend_op = VK_BLEND_OP_REVERSE_SUBTRACT;
+ new_blend_state.src_blend = VK_BLEND_FACTOR_ONE;
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_ONE;
+ new_blend_state.alpha_blend_op = VK_BLEND_OP_REVERSE_SUBTRACT;
+ new_blend_state.src_alpha_blend = VK_BLEND_FACTOR_ONE;
+ new_blend_state.dst_alpha_blend = VK_BLEND_FACTOR_ONE;
+ m_state_tracker->SetBlendState(new_blend_state);
+ return;
+ }
+
+ // Our render target always uses an alpha channel, so we need to override the blend functions to
+ // assume a destination alpha of 1 if the render target isn't supposed to have an alpha channel.
+ bool target_has_alpha = bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24;
+ bool use_dst_alpha = bpmem.dstalpha.enable && bpmem.blendmode.alphaupdate && target_has_alpha &&
+ g_vulkan_context->SupportsDualSourceBlend();
+
+ new_blend_state.blend_enable = VK_TRUE;
+ new_blend_state.blend_op = VK_BLEND_OP_ADD;
+
+ switch (bpmem.blendmode.srcfactor)
+ {
+ case BlendMode::ZERO:
+ new_blend_state.src_blend = VK_BLEND_FACTOR_ZERO;
+ break;
+ case BlendMode::ONE:
+ new_blend_state.src_blend = VK_BLEND_FACTOR_ONE;
+ break;
+ case BlendMode::DSTCLR:
+ new_blend_state.src_blend = VK_BLEND_FACTOR_DST_COLOR;
+ break;
+ case BlendMode::INVDSTCLR:
+ new_blend_state.src_blend = VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
+ break;
+ case BlendMode::SRCALPHA:
+ new_blend_state.src_blend =
+ use_dst_alpha ? VK_BLEND_FACTOR_SRC1_ALPHA : VK_BLEND_FACTOR_SRC_ALPHA;
+ break;
+ case BlendMode::INVSRCALPHA:
+ new_blend_state.src_blend =
+ use_dst_alpha ? VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA : VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ break;
+ case BlendMode::DSTALPHA:
+ new_blend_state.src_blend = target_has_alpha ? VK_BLEND_FACTOR_DST_ALPHA : VK_BLEND_FACTOR_ONE;
+ break;
+ case BlendMode::INVDSTALPHA:
+ new_blend_state.src_blend =
+ target_has_alpha ? VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA : VK_BLEND_FACTOR_ZERO;
+ break;
+ default:
+ new_blend_state.src_blend = VK_BLEND_FACTOR_ONE;
+ break;
+ }
+
+ switch (bpmem.blendmode.dstfactor)
+ {
+ case BlendMode::ZERO:
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_ZERO;
+ break;
+ case BlendMode::ONE:
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_ONE;
+ break;
+ case BlendMode::SRCCLR:
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_SRC_COLOR;
+ break;
+ case BlendMode::INVSRCCLR:
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
+ break;
+ case BlendMode::SRCALPHA:
+ new_blend_state.dst_blend =
+ use_dst_alpha ? VK_BLEND_FACTOR_SRC1_ALPHA : VK_BLEND_FACTOR_SRC_ALPHA;
+ break;
+ case BlendMode::INVSRCALPHA:
+ new_blend_state.dst_blend =
+ use_dst_alpha ? VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA : VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ break;
+ case BlendMode::DSTALPHA:
+ new_blend_state.dst_blend = target_has_alpha ? VK_BLEND_FACTOR_DST_ALPHA : VK_BLEND_FACTOR_ONE;
+ break;
+ case BlendMode::INVDSTALPHA:
+ new_blend_state.dst_blend =
+ target_has_alpha ? VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA : VK_BLEND_FACTOR_ZERO;
+ break;
+ default:
+ new_blend_state.dst_blend = VK_BLEND_FACTOR_ONE;
+ break;
+ }
+
+ if (use_dst_alpha)
+ {
+ // Destination alpha sets 1*SRC
+ new_blend_state.alpha_blend_op = VK_BLEND_OP_ADD;
+ new_blend_state.src_alpha_blend = VK_BLEND_FACTOR_ONE;
+ new_blend_state.dst_alpha_blend = VK_BLEND_FACTOR_ZERO;
+ }
+ else
+ {
+ new_blend_state.alpha_blend_op = VK_BLEND_OP_ADD;
+ new_blend_state.src_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.src_blend);
+ new_blend_state.dst_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.dst_blend);
+ }
+
+ m_state_tracker->SetBlendState(new_blend_state);
+}
+
+void Renderer::SetLogicOpMode()
+{
+ BlendState new_blend_state = {};
+ new_blend_state.bits = m_state_tracker->GetBlendState().bits;
+
+ // Does our device support logic ops?
+ bool logic_op_enable = bpmem.blendmode.logicopenable && !bpmem.blendmode.blendenable;
+ if (g_vulkan_context->SupportsLogicOps())
+ {
+ if (logic_op_enable)
+ {
+ static const std::array<VkLogicOp, 16> logic_ops = {
+ {VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_COPY,
+ VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR,
+ VK_LOGIC_OP_NOR, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE,
+ VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND, VK_LOGIC_OP_SET}};
+
+ new_blend_state.logic_op_enable = VK_TRUE;
+ new_blend_state.logic_op = logic_ops[bpmem.blendmode.logicmode];
+ }
+ else
+ {
+ new_blend_state.logic_op_enable = VK_FALSE;
+ new_blend_state.logic_op = VK_LOGIC_OP_CLEAR;
+ }
+
+ m_state_tracker->SetBlendState(new_blend_state);
+ }
+ else
+ {
+ // No logic op support, approximate with blending instead.
+ // This is by no means correct, but necessary for some devices.
+ if (logic_op_enable)
+ {
+ struct LogicOpBlend
+ {
+ VkBlendFactor src_factor;
+ VkBlendOp op;
+ VkBlendFactor dst_factor;
+ };
+ static const std::array<LogicOpBlend, 16> logic_ops = {
+ {{VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ZERO},
+ {VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ZERO},
+ {VK_BLEND_FACTOR_ONE, VK_BLEND_OP_SUBTRACT, VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR},
+ {VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ZERO},
+ {VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_OP_REVERSE_SUBTRACT, VK_BLEND_FACTOR_ONE},
+ {VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE},
+ {VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_OP_MAX,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR},
+ {VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE},
+ {VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_OP_MAX,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR},
+ {VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_OP_MAX, VK_BLEND_FACTOR_SRC_COLOR},
+ {VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_OP_ADD,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR},
+ {VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR},
+ {VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_OP_ADD,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR},
+ {VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE},
+ {VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_OP_ADD,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR},
+ {VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE}}};
+
+ new_blend_state.blend_enable = VK_TRUE;
+ new_blend_state.blend_op = logic_ops[bpmem.blendmode.logicmode].op;
+ new_blend_state.src_blend = logic_ops[bpmem.blendmode.logicmode].src_factor;
+ new_blend_state.dst_blend = logic_ops[bpmem.blendmode.logicmode].dst_factor;
+ new_blend_state.alpha_blend_op = new_blend_state.blend_op;
+ new_blend_state.src_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.src_blend);
+ new_blend_state.dst_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.dst_blend);
+
+ m_state_tracker->SetBlendState(new_blend_state);
+ }
+ else
+ {
+ // This is unfortunate. Since we clobber the blend state when enabling logic ops,
+ // we have to call SetBlendMode again to restore the current blend state.
+ SetBlendMode(true);
+ return;
+ }
+ }
+}
+
+void Renderer::SetSamplerState(int stage, int texindex, bool custom_tex)
+{
+ const FourTexUnits& tex = bpmem.tex[texindex];
+ const TexMode0& tm0 = tex.texMode0[stage];
+ const TexMode1& tm1 = tex.texMode1[stage];
+ SamplerState new_state = {};
+
+ if (g_ActiveConfig.bForceFiltering)
+ {
+ new_state.min_filter = VK_FILTER_LINEAR;
+ new_state.mag_filter = VK_FILTER_LINEAR;
+ new_state.mipmap_mode = SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0) ?
+ VK_SAMPLER_MIPMAP_MODE_LINEAR :
+ VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ }
+ else
+ {
+ // Constants for these?
+ new_state.min_filter = (tm0.min_filter & 4) != 0 ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
+ new_state.mipmap_mode = SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0) ?
+ VK_SAMPLER_MIPMAP_MODE_LINEAR :
+ VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ new_state.mag_filter = tm0.mag_filter != 0 ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
+ }
+
+ // If mipmaps are disabled, clamp min/max lod
+ new_state.max_lod = (SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) ?
+ static_cast<u32>(MathUtil::Clamp(tm1.max_lod / 16.0f, 0.0f, 255.0f)) :
+ 0;
+ new_state.min_lod =
+ std::min(new_state.max_lod.Value(),
+ static_cast<u32>(MathUtil::Clamp(tm1.min_lod / 16.0f, 0.0f, 255.0f)));
+ new_state.lod_bias = static_cast<s32>(tm0.lod_bias / 32.0f);
+
+ // Custom textures may have a greater number of mips
+ if (custom_tex)
+ new_state.max_lod = 255;
+
+ // Address modes
+ static const VkSamplerAddressMode address_modes[] = {
+ VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT};
+ new_state.wrap_u = address_modes[tm0.wrap_s];
+ new_state.wrap_v = address_modes[tm0.wrap_t];
+
+ // Only use anisotropic filtering for textures that would be linearly filtered.
+ if (g_vulkan_context->SupportsAnisotropicFiltering() && g_ActiveConfig.iMaxAnisotropy > 0 &&
+ !SamplerCommon::IsBpTexMode0PointFiltering(tm0))
+ {
+ new_state.anisotropy = g_ActiveConfig.iMaxAnisotropy;
+ }
+ else
+ {
+ new_state.anisotropy = 0;
+ }
+
+ // Skip lookup if the state hasn't changed.
+ size_t bind_index = (texindex * 4) + stage;
+ if (m_sampler_states[bind_index].bits == new_state.bits)
+ return;
+
+ // Look up new state and replace in state tracker.
+ VkSampler sampler = g_object_cache->GetSampler(new_state);
+ if (sampler == VK_NULL_HANDLE)
+ {
+ ERROR_LOG(VIDEO, "Failed to create sampler");
+ sampler = g_object_cache->GetPointSampler();
+ }
+
+ m_state_tracker->SetSampler(bind_index, sampler);
+ m_sampler_states[bind_index].bits = new_state.bits;
+}
+
+void Renderer::ResetSamplerStates()
+{
+ // Ensure none of the sampler objects are in use.
+ g_command_buffer_mgr->WaitForGPUIdle();
+
+ // Invalidate all sampler states, next draw will re-initialize them.
+ for (size_t i = 0; i < m_sampler_states.size(); i++)
+ {
+ m_sampler_states[i].bits = std::numeric_limits<decltype(m_sampler_states[i].bits)>::max();
+ m_state_tracker->SetSampler(i, g_object_cache->GetPointSampler());
+ }
+
+ // Invalidate all sampler objects (some will be unused now).
+ g_object_cache->ClearSamplerCache();
+}
+
+void Renderer::SetDitherMode()
+{
+}
+
+void Renderer::SetInterlacingMode()
+{
+}
+
+void Renderer::SetScissorRect(const EFBRectangle& rc)
+{
+ TargetRectangle target_rc = ConvertEFBRectangle(rc);
+
+ VkRect2D scissor = {
+ {target_rc.left, target_rc.top},
+ {static_cast<uint32_t>(target_rc.GetWidth()), static_cast<uint32_t>(target_rc.GetHeight())}};
+
+ m_state_tracker->SetScissor(scissor);
+}
+
+void Renderer::SetViewport()
+{
+ int scissor_x_offset = bpmem.scissorOffset.x * 2;
+ int scissor_y_offset = bpmem.scissorOffset.y * 2;
+
+ float x = Renderer::EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - scissor_x_offset);
+ float y = Renderer::EFBToScaledYf(xfmem.viewport.yOrig + xfmem.viewport.ht - scissor_y_offset);
+ float width = Renderer::EFBToScaledXf(2.0f * xfmem.viewport.wd);
+ float height = Renderer::EFBToScaledYf(-2.0f * xfmem.viewport.ht);
+ if (width < 0.0f)
+ {
+ x += width;
+ width = -width;
+ }
+ if (height < 0.0f)
+ {
+ y += height;
+ height = -height;
+ }
+
+ // If we do depth clipping and depth range in the vertex shader we only need to ensure
+ // depth values don't exceed the maximum value supported by the console GPU. If not,
+ // we simply clamp the near/far values themselves to the maximum value as done above.
+ float min_depth, max_depth;
+ if (g_ActiveConfig.backend_info.bSupportsDepthClamp)
+ {
+ min_depth = 1.0f - GX_MAX_DEPTH;
+ max_depth = 1.0f;
+ }
+ else
+ {
+ float near_val = MathUtil::Clamp<float>(xfmem.viewport.farZ -
+ MathUtil::Clamp<float>(xfmem.viewport.zRange,
+ -16777216.0f, 16777216.0f),
+ 0.0f, 16777215.0f) /
+ 16777216.0f;
+ float far_val = MathUtil::Clamp<float>(xfmem.viewport.farZ, 0.0f, 16777215.0f) / 16777216.0f;
+ min_depth = 1.0f - near_val;
+ max_depth = 1.0f - far_val;
+ }
+
+ VkViewport viewport = {x, y, width, height, min_depth, max_depth};
+ m_state_tracker->SetViewport(viewport);
+}
+
+void Renderer::ChangeSurface(void* new_surface_handle)
+{
+ // Called by the main thread when the window is resized.
+ s_new_surface_handle = new_surface_handle;
+ s_surface_needs_change.Set();
+ s_surface_changed.Set();
+}
+
+void Renderer::RecompileShaders()
+{
+ DestroyShaders();
+ if (!CompileShaders())
+ PanicAlert("Failed to recompile shaders.");
+}
+
+bool Renderer::CompileShaders()
+{
+ static const char CLEAR_FRAGMENT_SHADER_SOURCE[] = R"(
+ layout(location = 0) in float3 uv0;
+ layout(location = 1) in float4 col0;
+ layout(location = 0) out float4 ocol0;
+
+ void main()
+ {
+ ocol0 = col0;
+ }
+
+ )";
+
+ static const char BLIT_FRAGMENT_SHADER_SOURCE[] = R"(
+ layout(set = 1, binding = 0) uniform sampler2DArray samp0;
+
+ layout(location = 0) in float3 uv0;
+ layout(location = 1) in float4 col0;
+ layout(location = 0) out float4 ocol0;
+
+ void main()
+ {
+ ocol0 = texture(samp0, uv0);
+ }
+ )";
+
+ std::string header = g_object_cache->GetUtilityShaderHeader();
+ std::string source;
+
+ source = header + CLEAR_FRAGMENT_SHADER_SOURCE;
+ m_clear_fragment_shader = Util::CompileAndCreateFragmentShader(source);
+ source = header + BLIT_FRAGMENT_SHADER_SOURCE;
+ m_blit_fragment_shader = Util::CompileAndCreateFragmentShader(source);
+
+ if (m_clear_fragment_shader == VK_NULL_HANDLE || m_blit_fragment_shader == VK_NULL_HANDLE)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void Renderer::DestroyShaders()
+{
+ auto DestroyShader = [this](VkShaderModule& shader) {
+ if (shader != VK_NULL_HANDLE)
+ {
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr);
+ shader = VK_NULL_HANDLE;
+ }
+ };
+
+ DestroyShader(m_clear_fragment_shader);
+ DestroyShader(m_blit_fragment_shader);
+}
+
+} // namespace Vulkan