2016-08-13 12:57:50 +00:00
|
|
|
// Copyright 2016 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2021-12-10 02:22:16 +00:00
|
|
|
#include "VideoBackends/Vulkan/VKRenderer.h"
|
|
|
|
|
2017-09-08 09:42:56 +00:00
|
|
|
#include <algorithm>
|
2016-10-01 03:07:50 +00:00
|
|
|
#include <cstddef>
|
2016-08-13 12:57:50 +00:00
|
|
|
#include <cstdio>
|
|
|
|
#include <limits>
|
2016-10-01 03:07:50 +00:00
|
|
|
#include <string>
|
2017-04-09 19:05:24 +00:00
|
|
|
#include <tuple>
|
2016-10-01 03:07:50 +00:00
|
|
|
|
2018-01-21 13:13:25 +00:00
|
|
|
#include "Common/Assert.h"
|
2017-02-01 15:56:13 +00:00
|
|
|
#include "Common/CommonTypes.h"
|
2016-10-01 03:07:50 +00:00
|
|
|
#include "Common/Logging/Log.h"
|
|
|
|
#include "Common/MsgHandler.h"
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2016-10-22 12:41:42 +00:00
|
|
|
#include "Core/Core.h"
|
2016-08-13 14:40:04 +00:00
|
|
|
|
2016-08-13 12:57:50 +00:00
|
|
|
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
|
|
|
#include "VideoBackends/Vulkan/ObjectCache.h"
|
2021-06-09 11:42:21 +00:00
|
|
|
#include "VideoBackends/Vulkan/StagingBuffer.h"
|
2016-08-13 12:57:50 +00:00
|
|
|
#include "VideoBackends/Vulkan/StateTracker.h"
|
2020-09-15 12:43:41 +00:00
|
|
|
#include "VideoBackends/Vulkan/VKBoundingBox.h"
|
|
|
|
#include "VideoBackends/Vulkan/VKPerfQuery.h"
|
2017-09-08 09:42:56 +00:00
|
|
|
#include "VideoBackends/Vulkan/VKPipeline.h"
|
|
|
|
#include "VideoBackends/Vulkan/VKShader.h"
|
2020-09-15 12:43:41 +00:00
|
|
|
#include "VideoBackends/Vulkan/VKStreamBuffer.h"
|
|
|
|
#include "VideoBackends/Vulkan/VKSwapChain.h"
|
2017-04-23 04:44:34 +00:00
|
|
|
#include "VideoBackends/Vulkan/VKTexture.h"
|
2020-09-15 12:43:41 +00:00
|
|
|
#include "VideoBackends/Vulkan/VKVertexFormat.h"
|
2016-08-13 12:57:50 +00:00
|
|
|
#include "VideoBackends/Vulkan/VulkanContext.h"
|
|
|
|
|
2017-05-25 13:59:23 +00:00
|
|
|
#include "VideoCommon/DriverDetails.h"
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "VideoCommon/FramebufferManager.h"
|
2017-04-17 13:14:17 +00:00
|
|
|
#include "VideoCommon/RenderState.h"
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "VideoCommon/VertexManagerBase.h"
|
2017-01-23 16:20:20 +00:00
|
|
|
#include "VideoCommon/VideoBackendBase.h"
|
2017-05-29 22:02:09 +00:00
|
|
|
#include "VideoCommon/VideoCommon.h"
|
2016-08-13 12:57:50 +00:00
|
|
|
#include "VideoCommon/VideoConfig.h"
|
2017-02-01 15:56:13 +00:00
|
|
|
#include "VideoCommon/XFMemory.h"
|
2016-08-13 12:57:50 +00:00
|
|
|
|
|
|
|
namespace Vulkan
|
|
|
|
{
|
2019-01-18 14:35:00 +00:00
|
|
|
Renderer::Renderer(std::unique_ptr<SwapChain> swap_chain, float backbuffer_scale)
|
2017-03-04 06:40:08 +00:00
|
|
|
: ::Renderer(swap_chain ? static_cast<int>(swap_chain->GetWidth()) : 1,
|
2019-01-18 14:35:00 +00:00
|
|
|
swap_chain ? static_cast<int>(swap_chain->GetHeight()) : 0, backbuffer_scale,
|
2017-09-09 06:09:24 +00:00
|
|
|
swap_chain ? swap_chain->GetTextureFormat() : AbstractTextureFormat::Undefined),
|
2017-03-04 06:40:08 +00:00
|
|
|
m_swap_chain(std::move(swap_chain))
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2016-10-02 11:37:24 +00:00
|
|
|
UpdateActiveConfig();
|
2019-02-01 18:47:07 +00:00
|
|
|
for (SamplerState& m_sampler_state : m_sampler_states)
|
2021-08-09 04:11:50 +00:00
|
|
|
m_sampler_state = RenderState::GetPointSamplerState();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2018-02-09 10:52:25 +00:00
|
|
|
Renderer::~Renderer() = default;
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2018-10-03 13:03:13 +00:00
|
|
|
bool Renderer::IsHeadless() const
|
|
|
|
{
|
|
|
|
return m_swap_chain == nullptr;
|
|
|
|
}
|
|
|
|
|
2016-10-22 10:50:36 +00:00
|
|
|
bool Renderer::Initialize()
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2018-02-09 10:52:25 +00:00
|
|
|
if (!::Renderer::Initialize())
|
|
|
|
return false;
|
|
|
|
|
2016-08-13 12:57:50 +00:00
|
|
|
// Various initialization routines will have executed commands on the command buffer.
|
|
|
|
// Execute what we have done before beginning the first frame.
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(true, false);
|
2016-08-13 12:57:50 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-09 10:52:25 +00:00
|
|
|
void Renderer::Shutdown()
|
|
|
|
{
|
|
|
|
::Renderer::Shutdown();
|
2019-02-15 01:59:50 +00:00
|
|
|
m_swap_chain.reset();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2021-08-28 05:30:05 +00:00
|
|
|
std::unique_ptr<AbstractTexture> Renderer::CreateTexture(const TextureConfig& config,
|
|
|
|
std::string_view name)
|
2017-09-30 06:25:36 +00:00
|
|
|
{
|
2021-08-28 05:30:05 +00:00
|
|
|
return VKTexture::Create(config, name);
|
2017-09-30 06:25:36 +00:00
|
|
|
}
|
|
|
|
|
2017-10-21 14:49:40 +00:00
|
|
|
std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTextureType type,
|
|
|
|
const TextureConfig& config)
|
|
|
|
{
|
|
|
|
return VKStagingTexture::Create(type, config);
|
|
|
|
}
|
|
|
|
|
2021-08-28 05:30:05 +00:00
|
|
|
std::unique_ptr<AbstractShader>
|
|
|
|
Renderer::CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name)
|
2017-09-08 09:42:56 +00:00
|
|
|
{
|
2021-08-28 05:30:05 +00:00
|
|
|
return VKShader::CreateFromSource(stage, source, name);
|
2017-09-08 09:42:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<AbstractShader> Renderer::CreateShaderFromBinary(ShaderStage stage,
|
2021-08-28 05:30:05 +00:00
|
|
|
const void* data, size_t length,
|
|
|
|
std::string_view name)
|
2017-09-08 09:42:56 +00:00
|
|
|
{
|
2021-08-28 05:30:05 +00:00
|
|
|
return VKShader::CreateFromBinary(stage, data, length, name);
|
2017-09-08 09:42:56 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
std::unique_ptr<NativeVertexFormat>
|
|
|
|
Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl)
|
|
|
|
{
|
|
|
|
return std::make_unique<VertexFormat>(vtx_decl);
|
|
|
|
}
|
|
|
|
|
2019-04-15 11:55:26 +00:00
|
|
|
std::unique_ptr<AbstractPipeline> Renderer::CreatePipeline(const AbstractPipelineConfig& config,
|
|
|
|
const void* cache_data,
|
|
|
|
size_t cache_data_length)
|
2017-09-08 09:42:56 +00:00
|
|
|
{
|
|
|
|
return VKPipeline::Create(config);
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
std::unique_ptr<AbstractFramebuffer> Renderer::CreateFramebuffer(AbstractTexture* color_attachment,
|
|
|
|
AbstractTexture* depth_attachment)
|
2018-01-21 10:22:45 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
return VKFramebuffer::Create(static_cast<VKTexture*>(color_attachment),
|
|
|
|
static_cast<VKTexture*>(depth_attachment));
|
2018-01-21 10:22:45 +00:00
|
|
|
}
|
|
|
|
|
2017-09-08 09:42:56 +00:00
|
|
|
void Renderer::SetPipeline(const AbstractPipeline* pipeline)
|
|
|
|
{
|
2018-02-24 15:15:35 +00:00
|
|
|
StateTracker::GetInstance()->SetPipeline(static_cast<const VKPipeline*>(pipeline));
|
2017-09-08 09:42:56 +00:00
|
|
|
}
|
|
|
|
|
2021-06-09 11:42:21 +00:00
|
|
|
std::unique_ptr<BoundingBox> Renderer::CreateBoundingBox() const
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2021-06-09 11:42:21 +00:00
|
|
|
return std::make_unique<VKBoundingBox>();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 14:47:46 +00:00
|
|
|
void Renderer::ClearScreen(const MathUtil::Rectangle<int>& rc, bool color_enable, bool alpha_enable,
|
2016-08-13 12:57:50 +00:00
|
|
|
bool z_enable, u32 color, u32 z)
|
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
g_framebuffer_manager->FlushEFBPokes();
|
2019-03-02 07:05:38 +00:00
|
|
|
g_framebuffer_manager->FlagPeekCacheAsOutOfDate();
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2016-08-13 12:57:50 +00:00
|
|
|
// Native -> EFB coordinates
|
2019-04-15 14:47:46 +00:00
|
|
|
MathUtil::Rectangle<int> target_rc = Renderer::ConvertEFBRectangle(rc);
|
2017-04-14 08:10:53 +00:00
|
|
|
|
|
|
|
// Size we pass this size to vkBeginRenderPass, it has to be clamped to the framebuffer
|
|
|
|
// dimensions. The other backends just silently ignore this case.
|
|
|
|
target_rc.ClampUL(0, 0, m_target_width, m_target_height);
|
|
|
|
|
2016-09-11 06:37:41 +00:00
|
|
|
VkRect2D target_vk_rc = {
|
|
|
|
{target_rc.left, target_rc.top},
|
|
|
|
{static_cast<uint32_t>(target_rc.GetWidth()), static_cast<uint32_t>(target_rc.GetHeight())}};
|
|
|
|
|
2016-09-11 05:18:23 +00:00
|
|
|
// 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.
|
2021-02-11 02:11:31 +00:00
|
|
|
if (bpmem.zcontrol.pixel_format == PixelFormat::RGB565_Z16 ||
|
|
|
|
bpmem.zcontrol.pixel_format == PixelFormat::RGB8_Z24 ||
|
|
|
|
bpmem.zcontrol.pixel_format == PixelFormat::Z24)
|
2016-09-11 05:18:23 +00:00
|
|
|
{
|
2022-07-22 01:10:10 +00:00
|
|
|
// Force alpha writes, and clear the alpha channel. This is different from the other backends,
|
2016-12-27 02:59:52 +00:00
|
|
|
// where the existing values of the alpha channel are preserved.
|
2016-09-11 05:18:23 +00:00
|
|
|
alpha_enable = true;
|
2016-12-27 02:59:52 +00:00
|
|
|
color &= 0x00FFFFFF;
|
2016-09-11 05:18:23 +00:00
|
|
|
}
|
|
|
|
|
2016-10-23 11:00:20 +00:00
|
|
|
// 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;
|
2019-02-15 01:59:50 +00:00
|
|
|
clear_depth_value.depthStencil.depth = static_cast<float>(z & 0xFFFFFF) / 16777216.0f;
|
|
|
|
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
|
|
|
|
clear_depth_value.depthStencil.depth = 1.0f - clear_depth_value.depthStencil.depth;
|
2016-10-23 11:00:20 +00:00
|
|
|
|
2016-09-11 06:37:41 +00:00
|
|
|
// 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.
|
2017-09-03 04:04:14 +00:00
|
|
|
bool use_clear_attachments = (color_enable && alpha_enable) || z_enable;
|
|
|
|
bool use_clear_render_pass =
|
|
|
|
!StateTracker::GetInstance()->InRenderPass() && color_enable && alpha_enable && z_enable;
|
|
|
|
|
|
|
|
// The NVIDIA Vulkan driver causes the GPU to lock up, or throw exceptions if MSAA is enabled,
|
|
|
|
// a non-full clear rect is specified, and a clear loadop or vkCmdClearAttachments is used.
|
|
|
|
if (g_ActiveConfig.iMultisamples > 1 &&
|
|
|
|
DriverDetails::HasBug(DriverDetails::BUG_BROKEN_MSAA_CLEAR))
|
2017-06-20 01:46:17 +00:00
|
|
|
{
|
|
|
|
use_clear_render_pass = false;
|
2017-09-03 04:04:14 +00:00
|
|
|
use_clear_attachments = false;
|
2017-06-20 01:46:17 +00:00
|
|
|
}
|
2017-09-03 04:04:14 +00:00
|
|
|
|
|
|
|
// This path cannot be used if the driver implementation doesn't guarantee pixels with no drawn
|
|
|
|
// geometry in "this" renderpass won't be cleared
|
|
|
|
if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_CLEAR_LOADOP_RENDERPASS))
|
2016-09-11 06:37:41 +00:00
|
|
|
use_clear_render_pass = false;
|
|
|
|
|
|
|
|
// Fastest path: Use a render pass to clear the buffers.
|
|
|
|
if (use_clear_render_pass)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2018-01-21 10:22:45 +00:00
|
|
|
const std::array<VkClearValue, 2> clear_values = {{clear_color_value, clear_depth_value}};
|
|
|
|
StateTracker::GetInstance()->BeginClearRenderPass(target_vk_rc, clear_values.data(),
|
|
|
|
static_cast<u32>(clear_values.size()));
|
2016-09-11 06:37:41 +00:00
|
|
|
return;
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2016-09-11 06:37:41 +00:00
|
|
|
// Fast path: Use vkCmdClearAttachments to clear the buffers within a render path
|
|
|
|
// We can't use this when preserving alpha but clearing color.
|
2017-09-03 04:04:14 +00:00
|
|
|
if (use_clear_attachments)
|
2016-09-11 06:37:41 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
VkClearRect vk_rect = {target_vk_rc, 0, g_framebuffer_manager->GetEFBLayers()};
|
2016-10-22 10:50:36 +00:00
|
|
|
if (!StateTracker::GetInstance()->IsWithinRenderArea(
|
|
|
|
target_vk_rc.offset.x, target_vk_rc.offset.y, target_vk_rc.extent.width,
|
|
|
|
target_vk_rc.extent.height))
|
2016-09-11 06:37:41 +00:00
|
|
|
{
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->EndClearRenderPass();
|
2016-09-11 06:37:41 +00:00
|
|
|
}
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->BeginRenderPass();
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2016-09-11 06:37:41 +00:00
|
|
|
vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments,
|
2016-10-22 10:50:36 +00:00
|
|
|
clear_attachments, 1, &vk_rect);
|
2016-09-11 06:37:41 +00:00
|
|
|
}
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2016-09-11 06:37:41 +00:00
|
|
|
// Anything left over for the slow path?
|
2016-08-13 12:57:50 +00:00
|
|
|
if (!color_enable && !alpha_enable && !z_enable)
|
|
|
|
return;
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
g_framebuffer_manager->ClearEFB(rc, color_enable, alpha_enable, z_enable, color, z);
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::Flush()
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(true, false);
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::WaitForGPUIdle()
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(false, true);
|
2018-11-28 04:30:47 +00:00
|
|
|
}
|
2017-05-29 22:02:09 +00:00
|
|
|
|
2018-11-28 04:30:47 +00:00
|
|
|
void Renderer::BindBackbuffer(const ClearColor& clear_color)
|
|
|
|
{
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->EndRenderPass();
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2018-01-26 06:23:24 +00:00
|
|
|
// Handle host window resizes.
|
|
|
|
CheckForSurfaceChange();
|
|
|
|
CheckForSurfaceResize();
|
|
|
|
|
2019-09-30 15:10:08 +00:00
|
|
|
// Check for exclusive fullscreen request.
|
|
|
|
if (m_swap_chain->GetCurrentFullscreenState() != m_swap_chain->GetNextFullscreenState() &&
|
|
|
|
!m_swap_chain->SetFullscreenState(m_swap_chain->GetNextFullscreenState()))
|
|
|
|
{
|
|
|
|
// if it fails, don't keep trying
|
|
|
|
m_swap_chain->SetNextFullscreenState(m_swap_chain->GetCurrentFullscreenState());
|
|
|
|
}
|
|
|
|
|
2021-04-11 08:10:38 +00:00
|
|
|
const bool present_fail = g_command_buffer_mgr->CheckLastPresentFail();
|
|
|
|
VkResult res = present_fail ? g_command_buffer_mgr->GetLastPresentResult() :
|
|
|
|
m_swap_chain->AcquireNextImage();
|
|
|
|
|
|
|
|
if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT &&
|
|
|
|
!m_swap_chain->GetCurrentFullscreenState())
|
|
|
|
{
|
|
|
|
// AMD's binary driver as of 21.3 seems to return exclusive fullscreen lost even when it was
|
|
|
|
// never requested, so long as the caller requested it to be application controlled. Handle
|
|
|
|
// this ignoring the lost result and just continuing as normal if we never acquired it.
|
|
|
|
res = VK_SUCCESS;
|
|
|
|
if (present_fail)
|
|
|
|
{
|
|
|
|
// We still need to acquire an image.
|
|
|
|
res = m_swap_chain->AcquireNextImage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-30 15:10:08 +00:00
|
|
|
if (res != VK_SUCCESS)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
// Execute cmdbuffer before resizing, as the last frame could still be presenting.
|
|
|
|
ExecuteCommandBuffer(false, true);
|
2019-09-30 15:10:08 +00:00
|
|
|
|
|
|
|
// Was this a lost exclusive fullscreen?
|
|
|
|
if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
|
|
|
|
{
|
|
|
|
// The present keeps returning exclusive mode lost unless we re-create the swap chain.
|
2020-11-09 08:26:14 +00:00
|
|
|
INFO_LOG_FMT(VIDEO, "Lost exclusive fullscreen.");
|
2019-09-30 15:10:08 +00:00
|
|
|
m_swap_chain->RecreateSwapChain();
|
|
|
|
}
|
|
|
|
else if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
|
|
|
|
{
|
2020-11-09 08:26:14 +00:00
|
|
|
INFO_LOG_FMT(VIDEO, "Resizing swap chain due to suboptimal/out-of-date");
|
2019-09-30 15:10:08 +00:00
|
|
|
m_swap_chain->ResizeSwapChain();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-09 08:26:14 +00:00
|
|
|
ERROR_LOG_FMT(VIDEO, "Unknown present error {:#010X}, please report.", res);
|
2019-09-30 15:10:08 +00:00
|
|
|
m_swap_chain->RecreateSwapChain();
|
|
|
|
}
|
|
|
|
|
2019-01-27 02:59:57 +00:00
|
|
|
res = m_swap_chain->AcquireNextImage();
|
2021-04-11 08:10:38 +00:00
|
|
|
if (res != VK_SUCCESS)
|
|
|
|
PanicAlertFmt("Failed to grab image from swap chain: {:#010X}", res);
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2019-02-15 01:59:50 +00:00
|
|
|
m_swap_chain->GetCurrentTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
|
|
|
|
m_swap_chain->GetCurrentTexture()->TransitionToLayout(
|
|
|
|
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
|
|
|
SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(),
|
|
|
|
ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}});
|
2018-11-28 04:30:47 +00:00
|
|
|
}
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2018-11-28 04:30:47 +00:00
|
|
|
void Renderer::PresentBackbuffer()
|
|
|
|
{
|
2016-08-13 12:57:50 +00:00
|
|
|
// End drawing to backbuffer
|
2017-09-09 06:09:24 +00:00
|
|
|
StateTracker::GetInstance()->EndRenderPass();
|
2016-08-13 12:57:50 +00:00
|
|
|
|
|
|
|
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
|
|
|
|
// to it have finished before present.
|
2019-01-27 02:59:57 +00:00
|
|
|
m_swap_chain->GetCurrentTexture()->TransitionToLayout(
|
|
|
|
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
2018-11-28 04:30:47 +00:00
|
|
|
|
|
|
|
// 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.
|
2019-03-17 05:59:22 +00:00
|
|
|
g_command_buffer_mgr->SubmitCommandBuffer(true, false, m_swap_chain->GetSwapChain(),
|
2019-01-27 02:59:57 +00:00
|
|
|
m_swap_chain->GetCurrentImageIndex());
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// New cmdbuffer, so invalidate state.
|
|
|
|
StateTracker::GetInstance()->InvalidateCachedState();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2019-09-30 15:10:08 +00:00
|
|
|
void Renderer::SetFullscreen(bool enable_fullscreen)
|
|
|
|
{
|
|
|
|
if (!m_swap_chain->IsFullscreenSupported())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_swap_chain->SetNextFullscreenState(enable_fullscreen);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Renderer::IsFullscreen() const
|
|
|
|
{
|
|
|
|
return m_swap_chain && m_swap_chain->GetCurrentFullscreenState();
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->EndRenderPass();
|
2018-11-28 04:30:47 +00:00
|
|
|
|
2019-03-17 05:59:22 +00:00
|
|
|
g_command_buffer_mgr->SubmitCommandBuffer(submit_off_thread, wait_for_completion);
|
2018-11-28 04:30:47 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->InvalidateCachedState();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::CheckForSurfaceChange()
|
|
|
|
{
|
2018-10-03 13:03:13 +00:00
|
|
|
if (!m_surface_changed.TestAndClear() || !m_swap_chain)
|
2016-08-13 12:57:50 +00:00
|
|
|
return;
|
|
|
|
|
2018-01-26 06:23:24 +00:00
|
|
|
// Submit the current draws up until rendering the XFB.
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(false, true);
|
2017-09-16 06:15:20 +00:00
|
|
|
|
|
|
|
// Clear the present failed flag, since we don't want to resize after recreating.
|
|
|
|
g_command_buffer_mgr->CheckLastPresentFail();
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2018-10-03 13:03:13 +00:00
|
|
|
// Recreate the surface. If this fails we're in trouble.
|
|
|
|
if (!m_swap_chain->RecreateSurface(m_new_surface_handle))
|
2020-12-02 18:17:27 +00:00
|
|
|
PanicAlertFmt("Failed to recreate Vulkan surface. Cannot continue.");
|
2018-10-03 13:03:13 +00:00
|
|
|
m_new_surface_handle = nullptr;
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2017-09-16 06:15:20 +00:00
|
|
|
// Handle case where the dimensions are now different.
|
|
|
|
OnSwapChainResized();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2018-01-26 06:23:24 +00:00
|
|
|
void Renderer::CheckForSurfaceResize()
|
|
|
|
{
|
|
|
|
if (!m_surface_resized.TestAndClear())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If we don't have a surface, how can we resize the swap chain?
|
|
|
|
// CheckForSurfaceChange should handle this case.
|
|
|
|
if (!m_swap_chain)
|
|
|
|
{
|
2020-11-09 08:26:14 +00:00
|
|
|
WARN_LOG_FMT(VIDEO, "Surface resize event received without active surface, ignoring");
|
2018-01-26 06:23:24 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the GPU to catch up since we're going to destroy the swap chain.
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(false, true);
|
2018-01-26 06:23:24 +00:00
|
|
|
|
|
|
|
// Clear the present failed flag, since we don't want to resize after recreating.
|
|
|
|
g_command_buffer_mgr->CheckLastPresentFail();
|
|
|
|
|
|
|
|
// Resize the swap chain.
|
|
|
|
m_swap_chain->RecreateSwapChain();
|
|
|
|
OnSwapChainResized();
|
|
|
|
}
|
|
|
|
|
2018-11-28 04:30:47 +00:00
|
|
|
void Renderer::OnConfigChanged(u32 bits)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
if (bits & CONFIG_CHANGE_BIT_HOST_CONFIG)
|
|
|
|
g_object_cache->ReloadPipelineCache();
|
2016-08-13 12:57:50 +00:00
|
|
|
|
|
|
|
// For vsync, we need to change the present mode, which means recreating the swap chain.
|
2018-11-28 04:30:47 +00:00
|
|
|
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_VSYNC)
|
2016-10-02 12:09:19 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(false, true);
|
2019-01-27 02:24:53 +00:00
|
|
|
m_swap_chain->SetVSync(g_ActiveConfig.bVSyncActive);
|
2016-10-02 12:09:19 +00:00
|
|
|
}
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2017-06-26 19:08:21 +00:00
|
|
|
// For quad-buffered stereo we need to change the layer count, so recreate the swap chain.
|
2018-11-28 04:30:47 +00:00
|
|
|
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE)
|
2017-09-16 06:15:20 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
ExecuteCommandBuffer(false, true);
|
2017-09-16 06:15:20 +00:00
|
|
|
m_swap_chain->RecreateSwapChain();
|
|
|
|
}
|
2017-06-26 19:08:21 +00:00
|
|
|
|
2016-08-13 12:57:50 +00:00
|
|
|
// Wipe sampler cache if force texture filtering or anisotropy changes.
|
2018-11-28 04:30:47 +00:00
|
|
|
if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING))
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
ExecuteCommandBuffer(false, true);
|
2016-08-13 12:57:50 +00:00
|
|
|
ResetSamplerStates();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::OnSwapChainResized()
|
|
|
|
{
|
2017-03-04 06:42:21 +00:00
|
|
|
m_backbuffer_width = m_swap_chain->GetWidth();
|
|
|
|
m_backbuffer_height = m_swap_chain->GetHeight();
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::BindFramebuffer(VKFramebuffer* fb)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->EndRenderPass();
|
2016-08-13 12:57:50 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
// Shouldn't be bound as a texture.
|
|
|
|
if (fb->GetColorAttachment())
|
|
|
|
{
|
|
|
|
StateTracker::GetInstance()->UnbindTexture(
|
|
|
|
static_cast<VKTexture*>(fb->GetColorAttachment())->GetView());
|
|
|
|
}
|
|
|
|
if (fb->GetDepthAttachment())
|
|
|
|
{
|
|
|
|
StateTracker::GetInstance()->UnbindTexture(
|
|
|
|
static_cast<VKTexture*>(fb->GetDepthAttachment())->GetView());
|
|
|
|
}
|
2018-01-21 10:22:45 +00:00
|
|
|
|
|
|
|
fb->TransitionForRender();
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->SetFramebuffer(fb);
|
2018-01-21 10:22:45 +00:00
|
|
|
m_current_framebuffer = fb;
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer)
|
2018-01-21 10:22:45 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
if (m_current_framebuffer == framebuffer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VKFramebuffer* vkfb = static_cast<VKFramebuffer*>(framebuffer);
|
2018-01-21 10:22:45 +00:00
|
|
|
BindFramebuffer(vkfb);
|
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer)
|
2018-01-21 10:22:45 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
if (m_current_framebuffer == framebuffer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VKFramebuffer* vkfb = static_cast<VKFramebuffer*>(framebuffer);
|
2018-01-21 10:22:45 +00:00
|
|
|
BindFramebuffer(vkfb);
|
|
|
|
|
|
|
|
// If we're discarding, begin the discard pass, then switch to a load pass.
|
|
|
|
// This way if the command buffer is flushed, we don't start another discard pass.
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->BeginDiscardRenderPass();
|
2018-01-21 10:22:45 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer,
|
2018-01-21 10:22:45 +00:00
|
|
|
const ClearColor& color_value, float depth_value)
|
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
VKFramebuffer* vkfb = static_cast<VKFramebuffer*>(framebuffer);
|
2018-01-21 10:22:45 +00:00
|
|
|
BindFramebuffer(vkfb);
|
|
|
|
|
|
|
|
std::array<VkClearValue, 2> clear_values;
|
|
|
|
u32 num_clear_values = 0;
|
|
|
|
if (vkfb->GetColorFormat() != AbstractTextureFormat::Undefined)
|
|
|
|
{
|
|
|
|
std::memcpy(clear_values[num_clear_values].color.float32, color_value.data(),
|
|
|
|
sizeof(clear_values[num_clear_values].color.float32));
|
|
|
|
num_clear_values++;
|
|
|
|
}
|
|
|
|
if (vkfb->GetDepthFormat() != AbstractTextureFormat::Undefined)
|
|
|
|
{
|
|
|
|
clear_values[num_clear_values].depthStencil.depth = depth_value;
|
|
|
|
clear_values[num_clear_values].depthStencil.stencil = 0;
|
|
|
|
num_clear_values++;
|
|
|
|
}
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->BeginClearRenderPass(vkfb->GetRect(), clear_values.data(),
|
2018-01-21 10:22:45 +00:00
|
|
|
num_clear_values);
|
|
|
|
}
|
|
|
|
|
2018-01-21 13:13:25 +00:00
|
|
|
void Renderer::SetTexture(u32 index, const AbstractTexture* texture)
|
|
|
|
{
|
|
|
|
// Texture should always be in SHADER_READ_ONLY layout prior to use.
|
|
|
|
// This is so we don't need to transition during render passes.
|
2019-02-15 01:59:50 +00:00
|
|
|
const VKTexture* tex = static_cast<const VKTexture*>(texture);
|
|
|
|
if (tex)
|
|
|
|
{
|
|
|
|
if (tex->GetLayout() != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
|
|
|
{
|
|
|
|
if (StateTracker::GetInstance()->InRenderPass())
|
|
|
|
{
|
2020-11-09 08:26:14 +00:00
|
|
|
WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in Renderer::SetTexture()");
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->EndRenderPass();
|
|
|
|
}
|
|
|
|
|
|
|
|
tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
|
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
StateTracker::GetInstance()->SetTexture(index, tex->GetView());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
StateTracker::GetInstance()->SetTexture(0, VK_NULL_HANDLE);
|
|
|
|
}
|
2018-01-21 13:13:25 +00:00
|
|
|
}
|
|
|
|
|
2017-09-09 08:30:15 +00:00
|
|
|
void Renderer::SetSamplerState(u32 index, const SamplerState& state)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
|
|
|
// Skip lookup if the state hasn't changed.
|
2021-08-09 04:11:50 +00:00
|
|
|
if (m_sampler_states[index] == state)
|
2016-08-13 12:57:50 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Look up new state and replace in state tracker.
|
2017-09-09 08:30:15 +00:00
|
|
|
VkSampler sampler = g_object_cache->GetSampler(state);
|
2016-08-13 12:57:50 +00:00
|
|
|
if (sampler == VK_NULL_HANDLE)
|
|
|
|
{
|
2020-11-09 08:26:14 +00:00
|
|
|
ERROR_LOG_FMT(VIDEO, "Failed to create sampler");
|
2016-08-13 12:57:50 +00:00
|
|
|
sampler = g_object_cache->GetPointSampler();
|
|
|
|
}
|
|
|
|
|
2017-09-09 08:30:15 +00:00
|
|
|
StateTracker::GetInstance()->SetSampler(index, sampler);
|
2021-08-09 04:11:50 +00:00
|
|
|
m_sampler_states[index] = state;
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write)
|
|
|
|
{
|
|
|
|
VKTexture* vk_texture = static_cast<VKTexture*>(texture);
|
|
|
|
if (vk_texture)
|
|
|
|
{
|
|
|
|
StateTracker::GetInstance()->EndRenderPass();
|
|
|
|
StateTracker::GetInstance()->SetImageTexture(vk_texture->GetView());
|
|
|
|
vk_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
|
|
|
read ? (write ? VKTexture::ComputeImageLayout::ReadWrite :
|
|
|
|
VKTexture::ComputeImageLayout::ReadOnly) :
|
|
|
|
VKTexture::ComputeImageLayout::WriteOnly);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
StateTracker::GetInstance()->SetImageTexture(VK_NULL_HANDLE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-21 13:13:25 +00:00
|
|
|
void Renderer::UnbindTexture(const AbstractTexture* texture)
|
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
StateTracker::GetInstance()->UnbindTexture(static_cast<const VKTexture*>(texture)->GetView());
|
2018-01-21 13:13:25 +00:00
|
|
|
}
|
|
|
|
|
2016-08-13 12:57:50 +00:00
|
|
|
void Renderer::ResetSamplerStates()
|
|
|
|
{
|
|
|
|
// Invalidate all sampler states, next draw will re-initialize them.
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < m_sampler_states.size(); i++)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2021-08-09 04:11:50 +00:00
|
|
|
m_sampler_states[i] = RenderState::GetPointSamplerState();
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->SetSampler(i, g_object_cache->GetPointSampler());
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Invalidate all sampler objects (some will be unused now).
|
|
|
|
g_object_cache->ClearSamplerCache();
|
|
|
|
}
|
|
|
|
|
2018-01-21 12:12:32 +00:00
|
|
|
void Renderer::SetScissorRect(const MathUtil::Rectangle<int>& rc)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2018-01-21 12:12:32 +00:00
|
|
|
VkRect2D scissor = {{rc.left, rc.top},
|
|
|
|
{static_cast<u32>(rc.GetWidth()), static_cast<u32>(rc.GetHeight())}};
|
2019-04-28 06:01:07 +00:00
|
|
|
|
|
|
|
// See Vulkan spec for vkCmdSetScissor:
|
|
|
|
// The x and y members of offset must be greater than or equal to 0.
|
|
|
|
if (scissor.offset.x < 0)
|
|
|
|
{
|
|
|
|
scissor.extent.width -= -scissor.offset.x;
|
|
|
|
scissor.offset.x = 0;
|
|
|
|
}
|
|
|
|
if (scissor.offset.y < 0)
|
|
|
|
{
|
|
|
|
scissor.extent.height -= -scissor.offset.y;
|
|
|
|
scissor.offset.y = 0;
|
|
|
|
}
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->SetScissor(scissor);
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2018-01-21 12:04:15 +00:00
|
|
|
void Renderer::SetViewport(float x, float y, float width, float height, float near_depth,
|
|
|
|
float far_depth)
|
2016-08-13 12:57:50 +00:00
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
VkViewport viewport = {x, y, width, height, near_depth, far_depth};
|
2016-10-22 10:50:36 +00:00
|
|
|
StateTracker::GetInstance()->SetViewport(viewport);
|
2016-08-13 12:57:50 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 07:16:53 +00:00
|
|
|
void Renderer::Draw(u32 base_vertex, u32 num_vertices)
|
|
|
|
{
|
2019-02-15 01:59:50 +00:00
|
|
|
if (!StateTracker::GetInstance()->Bind())
|
2018-11-27 07:16:53 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
vkCmdDraw(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex)
|
|
|
|
{
|
|
|
|
if (!StateTracker::GetInstance()->Bind())
|
|
|
|
return;
|
|
|
|
|
|
|
|
vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_indices, 1, base_index,
|
|
|
|
base_vertex, 0);
|
|
|
|
}
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2022-06-01 09:58:13 +00:00
|
|
|
void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y,
|
|
|
|
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z)
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
StateTracker::GetInstance()->SetComputeShader(static_cast<const VKShader*>(shader));
|
|
|
|
if (StateTracker::GetInstance()->BindCompute())
|
|
|
|
vkCmdDispatch(g_command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z);
|
|
|
|
}
|
|
|
|
|
2016-08-13 12:57:50 +00:00
|
|
|
} // namespace Vulkan
|