mirror of
https://github.com/jpd002/Play-.git
synced 2025-04-28 13:47:57 +03:00

Some checks failed
Build macOS / build_macos (push) Has been cancelled
Build Android / build_android (apk) (push) Has been cancelled
Build Android / build_android (libretro) (push) Has been cancelled
Build Linux ARM32 / build_linux_arm32 (push) Has been cancelled
Build Linux ARM64 / build_linux_arm64 (push) Has been cancelled
Build Windows Psf / build_windows_psf (off, x86_64, Visual Studio 16 2019, installer64.nsi, x64) (push) Has been cancelled
Build Windows Psf / build_windows_psf (on, x86_64, Visual Studio 16 2019, installer64.nsi, x64) (push) Has been cancelled
Build Windows / build_windows (x86_32, Visual Studio 16 2019, installer32.nsi, win32_msvc2019, Win32) (push) Has been cancelled
Build Windows / build_windows (x86_64, Visual Studio 16 2019, installer64.nsi, win64_msvc2019_64, x64) (push) Has been cancelled
Check Format / run_clangformat (push) Has been cancelled
Build iOS / build_ios (push) Has been cancelled
Build JavaScript / build_js (push) Has been cancelled
Build Linux / build_linux (push) Has been cancelled
1822 lines
57 KiB
C++
1822 lines
57 KiB
C++
#include "GSH_Vulkan.h"
|
|
#include <cstring>
|
|
#include "std_experimental_map.h"
|
|
#include "../GsPixelFormats.h"
|
|
#include "../GsTransferRange.h"
|
|
#include "Log.h"
|
|
#include "AppConfig.h"
|
|
#include "GSH_VulkanPlatformDefs.h"
|
|
#include "GSH_VulkanDrawDesktop.h"
|
|
#include "GSH_VulkanDrawMobile.h"
|
|
#include "GSH_VulkanDeviceInfo.h"
|
|
#include "vulkan/Loader.h"
|
|
#include "vulkan/StructDefs.h"
|
|
#include "vulkan/StructChain.h"
|
|
#include "vulkan/Utils.h"
|
|
|
|
#define LOG_NAME ("gsh_vulkan")
|
|
|
|
using namespace GSH_Vulkan;
|
|
|
|
static uint32 MakeColor(uint8 r, uint8 g, uint8 b, uint8 a)
|
|
{
|
|
return (a << 24) | (b << 16) | (g << 8) | (r);
|
|
}
|
|
|
|
static uint16 RGBA32ToRGBA16(uint32 inputColor)
|
|
{
|
|
uint32 result = 0;
|
|
result |= ((inputColor & 0x000000F8) >> (0 + 3)) << 0;
|
|
result |= ((inputColor & 0x0000F800) >> (8 + 3)) << 5;
|
|
result |= ((inputColor & 0x00F80000) >> (16 + 3)) << 10;
|
|
result |= ((inputColor & 0x80000000) >> 31) << 15;
|
|
return result;
|
|
}
|
|
|
|
template <typename StorageFormat>
|
|
static Framework::Vulkan::CImage CreateSwizzleTable(Framework::Vulkan::CDevice& device,
|
|
const VkPhysicalDeviceMemoryProperties& memoryProperties, VkQueue queue, Framework::Vulkan::CCommandBufferPool& commandBufferPool)
|
|
{
|
|
auto result = Framework::Vulkan::CImage(device, memoryProperties,
|
|
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
|
VK_FORMAT_R32_UINT, StorageFormat::PAGEWIDTH, StorageFormat::PAGEHEIGHT);
|
|
result.Fill(queue, commandBufferPool, memoryProperties,
|
|
CGsPixelFormats::CPixelIndexor<StorageFormat>::GetPageOffsets());
|
|
result.SetLayout(queue, commandBufferPool,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_SHADER_READ_BIT);
|
|
return result;
|
|
}
|
|
|
|
CGSH_Vulkan::CGSH_Vulkan()
|
|
{
|
|
m_context = std::make_shared<CContext>();
|
|
}
|
|
|
|
Framework::Vulkan::CInstance CGSH_Vulkan::CreateInstance(bool useValidationLayers)
|
|
{
|
|
auto instanceCreateInfo = Framework::Vulkan::InstanceCreateInfo();
|
|
|
|
std::vector<const char*> layers;
|
|
#if defined(_DEBUG)
|
|
static const char* validationLayerName = "VK_LAYER_KHRONOS_validation";
|
|
if(useValidationLayers)
|
|
{
|
|
useValidationLayers = Framework::Vulkan::CLoader::GetInstance().IsInstanceLayerPresent(validationLayerName);
|
|
}
|
|
if(useValidationLayers)
|
|
{
|
|
layers.push_back(validationLayerName);
|
|
}
|
|
#endif
|
|
|
|
std::vector<const char*> extensions;
|
|
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
|
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
|
#if GSH_VULKAN_USE_ANNOTATIONS
|
|
if(
|
|
Framework::Vulkan::CLoader::GetInstance().IsInstanceExtensionPresent(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)
|
|
#if defined(__ANDROID__)
|
|
|| useValidationLayers //On Android, the validation layer implements the debug utils extension, but it doesn't seem to be reported properly
|
|
#endif
|
|
)
|
|
{
|
|
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
}
|
|
#endif
|
|
#ifdef _WIN32
|
|
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
|
#endif
|
|
#ifdef __APPLE__
|
|
#if TARGET_OS_IPHONE
|
|
extensions.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
|
|
#else
|
|
extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
|
|
#endif
|
|
#endif
|
|
#ifdef __linux__
|
|
#ifdef __ANDROID__
|
|
extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
#else
|
|
extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
|
|
#endif
|
|
#endif
|
|
|
|
auto appInfo = Framework::Vulkan::ApplicationInfo();
|
|
appInfo.pApplicationName = "Play!";
|
|
appInfo.pEngineName = "Play!";
|
|
#if GSH_VULKAN_IS_DESKTOP
|
|
appInfo.apiVersion = VK_API_VERSION_1_2;
|
|
#elif GSH_VULKAN_IS_MOBILE
|
|
appInfo.apiVersion = VK_API_VERSION_1_0;
|
|
#else
|
|
#error Unsupported Vulkan flavor
|
|
#endif
|
|
|
|
instanceCreateInfo.pApplicationInfo = &appInfo;
|
|
instanceCreateInfo.enabledExtensionCount = extensions.size();
|
|
instanceCreateInfo.ppEnabledExtensionNames = extensions.data();
|
|
instanceCreateInfo.enabledLayerCount = layers.size();
|
|
instanceCreateInfo.ppEnabledLayerNames = layers.data();
|
|
return Framework::Vulkan::CInstance(instanceCreateInfo);
|
|
}
|
|
|
|
void CGSH_Vulkan::InitializeImpl()
|
|
{
|
|
assert(!m_instance.IsEmpty());
|
|
m_context->instance = &m_instance;
|
|
|
|
auto physicalDevices = GetPhysicalDevices();
|
|
assert(!physicalDevices.empty());
|
|
auto physicalDeviceIndex = GetPhysicalDeviceIndex(physicalDevices);
|
|
m_context->physicalDevice = physicalDevices[physicalDeviceIndex];
|
|
|
|
auto renderQueueFamilies = GetRenderQueueFamilies(m_context->physicalDevice);
|
|
assert(!renderQueueFamilies.empty());
|
|
auto renderQueueFamily = renderQueueFamilies[0];
|
|
|
|
m_instance.vkGetPhysicalDeviceMemoryProperties(m_context->physicalDevice, &m_context->physicalDeviceMemoryProperties);
|
|
|
|
if(m_context->surface)
|
|
{
|
|
auto surfaceFormats = GetDeviceSurfaceFormats(m_context->physicalDevice);
|
|
assert(surfaceFormats.size() > 0);
|
|
m_context->surfaceFormat = surfaceFormats[0];
|
|
}
|
|
|
|
CreateDevice(m_context->physicalDevice);
|
|
m_context->device.vkGetDeviceQueue(m_context->device, renderQueueFamily, 0, &m_context->queue);
|
|
m_context->commandBufferPool = Framework::Vulkan::CCommandBufferPool(m_context->device, renderQueueFamily);
|
|
|
|
CreateDescriptorPool();
|
|
CreateMemoryBuffer();
|
|
CreateClutBuffer();
|
|
|
|
m_swizzleTablePSMCT32 = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMCT32>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMCT16 = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMCT16>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMCT16S = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMCT16S>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMT8 = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMT8>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMT4 = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMT4>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMZ32 = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMZ32>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMZ16 = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMZ16>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
m_swizzleTablePSMZ16S = CreateSwizzleTable<CGsPixelFormats::STORAGEPSMZ16S>(m_context->device, m_context->physicalDeviceMemoryProperties, m_context->queue, m_context->commandBufferPool);
|
|
|
|
m_context->swizzleTablePSMCT32View = m_swizzleTablePSMCT32.CreateImageView();
|
|
m_context->swizzleTablePSMCT16View = m_swizzleTablePSMCT16.CreateImageView();
|
|
m_context->swizzleTablePSMCT16SView = m_swizzleTablePSMCT16S.CreateImageView();
|
|
m_context->swizzleTablePSMT8View = m_swizzleTablePSMT8.CreateImageView();
|
|
m_context->swizzleTablePSMT4View = m_swizzleTablePSMT4.CreateImageView();
|
|
m_context->swizzleTablePSMZ32View = m_swizzleTablePSMZ32.CreateImageView();
|
|
m_context->swizzleTablePSMZ16View = m_swizzleTablePSMZ16.CreateImageView();
|
|
m_context->swizzleTablePSMZ16SView = m_swizzleTablePSMZ16S.CreateImageView();
|
|
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMCT32, "Swizzle Table PSMCT32");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMCT16, "Swizzle Table PSMCT16");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMCT16S, "Swizzle Table PSMCT16S");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMT8, "Swizzle Table PSMT8");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMT4, "Swizzle Table PSMT4");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMZ32, "Swizzle Table PSMZ32");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMZ16, "Swizzle Table PSMZ16");
|
|
m_context->annotations.SetImageName(m_swizzleTablePSMZ16S, "Swizzle Table PSMZ16S");
|
|
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMCT32View, "Swizzle Table View PSMCT32");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMCT16View, "Swizzle Table View PSMCT16");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMCT16SView, "Swizzle Table View PSMCT16S");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMT8View, "Swizzle Table View PSMT8");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMT4View, "Swizzle Table View PSMT4");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMZ32View, "Swizzle Table View PSMZ32");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMZ16View, "Swizzle Table View PSMZ16");
|
|
m_context->annotations.SetImageViewName(m_context->swizzleTablePSMZ16SView, "Swizzle Table View PSMZ16S");
|
|
|
|
m_frameCommandBuffer = std::make_shared<CFrameCommandBuffer>(m_context);
|
|
m_clutLoad = std::make_shared<CClutLoad>(m_context, m_frameCommandBuffer);
|
|
#if GSH_VULKAN_IS_DESKTOP
|
|
m_draw = std::make_shared<CDrawDesktop>(m_context, m_frameCommandBuffer);
|
|
#elif GSH_VULKAN_IS_MOBILE
|
|
m_draw = std::make_shared<CDrawMobile>(m_context, m_frameCommandBuffer);
|
|
#else
|
|
#error Unsupported Vulkan flavor
|
|
#endif
|
|
if(m_context->surface)
|
|
{
|
|
m_present = std::make_shared<CPresent>(m_context);
|
|
}
|
|
m_transferHost = std::make_shared<CTransferHost>(m_context, m_frameCommandBuffer);
|
|
m_transferLocal = std::make_shared<CTransferLocal>(m_context, m_frameCommandBuffer);
|
|
|
|
m_frameCommandBuffer->RegisterWriter(m_draw.get());
|
|
m_frameCommandBuffer->RegisterWriter(m_transferHost.get());
|
|
m_frameCommandBuffer->BeginFrame();
|
|
}
|
|
|
|
void CGSH_Vulkan::ReleaseImpl()
|
|
{
|
|
ResetImpl();
|
|
|
|
//Flush any pending rendering commands
|
|
m_context->device.vkQueueWaitIdle(m_context->queue);
|
|
|
|
m_clutLoad.reset();
|
|
m_draw.reset();
|
|
m_present.reset();
|
|
m_transferHost.reset();
|
|
m_transferLocal.reset();
|
|
m_frameCommandBuffer.reset();
|
|
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMCT32View, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMCT16View, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMCT16SView, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMT8View, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMT4View, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMZ32View, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMZ16View, nullptr);
|
|
m_context->device.vkDestroyImageView(m_context->device, m_context->swizzleTablePSMZ16SView, nullptr);
|
|
|
|
m_swizzleTablePSMCT32.Reset();
|
|
m_swizzleTablePSMCT16.Reset();
|
|
m_swizzleTablePSMCT16S.Reset();
|
|
m_swizzleTablePSMT8.Reset();
|
|
m_swizzleTablePSMT4.Reset();
|
|
m_swizzleTablePSMZ32.Reset();
|
|
m_swizzleTablePSMZ16.Reset();
|
|
m_swizzleTablePSMZ16S.Reset();
|
|
|
|
m_context->device.vkDestroyDescriptorPool(m_context->device, m_context->descriptorPool, nullptr);
|
|
m_context->clutBuffer.Reset();
|
|
m_context->memoryBuffer.Reset();
|
|
m_context->memoryBufferCopy.Reset();
|
|
m_context->memoryBufferTransfer.Reset();
|
|
m_context->commandBufferPool.Reset();
|
|
m_context->device.Reset();
|
|
|
|
delete[] m_memoryCache;
|
|
m_memoryCache = nullptr;
|
|
}
|
|
|
|
void CGSH_Vulkan::ResetImpl()
|
|
{
|
|
m_vtxCount = 0;
|
|
m_primitiveType = PRIM_INVALID;
|
|
m_pendingPrim = false;
|
|
m_pendingPrimValue = 0;
|
|
m_regState.isValid = false;
|
|
memset(&m_clutStates, 0, sizeof(m_clutStates));
|
|
memset(m_memoryCache, 0, RAMSIZE);
|
|
WriteBackMemoryCache();
|
|
}
|
|
|
|
void CGSH_Vulkan::SetPresentationParams(const CGSHandler::PRESENTATION_PARAMS& presentationParams)
|
|
{
|
|
CGSHandler::SetPresentationParams(presentationParams);
|
|
m_present->ValidateSwapChain(presentationParams);
|
|
}
|
|
|
|
void CGSH_Vulkan::MarkNewFrame()
|
|
{
|
|
m_drawCallCount = m_frameCommandBuffer->GetFlushCount();
|
|
m_frameCommandBuffer->ResetFlushCount();
|
|
m_frameCommandBuffer->EndFrame();
|
|
m_frameCommandBuffer->BeginFrame();
|
|
CGSHandler::MarkNewFrame();
|
|
}
|
|
|
|
void CGSH_Vulkan::FlipImpl(const DISPLAY_INFO& dispInfo)
|
|
{
|
|
if(m_present)
|
|
{
|
|
m_present->SetPresentationViewport(GetPresentationViewport());
|
|
m_present->DoPresent(dispInfo);
|
|
}
|
|
|
|
PresentBackbuffer();
|
|
for(auto& xferHistoryPair : m_xferHistory)
|
|
{
|
|
xferHistoryPair.second.Advance();
|
|
}
|
|
std::experimental::erase_if(m_xferHistory,
|
|
[](const auto& xferTrackerPair) { return xferTrackerPair.second.IsEmpty(); });
|
|
CGSHandler::FlipImpl(dispInfo);
|
|
}
|
|
|
|
std::vector<VkPhysicalDevice> CGSH_Vulkan::GetPhysicalDevices()
|
|
{
|
|
auto result = VK_SUCCESS;
|
|
|
|
uint32_t physicalDeviceCount = 0;
|
|
result = m_instance.vkEnumeratePhysicalDevices(m_instance, &physicalDeviceCount, nullptr);
|
|
CHECKVULKANERROR(result);
|
|
|
|
std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
|
|
result = m_instance.vkEnumeratePhysicalDevices(m_instance, &physicalDeviceCount, physicalDevices.data());
|
|
CHECKVULKANERROR(result);
|
|
|
|
return physicalDevices;
|
|
}
|
|
|
|
uint32 CGSH_Vulkan::GetPhysicalDeviceIndex(const std::vector<VkPhysicalDevice>& physicalDevices) const
|
|
{
|
|
auto selectedDevice = CDeviceInfo::GetInstance().GetSelectedDevice();
|
|
for(uint32 i = 0; i < physicalDevices.size(); i++)
|
|
{
|
|
const auto& physicalDevice = physicalDevices[i];
|
|
VkPhysicalDeviceProperties physicalDeviceProperties = {};
|
|
m_instance.vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties);
|
|
if(
|
|
(physicalDeviceProperties.deviceID == selectedDevice.deviceId) &&
|
|
(physicalDeviceProperties.vendorID == selectedDevice.vendorId))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
std::vector<uint32_t> CGSH_Vulkan::GetRenderQueueFamilies(VkPhysicalDevice physicalDevice)
|
|
{
|
|
auto result = VK_SUCCESS;
|
|
std::vector<uint32_t> renderQueueFamilies;
|
|
|
|
uint32_t queueFamilyCount = 0;
|
|
m_instance.vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Found %d queue families.\r\n", queueFamilyCount);
|
|
|
|
std::vector<VkQueueFamilyProperties> queueFamilyPropertiesArray(queueFamilyCount);
|
|
m_instance.vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyPropertiesArray.data());
|
|
|
|
for(uint32_t queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
|
|
{
|
|
bool graphicsSupported = false;
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Queue Family Info:\r\n");
|
|
|
|
const auto& queueFamilyProperties = queueFamilyPropertiesArray[queueFamilyIndex];
|
|
CLog::GetInstance().Print(LOG_NAME, "Queue Count: %d\r\n", queueFamilyProperties.queueCount);
|
|
CLog::GetInstance().Print(LOG_NAME, "Operating modes:\r\n");
|
|
if(queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
|
{
|
|
graphicsSupported = true;
|
|
CLog::GetInstance().Print(LOG_NAME, " Graphics\r\n");
|
|
}
|
|
if(queueFamilyProperties.queueFlags & VK_QUEUE_COMPUTE_BIT)
|
|
{
|
|
CLog::GetInstance().Print(LOG_NAME, " Compute\r\n");
|
|
}
|
|
if(queueFamilyProperties.queueFlags & VK_QUEUE_TRANSFER_BIT)
|
|
{
|
|
CLog::GetInstance().Print(LOG_NAME, " Transfer\r\n");
|
|
}
|
|
if(queueFamilyProperties.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)
|
|
{
|
|
CLog::GetInstance().Print(LOG_NAME, " Sparse Binding\r\n");
|
|
}
|
|
|
|
VkBool32 surfaceSupported = VK_FALSE;
|
|
if(m_context->surface)
|
|
{
|
|
result = m_instance.vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, queueFamilyIndex, m_context->surface, &surfaceSupported);
|
|
CHECKVULKANERROR(result);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Supports surface: %d\r\n", surfaceSupported);
|
|
}
|
|
|
|
if(graphicsSupported && (!m_context->surface || surfaceSupported))
|
|
{
|
|
renderQueueFamilies.push_back(queueFamilyIndex);
|
|
}
|
|
}
|
|
|
|
return renderQueueFamilies;
|
|
}
|
|
|
|
std::vector<VkSurfaceFormatKHR> CGSH_Vulkan::GetDeviceSurfaceFormats(VkPhysicalDevice physicalDevice)
|
|
{
|
|
assert(m_context->surface != VK_NULL_HANDLE);
|
|
|
|
auto result = VK_SUCCESS;
|
|
|
|
uint32_t surfaceFormatCount = 0;
|
|
result = m_instance.vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, m_context->surface, &surfaceFormatCount, nullptr);
|
|
CHECKVULKANERROR(result);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Found %d surface formats.\r\n", surfaceFormatCount);
|
|
|
|
std::vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatCount);
|
|
result = m_instance.vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, m_context->surface, &surfaceFormatCount, surfaceFormats.data());
|
|
CHECKVULKANERROR(result);
|
|
|
|
for(const auto& surfaceFormat : surfaceFormats)
|
|
{
|
|
CLog::GetInstance().Print(LOG_NAME, "Surface Format Info:\r\n");
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Format: %d\r\n", surfaceFormat.format);
|
|
CLog::GetInstance().Print(LOG_NAME, "Color Space: %d\r\n", surfaceFormat.colorSpace);
|
|
}
|
|
|
|
return surfaceFormats;
|
|
}
|
|
|
|
void CGSH_Vulkan::CreateDevice(VkPhysicalDevice physicalDevice)
|
|
{
|
|
assert(m_context->device.IsEmpty());
|
|
|
|
float queuePriorities[] = {1.0f};
|
|
|
|
auto deviceQueueCreateInfo = Framework::Vulkan::DeviceQueueCreateInfo();
|
|
deviceQueueCreateInfo.flags = 0;
|
|
deviceQueueCreateInfo.queueFamilyIndex = 0;
|
|
deviceQueueCreateInfo.queueCount = 1;
|
|
deviceQueueCreateInfo.pQueuePriorities = queuePriorities;
|
|
|
|
std::vector<const char*> enabledExtensions;
|
|
enabledExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
|
#if GSH_VULKAN_IS_DESKTOP
|
|
enabledExtensions.push_back(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME);
|
|
#endif
|
|
|
|
std::vector<const char*> enabledLayers;
|
|
|
|
Framework::Vulkan::CStructChain createDeviceStructs;
|
|
|
|
#if GSH_VULKAN_IS_DESKTOP
|
|
{
|
|
auto physicalDeviceFeaturesInvocationInterlock = Framework::Vulkan::PhysicalDeviceFragmentShaderInterlockFeaturesEXT();
|
|
physicalDeviceFeaturesInvocationInterlock.fragmentShaderPixelInterlock = VK_TRUE;
|
|
createDeviceStructs.AddStruct(physicalDeviceFeaturesInvocationInterlock);
|
|
}
|
|
#endif
|
|
|
|
{
|
|
auto physicalDeviceFeatures2 = Framework::Vulkan::PhysicalDeviceFeatures2KHR();
|
|
#ifndef __APPLE__
|
|
//MoltenVK doesn't report this properly (probably due to mobile devices supporting buffer stores and not image stores)
|
|
physicalDeviceFeatures2.features.fragmentStoresAndAtomics = VK_TRUE;
|
|
#endif
|
|
#if GSH_VULKAN_IS_DESKTOP
|
|
physicalDeviceFeatures2.features.shaderInt16 = VK_TRUE;
|
|
#endif
|
|
createDeviceStructs.AddStruct(physicalDeviceFeatures2);
|
|
}
|
|
|
|
#if GSH_VULKAN_IS_DESKTOP
|
|
{
|
|
auto physicalDeviceVulkan12Features = Framework::Vulkan::PhysicalDeviceVulkan12Features();
|
|
physicalDeviceVulkan12Features.shaderInt8 = VK_TRUE;
|
|
physicalDeviceVulkan12Features.storageBuffer8BitAccess = VK_TRUE;
|
|
physicalDeviceVulkan12Features.uniformAndStorageBuffer8BitAccess = VK_TRUE;
|
|
createDeviceStructs.AddStruct(physicalDeviceVulkan12Features);
|
|
}
|
|
|
|
{
|
|
auto physicalDevice16BitStorageFeatures = Framework::Vulkan::PhysicalDevice16BitStorageFeatures();
|
|
physicalDevice16BitStorageFeatures.storageBuffer16BitAccess = VK_TRUE;
|
|
physicalDevice16BitStorageFeatures.uniformAndStorageBuffer16BitAccess = VK_TRUE;
|
|
createDeviceStructs.AddStruct(physicalDevice16BitStorageFeatures);
|
|
}
|
|
#endif
|
|
|
|
auto deviceCreateInfo = Framework::Vulkan::DeviceCreateInfo();
|
|
deviceCreateInfo.pNext = createDeviceStructs.GetNext();
|
|
deviceCreateInfo.flags = 0;
|
|
deviceCreateInfo.enabledLayerCount = static_cast<uint32>(enabledLayers.size());
|
|
deviceCreateInfo.ppEnabledLayerNames = enabledLayers.data();
|
|
deviceCreateInfo.enabledExtensionCount = static_cast<uint32>(enabledExtensions.size());
|
|
deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();
|
|
deviceCreateInfo.queueCreateInfoCount = 1;
|
|
deviceCreateInfo.pQueueCreateInfos = &deviceQueueCreateInfo;
|
|
|
|
m_context->device = Framework::Vulkan::CDevice(m_instance, physicalDevice, deviceCreateInfo);
|
|
|
|
{
|
|
VkPhysicalDeviceProperties deviceProperties = {};
|
|
m_context->instance->vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
|
|
m_context->storageBufferAlignment = deviceProperties.limits.minStorageBufferOffsetAlignment;
|
|
m_context->computeWorkgroupInvocations = deviceProperties.limits.maxComputeWorkGroupInvocations;
|
|
}
|
|
|
|
m_context->annotations = decltype(m_context->annotations)(m_context->instance, &m_context->device);
|
|
}
|
|
|
|
void CGSH_Vulkan::CreateDescriptorPool()
|
|
{
|
|
std::vector<VkDescriptorPoolSize> poolSizes;
|
|
|
|
{
|
|
VkDescriptorPoolSize poolSize = {};
|
|
poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
|
poolSize.descriptorCount = 0x800;
|
|
poolSizes.push_back(poolSize);
|
|
}
|
|
|
|
{
|
|
VkDescriptorPoolSize poolSize = {};
|
|
poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
poolSize.descriptorCount = 0x800;
|
|
poolSizes.push_back(poolSize);
|
|
}
|
|
|
|
{
|
|
VkDescriptorPoolSize poolSize = {};
|
|
poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
|
|
poolSize.descriptorCount = 0x800;
|
|
poolSizes.push_back(poolSize);
|
|
}
|
|
|
|
{
|
|
VkDescriptorPoolSize poolSize = {};
|
|
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
|
poolSize.descriptorCount = 0x800;
|
|
poolSizes.push_back(poolSize);
|
|
}
|
|
|
|
{
|
|
VkDescriptorPoolSize poolSize = {};
|
|
poolSize.type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
|
|
poolSize.descriptorCount = 0x800;
|
|
poolSizes.push_back(poolSize);
|
|
}
|
|
|
|
auto descriptorPoolCreateInfo = Framework::Vulkan::DescriptorPoolCreateInfo();
|
|
descriptorPoolCreateInfo.poolSizeCount = static_cast<uint32>(poolSizes.size());
|
|
descriptorPoolCreateInfo.pPoolSizes = poolSizes.data();
|
|
descriptorPoolCreateInfo.maxSets = 0x1000;
|
|
|
|
auto result = m_context->device.vkCreateDescriptorPool(m_context->device, &descriptorPoolCreateInfo, nullptr, &m_context->descriptorPool);
|
|
CHECKVULKANERROR(result);
|
|
}
|
|
|
|
void CGSH_Vulkan::CreateMemoryBuffer()
|
|
{
|
|
assert(m_context->memoryBuffer.IsEmpty());
|
|
assert(!m_memoryCache);
|
|
|
|
m_context->memoryBuffer = Framework::Vulkan::CBuffer(m_context->device,
|
|
m_context->physicalDeviceMemoryProperties,
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
RAMSIZE);
|
|
m_context->annotations.SetBufferName(m_context->memoryBuffer, "GS Memory");
|
|
|
|
m_memoryCache = new uint8[RAMSIZE];
|
|
memset(m_memoryCache, 0, RAMSIZE);
|
|
|
|
m_context->memoryBuffer.Write(m_context->queue, m_context->commandBufferPool,
|
|
m_context->physicalDeviceMemoryProperties, m_memoryCache);
|
|
|
|
m_context->memoryBufferCopy = Framework::Vulkan::CBuffer(m_context->device,
|
|
m_context->physicalDeviceMemoryProperties,
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
RAMSIZE);
|
|
m_context->annotations.SetBufferName(m_context->memoryBufferCopy, "GS Memory Copy");
|
|
|
|
m_context->memoryBufferTransfer = Framework::Vulkan::CBuffer(m_context->device,
|
|
m_context->physicalDeviceMemoryProperties,
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
|
RAMSIZE);
|
|
m_context->annotations.SetBufferName(m_context->memoryBufferTransfer, "GS Memory Transfer");
|
|
}
|
|
|
|
void CGSH_Vulkan::CreateClutBuffer()
|
|
{
|
|
assert(m_context->clutBuffer.IsEmpty());
|
|
|
|
static const uint32 clutBufferSize = CLUTENTRYCOUNT * sizeof(uint32) * CLUT_CACHE_SIZE;
|
|
m_context->clutBuffer = Framework::Vulkan::CBuffer(m_context->device,
|
|
m_context->physicalDeviceMemoryProperties,
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
clutBufferSize);
|
|
m_context->annotations.SetBufferName(m_context->clutBuffer, "CLUT Buffer");
|
|
}
|
|
|
|
void CGSH_Vulkan::ProcessPrim(uint64 data)
|
|
{
|
|
unsigned int newPrimitiveType = static_cast<unsigned int>(data & 0x07);
|
|
if(newPrimitiveType != m_primitiveType)
|
|
{
|
|
m_draw->FlushVertices();
|
|
}
|
|
m_primitiveType = newPrimitiveType;
|
|
switch(m_primitiveType)
|
|
{
|
|
case PRIM_POINT:
|
|
m_vtxCount = 1;
|
|
break;
|
|
case PRIM_LINE:
|
|
case PRIM_LINESTRIP:
|
|
m_vtxCount = 2;
|
|
break;
|
|
case PRIM_TRIANGLE:
|
|
case PRIM_TRIANGLESTRIP:
|
|
case PRIM_TRIANGLEFAN:
|
|
m_vtxCount = 3;
|
|
break;
|
|
case PRIM_SPRITE:
|
|
m_vtxCount = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGSH_Vulkan::VertexKick(uint8 registerId, uint64 data)
|
|
{
|
|
if(m_pendingPrim)
|
|
{
|
|
m_pendingPrim = false;
|
|
ProcessPrim(m_pendingPrimValue);
|
|
}
|
|
|
|
if(m_vtxCount == 0) return;
|
|
|
|
bool drawingKick = (registerId == GS_REG_XYZ2) || (registerId == GS_REG_XYZF2);
|
|
bool fog = (registerId == GS_REG_XYZF2) || (registerId == GS_REG_XYZF3);
|
|
|
|
if(!m_drawEnabled) drawingKick = false;
|
|
|
|
if(fog)
|
|
{
|
|
m_vtxBuffer[m_vtxCount - 1].position = data & 0x00FFFFFFFFFFFFFFULL;
|
|
m_vtxBuffer[m_vtxCount - 1].rgbaq = m_nReg[GS_REG_RGBAQ];
|
|
m_vtxBuffer[m_vtxCount - 1].uv = m_nReg[GS_REG_UV];
|
|
m_vtxBuffer[m_vtxCount - 1].st = m_nReg[GS_REG_ST];
|
|
m_vtxBuffer[m_vtxCount - 1].fog = static_cast<uint8>(data >> 56);
|
|
}
|
|
else
|
|
{
|
|
m_vtxBuffer[m_vtxCount - 1].position = data;
|
|
m_vtxBuffer[m_vtxCount - 1].rgbaq = m_nReg[GS_REG_RGBAQ];
|
|
m_vtxBuffer[m_vtxCount - 1].uv = m_nReg[GS_REG_UV];
|
|
m_vtxBuffer[m_vtxCount - 1].st = m_nReg[GS_REG_ST];
|
|
m_vtxBuffer[m_vtxCount - 1].fog = static_cast<uint8>(m_nReg[GS_REG_FOG] >> 56);
|
|
}
|
|
|
|
m_vtxCount--;
|
|
|
|
if(m_vtxCount == 0)
|
|
{
|
|
if((m_nReg[GS_REG_PRMODECONT] & 1) != 0)
|
|
{
|
|
m_primitiveMode <<= m_nReg[GS_REG_PRIM];
|
|
}
|
|
else
|
|
{
|
|
m_primitiveMode <<= m_nReg[GS_REG_PRMODE];
|
|
}
|
|
|
|
if(drawingKick)
|
|
{
|
|
SetRenderingContext(m_primitiveMode);
|
|
}
|
|
|
|
switch(m_primitiveType)
|
|
{
|
|
case PRIM_POINT:
|
|
if(drawingKick) Prim_Point();
|
|
m_vtxCount = 1;
|
|
break;
|
|
case PRIM_LINE:
|
|
if(drawingKick) Prim_Line();
|
|
m_vtxCount = 2;
|
|
break;
|
|
case PRIM_LINESTRIP:
|
|
if(drawingKick) Prim_Line();
|
|
memcpy(&m_vtxBuffer[1], &m_vtxBuffer[0], sizeof(VERTEX));
|
|
m_vtxCount = 1;
|
|
break;
|
|
case PRIM_TRIANGLE:
|
|
if(drawingKick) Prim_Triangle();
|
|
m_vtxCount = 3;
|
|
break;
|
|
case PRIM_TRIANGLESTRIP:
|
|
if(drawingKick) Prim_Triangle();
|
|
memcpy(&m_vtxBuffer[2], &m_vtxBuffer[1], sizeof(VERTEX));
|
|
memcpy(&m_vtxBuffer[1], &m_vtxBuffer[0], sizeof(VERTEX));
|
|
m_vtxCount = 1;
|
|
break;
|
|
case PRIM_TRIANGLEFAN:
|
|
if(drawingKick) Prim_Triangle();
|
|
memcpy(&m_vtxBuffer[1], &m_vtxBuffer[0], sizeof(VERTEX));
|
|
m_vtxCount = 1;
|
|
break;
|
|
case PRIM_SPRITE:
|
|
if(drawingKick) Prim_Sprite();
|
|
m_vtxCount = 2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::pair<uint32, uint32> GetMipLevelInfo(uint32 level, const CGSHandler::MIPTBP1& miptbp1, const CGSHandler::MIPTBP2& miptbp2)
|
|
{
|
|
switch(level)
|
|
{
|
|
default:
|
|
assert(false);
|
|
return std::pair<uint32, uint32>(0, 0);
|
|
case 1:
|
|
return std::pair<uint32, uint32>(miptbp1.GetTbp1(), miptbp1.GetTbw1());
|
|
case 2:
|
|
return std::pair<uint32, uint32>(miptbp1.GetTbp2(), miptbp1.GetTbw2());
|
|
case 3:
|
|
return std::pair<uint32, uint32>(miptbp1.GetTbp3(), miptbp1.GetTbw3());
|
|
case 4:
|
|
return std::pair<uint32, uint32>(miptbp2.GetTbp4(), miptbp2.GetTbw4());
|
|
case 5:
|
|
return std::pair<uint32, uint32>(miptbp2.GetTbp5(), miptbp2.GetTbw5());
|
|
case 6:
|
|
return std::pair<uint32, uint32>(miptbp2.GetTbp6(), miptbp2.GetTbw6());
|
|
}
|
|
}
|
|
|
|
void CGSH_Vulkan::SetRenderingContext(uint64 primReg)
|
|
{
|
|
auto prim = make_convertible<PRMODE>(primReg);
|
|
|
|
unsigned int context = prim.nContext;
|
|
|
|
auto offset = make_convertible<XYOFFSET>(m_nReg[GS_REG_XYOFFSET_1 + context]);
|
|
auto frame = make_convertible<FRAME>(m_nReg[GS_REG_FRAME_1 + context]);
|
|
auto zbuf = make_convertible<ZBUF>(m_nReg[GS_REG_ZBUF_1 + context]);
|
|
auto tex0 = make_convertible<TEX0>(m_nReg[GS_REG_TEX0_1 + context]);
|
|
auto tex1 = make_convertible<TEX1>(m_nReg[GS_REG_TEX1_1 + context]);
|
|
auto miptbp1 = make_convertible<MIPTBP1>(m_nReg[GS_REG_MIPTBP1_1 + context]);
|
|
auto miptbp2 = make_convertible<MIPTBP2>(m_nReg[GS_REG_MIPTBP2_1 + context]);
|
|
auto clamp = make_convertible<CLAMP>(m_nReg[GS_REG_CLAMP_1 + context]);
|
|
auto alpha = make_convertible<ALPHA>(m_nReg[GS_REG_ALPHA_1 + context]);
|
|
auto scissor = make_convertible<SCISSOR>(m_nReg[GS_REG_SCISSOR_1 + context]);
|
|
auto test = make_convertible<TEST>(m_nReg[GS_REG_TEST_1 + context]);
|
|
auto texA = make_convertible<TEXA>(m_nReg[GS_REG_TEXA]);
|
|
auto fogCol = make_convertible<FOGCOL>(m_nReg[GS_REG_FOGCOL]);
|
|
auto scanMask = m_nReg[GS_REG_SCANMSK] & 3;
|
|
auto colClamp = m_nReg[GS_REG_COLCLAMP] & 1;
|
|
auto fba = m_nReg[GS_REG_FBA_1 + context] & 1;
|
|
|
|
auto pipelineCaps = make_convertible<CDraw::PIPELINE_CAPS>(0);
|
|
if(prim.nTexture)
|
|
{
|
|
pipelineCaps.hasTexture = 1;
|
|
pipelineCaps.textureHasAlpha = tex0.nColorComp;
|
|
pipelineCaps.textureBlackIsTransparent = texA.nAEM;
|
|
pipelineCaps.textureFunction = tex0.nFunction;
|
|
pipelineCaps.texClampU = clamp.nWMS;
|
|
pipelineCaps.texClampV = clamp.nWMT;
|
|
pipelineCaps.textureFormat = tex0.nPsm;
|
|
pipelineCaps.clutFormat = tex0.nCPSM;
|
|
}
|
|
pipelineCaps.hasFog = prim.nFog;
|
|
pipelineCaps.scanMask = scanMask;
|
|
pipelineCaps.hasAlphaBlending = prim.nAlpha && m_alphaBlendingEnabled;
|
|
pipelineCaps.colClamp = colClamp;
|
|
pipelineCaps.fba = fba;
|
|
pipelineCaps.hasDstAlphaTest = test.nDestAlphaEnabled;
|
|
pipelineCaps.dstAlphaTestRef = test.nDestAlphaMode;
|
|
pipelineCaps.writeDepth = (zbuf.nMask == 0) && (test.nDepthEnabled != 0); //Depth test disabled -> no writes to depth buffer
|
|
pipelineCaps.framebufferFormat = frame.nPsm;
|
|
pipelineCaps.depthbufferFormat = zbuf.nPsm | 0x30;
|
|
|
|
switch(m_primitiveType)
|
|
{
|
|
default:
|
|
assert(false);
|
|
[[fallthrough]];
|
|
case PRIM_TRIANGLE:
|
|
case PRIM_TRIANGLEFAN:
|
|
case PRIM_TRIANGLESTRIP:
|
|
case PRIM_SPRITE:
|
|
pipelineCaps.primitiveType = CDraw::PIPELINE_PRIMITIVE_TRIANGLE;
|
|
break;
|
|
case PRIM_LINE:
|
|
case PRIM_LINESTRIP:
|
|
pipelineCaps.primitiveType = CDraw::PIPELINE_PRIMITIVE_LINE;
|
|
break;
|
|
case PRIM_POINT:
|
|
pipelineCaps.primitiveType = CDraw::PIPELINE_PRIMITIVE_POINT;
|
|
break;
|
|
}
|
|
|
|
uint32 fbWriteMask = ~frame.nMask;
|
|
|
|
uint32 texBufPtr = tex0.GetBufPtr();
|
|
uint32 texBufWidth = tex0.GetBufWidth();
|
|
uint32 texMipLevel = 0;
|
|
|
|
if(prim.nTexture)
|
|
{
|
|
bool minLinear = false;
|
|
bool magLinear = false;
|
|
|
|
switch(tex1.nMinFilter)
|
|
{
|
|
case MIN_FILTER_NEAREST:
|
|
case MIN_FILTER_NEAREST_MIP_NEAREST:
|
|
case MIN_FILTER_NEAREST_MIP_LINEAR:
|
|
minLinear = false;
|
|
break;
|
|
case MIN_FILTER_LINEAR:
|
|
case MIN_FILTER_LINEAR_MIP_NEAREST:
|
|
case MIN_FILTER_LINEAR_MIP_LINEAR:
|
|
minLinear = true;
|
|
break;
|
|
}
|
|
|
|
switch(tex1.nMagFilter)
|
|
{
|
|
case MAG_FILTER_NEAREST:
|
|
magLinear = false;
|
|
break;
|
|
case MAG_FILTER_LINEAR:
|
|
magLinear = true;
|
|
break;
|
|
}
|
|
|
|
//Ignore min filter if we don't have mipmap levels.
|
|
if(tex1.nMaxMip == 0)
|
|
{
|
|
minLinear = magLinear;
|
|
}
|
|
else
|
|
{
|
|
bool hasMip = (tex1.nMinFilter >= MIN_FILTER_NEAREST_MIP_NEAREST);
|
|
if(hasMip)
|
|
{
|
|
if(tex1.nLODMethod == LOD_CALC_STATIC)
|
|
{
|
|
int k = trunc(tex1.GetK());
|
|
texMipLevel = std::clamp<int>(k, 0, tex1.nMaxMip);
|
|
if(texMipLevel != 0)
|
|
{
|
|
auto mipLevelInfo = GetMipLevelInfo(texMipLevel, miptbp1, miptbp2);
|
|
texBufPtr = mipLevelInfo.first;
|
|
texBufWidth = mipLevelInfo.second;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pipelineCaps.textureUseDynamicMipLOD = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
pipelineCaps.textureUseLinearFiltering = (minLinear && magLinear);
|
|
}
|
|
|
|
if(prim.nAlpha)
|
|
{
|
|
pipelineCaps.alphaA = alpha.nA;
|
|
pipelineCaps.alphaB = alpha.nB;
|
|
pipelineCaps.alphaC = alpha.nC;
|
|
pipelineCaps.alphaD = alpha.nD;
|
|
}
|
|
|
|
pipelineCaps.depthTestFunction = test.nDepthMethod;
|
|
if(!test.nDepthEnabled || !m_depthTestingEnabled)
|
|
{
|
|
pipelineCaps.depthTestFunction = CGSHandler::DEPTH_TEST_ALWAYS;
|
|
}
|
|
|
|
pipelineCaps.alphaTestFunction = test.nAlphaMethod;
|
|
pipelineCaps.alphaTestFailAction = test.nAlphaFail;
|
|
if(!test.nAlphaEnabled || !m_alphaTestingEnabled)
|
|
{
|
|
pipelineCaps.alphaTestFunction = CGSHandler::ALPHA_TEST_ALWAYS;
|
|
}
|
|
|
|
//Convert alpha testing to write masking if possible
|
|
if(
|
|
(pipelineCaps.alphaTestFunction == CGSHandler::ALPHA_TEST_NEVER) &&
|
|
(pipelineCaps.alphaTestFailAction == CGSHandler::ALPHA_TEST_FAIL_FBONLY))
|
|
{
|
|
pipelineCaps.alphaTestFunction = CGSHandler::ALPHA_TEST_ALWAYS;
|
|
pipelineCaps.writeDepth = 0;
|
|
}
|
|
|
|
if(
|
|
(pipelineCaps.alphaTestFunction == CGSHandler::ALPHA_TEST_NEVER) &&
|
|
(pipelineCaps.alphaTestFailAction == CGSHandler::ALPHA_TEST_FAIL_RGBONLY))
|
|
{
|
|
pipelineCaps.alphaTestFunction = CGSHandler::ALPHA_TEST_ALWAYS;
|
|
pipelineCaps.writeDepth = 0;
|
|
fbWriteMask &= 0x00FFFFFF;
|
|
}
|
|
|
|
switch(frame.nPsm)
|
|
{
|
|
case CGSHandler::PSMCT32:
|
|
case CGSHandler::PSMZ32:
|
|
pipelineCaps.maskColor = (fbWriteMask != 0xFFFFFFFF);
|
|
break;
|
|
case CGSHandler::PSMCT24:
|
|
case CGSHandler::PSMZ24:
|
|
fbWriteMask = fbWriteMask & 0x00FFFFFF;
|
|
pipelineCaps.maskColor = (fbWriteMask != 0x00FFFFFF);
|
|
break;
|
|
case CGSHandler::PSMCT16:
|
|
case CGSHandler::PSMCT16S:
|
|
case CGSHandler::PSMZ16S:
|
|
fbWriteMask = RGBA32ToRGBA16(fbWriteMask) & 0xFFFF;
|
|
pipelineCaps.maskColor = (fbWriteMask != 0xFFFF);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
//Check if we need to save a copy of RAM because primitive writes to texture area
|
|
bool needsTextureCopy = false;
|
|
uint32 memoryCopyAddress = 0;
|
|
uint32 memoryCopySize = 0;
|
|
if((m_primitiveType == PRIM_SPRITE) && pipelineCaps.hasTexture)
|
|
{
|
|
uint32 texBufPtr = tex0.GetBufPtr();
|
|
uint32 frameBufPtr = frame.GetBasePtr();
|
|
uint32 depthBufPtr = zbuf.GetBasePtr();
|
|
bool isTexUpperBytePsm = CGsPixelFormats::IsPsmUpperByte(tex0.nPsm);
|
|
{
|
|
bool isFrame24Bits = CGsPixelFormats::IsPsm24Bits(frame.nPsm);
|
|
needsTextureCopy |= (texBufPtr == frameBufPtr) && !(isTexUpperBytePsm && isFrame24Bits);
|
|
}
|
|
if(pipelineCaps.writeDepth)
|
|
{
|
|
bool isDepth24Bits = CGsPixelFormats::IsPsm24Bits(zbuf.nPsm);
|
|
needsTextureCopy |= (texBufPtr == depthBufPtr) && !(isTexUpperBytePsm && isDepth24Bits);
|
|
}
|
|
if(needsTextureCopy)
|
|
{
|
|
CGsCachedArea textureArea;
|
|
textureArea.SetArea(tex0.nPsm, tex0.nBufPtr, tex0.GetBufWidth(), tex0.GetHeight());
|
|
memoryCopyAddress = texBufPtr;
|
|
memoryCopySize = textureArea.GetSize();
|
|
if((memoryCopyAddress + memoryCopySize) > RAMSIZE)
|
|
{
|
|
//Some games (Castlevania: Curse of Darkness) have a texture that goes beyond
|
|
//RAMSIZE, make sure we stay inside bounds.
|
|
memoryCopySize = RAMSIZE - memoryCopyAddress;
|
|
}
|
|
m_draw->SetMemoryCopyParams(memoryCopyAddress, memoryCopySize);
|
|
}
|
|
}
|
|
pipelineCaps.textureUseMemoryCopy = needsTextureCopy;
|
|
|
|
m_draw->SetPipelineCaps(pipelineCaps);
|
|
m_draw->SetFramebufferParams(frame.GetBasePtr(), frame.GetWidth(), fbWriteMask);
|
|
m_draw->SetDepthbufferParams(zbuf.GetBasePtr(), frame.GetWidth());
|
|
m_draw->SetTextureParams(texBufPtr, texBufWidth, tex0.GetWidth(), tex0.GetHeight(), texMipLevel, tex0.nCSA * 0x10);
|
|
if(
|
|
pipelineCaps.textureUseDynamicMipLOD &&
|
|
(!m_regState.isValid ||
|
|
(m_regState.tex1 != tex1) ||
|
|
(m_regState.miptbp1 != miptbp1) ||
|
|
(m_regState.miptbp2 != miptbp2)))
|
|
{
|
|
CDraw::MipBufs mipBufs;
|
|
for(int i = 1; i <= tex1.nMaxMip; i++)
|
|
{
|
|
mipBufs[i - 1] = GetMipLevelInfo(i, miptbp1, miptbp2);
|
|
}
|
|
for(int i = tex1.nMaxMip + 1; i <= 6; i++)
|
|
{
|
|
mipBufs[i - 1] = std::pair<uint32, uint32>(0, 0);
|
|
}
|
|
m_draw->SetMipParams(mipBufs, tex1.nMaxMip, tex1.GetK(), tex1.nLODL);
|
|
}
|
|
m_draw->SetTextureAlphaParams(texA.nTA0, texA.nTA1);
|
|
m_draw->SetTextureClampParams(
|
|
clamp.GetMinU(), clamp.GetMinV(),
|
|
clamp.GetMaxU(), clamp.GetMaxV());
|
|
m_draw->SetFogParams(
|
|
static_cast<float>(fogCol.nFCR) / 255.f,
|
|
static_cast<float>(fogCol.nFCG) / 255.f,
|
|
static_cast<float>(fogCol.nFCB) / 255.f);
|
|
m_draw->SetAlphaBlendingParams(alpha.nFix);
|
|
m_draw->SetAlphaTestParams(test.nAlphaRef);
|
|
m_draw->SetScissor(scissor.scax0, scissor.scay0,
|
|
scissor.scax1 - scissor.scax0 + 1,
|
|
scissor.scay1 - scissor.scay0 + 1);
|
|
|
|
m_fbBasePtr = frame.GetBasePtr();
|
|
|
|
m_primOfsX = offset.GetX();
|
|
m_primOfsY = offset.GetY();
|
|
|
|
m_texWidth = tex0.GetWidth();
|
|
m_texHeight = tex0.GetHeight();
|
|
|
|
m_regState.isValid = true;
|
|
m_regState.tex1 = tex1;
|
|
m_regState.miptbp1 = miptbp1;
|
|
m_regState.miptbp2 = miptbp2;
|
|
}
|
|
|
|
void CGSH_Vulkan::Prim_Point()
|
|
{
|
|
auto xyz = make_convertible<XYZ>(m_vtxBuffer[0].position);
|
|
auto rgbaq = make_convertible<RGBAQ>(m_vtxBuffer[0].rgbaq);
|
|
|
|
float x = xyz.GetX();
|
|
float y = xyz.GetY();
|
|
uint32 z = xyz.nZ;
|
|
|
|
x -= m_primOfsX;
|
|
y -= m_primOfsY;
|
|
|
|
auto color = MakeColor(
|
|
rgbaq.nR, rgbaq.nG,
|
|
rgbaq.nB, rgbaq.nA);
|
|
|
|
// clang-format off
|
|
CDraw::PRIM_VERTEX vertex =
|
|
{
|
|
//x, y, z, color, s, t, q, f
|
|
x, y, z, color, 0, 0, 1, 0,
|
|
};
|
|
// clang-format on
|
|
|
|
m_draw->AddVertices(&vertex, &vertex + 1);
|
|
}
|
|
|
|
void CGSH_Vulkan::Prim_Line()
|
|
{
|
|
XYZ pos[2];
|
|
pos[0] <<= m_vtxBuffer[1].position;
|
|
pos[1] <<= m_vtxBuffer[0].position;
|
|
|
|
float x1 = pos[0].GetX(), x2 = pos[1].GetX();
|
|
float y1 = pos[0].GetY(), y2 = pos[1].GetY();
|
|
uint32 z1 = pos[0].nZ, z2 = pos[1].nZ;
|
|
|
|
RGBAQ rgbaq[2];
|
|
rgbaq[0] <<= m_vtxBuffer[1].rgbaq;
|
|
rgbaq[1] <<= m_vtxBuffer[0].rgbaq;
|
|
|
|
x1 -= m_primOfsX;
|
|
x2 -= m_primOfsX;
|
|
|
|
y1 -= m_primOfsY;
|
|
y2 -= m_primOfsY;
|
|
|
|
float s[2] = {0, 0};
|
|
float t[2] = {0, 0};
|
|
float q[2] = {1, 1};
|
|
|
|
if(m_primitiveMode.nTexture)
|
|
{
|
|
if(m_primitiveMode.nUseUV)
|
|
{
|
|
UV uv[3];
|
|
uv[0] <<= m_vtxBuffer[1].uv;
|
|
uv[1] <<= m_vtxBuffer[0].uv;
|
|
|
|
s[0] = uv[0].GetU() / static_cast<float>(m_texWidth);
|
|
s[1] = uv[1].GetU() / static_cast<float>(m_texWidth);
|
|
|
|
t[0] = uv[0].GetV() / static_cast<float>(m_texHeight);
|
|
t[1] = uv[1].GetV() / static_cast<float>(m_texHeight);
|
|
|
|
m_lastLineU = s[0];
|
|
m_lastLineV = t[0];
|
|
}
|
|
else
|
|
{
|
|
ST st[2];
|
|
st[0] <<= m_vtxBuffer[1].st;
|
|
st[1] <<= m_vtxBuffer[0].st;
|
|
|
|
s[0] = st[0].nS;
|
|
s[1] = st[1].nS;
|
|
t[0] = st[0].nT;
|
|
t[1] = st[1].nT;
|
|
|
|
q[0] = rgbaq[0].nQ;
|
|
q[1] = rgbaq[1].nQ;
|
|
}
|
|
}
|
|
|
|
auto color1 = MakeColor(
|
|
rgbaq[0].nR, rgbaq[0].nG,
|
|
rgbaq[0].nB, rgbaq[0].nA);
|
|
|
|
auto color2 = MakeColor(
|
|
rgbaq[1].nR, rgbaq[1].nG,
|
|
rgbaq[1].nB, rgbaq[1].nA);
|
|
|
|
// clang-format off
|
|
CDraw::PRIM_VERTEX vertices[] =
|
|
{
|
|
{ x1, y1, z1, color1, s[0], t[0], q[0], 0 },
|
|
{ x2, y2, z2, color2, s[1], t[1], q[1], 0 },
|
|
};
|
|
// clang-format on
|
|
|
|
m_draw->AddVertices(std::begin(vertices), std::end(vertices));
|
|
}
|
|
|
|
void CGSH_Vulkan::Prim_Triangle()
|
|
{
|
|
XYZ pos[3];
|
|
pos[0] <<= m_vtxBuffer[2].position;
|
|
pos[1] <<= m_vtxBuffer[1].position;
|
|
pos[2] <<= m_vtxBuffer[0].position;
|
|
|
|
float x1 = pos[0].GetX(), x2 = pos[1].GetX(), x3 = pos[2].GetX();
|
|
float y1 = pos[0].GetY(), y2 = pos[1].GetY(), y3 = pos[2].GetY();
|
|
uint32 z1 = pos[0].nZ, z2 = pos[1].nZ, z3 = pos[2].nZ;
|
|
|
|
RGBAQ rgbaq[3];
|
|
rgbaq[0] <<= m_vtxBuffer[2].rgbaq;
|
|
rgbaq[1] <<= m_vtxBuffer[1].rgbaq;
|
|
rgbaq[2] <<= m_vtxBuffer[0].rgbaq;
|
|
|
|
x1 -= m_primOfsX;
|
|
x2 -= m_primOfsX;
|
|
x3 -= m_primOfsX;
|
|
|
|
y1 -= m_primOfsY;
|
|
y2 -= m_primOfsY;
|
|
y3 -= m_primOfsY;
|
|
|
|
float s[3] = {0, 0, 0};
|
|
float t[3] = {0, 0, 0};
|
|
float q[3] = {1, 1, 1};
|
|
|
|
float f[3] = {0, 0, 0};
|
|
|
|
if(m_primitiveMode.nFog)
|
|
{
|
|
f[0] = static_cast<float>(0xFF - m_vtxBuffer[2].fog) / 255.0f;
|
|
f[1] = static_cast<float>(0xFF - m_vtxBuffer[1].fog) / 255.0f;
|
|
f[2] = static_cast<float>(0xFF - m_vtxBuffer[0].fog) / 255.0f;
|
|
}
|
|
|
|
if(m_primitiveMode.nTexture)
|
|
{
|
|
if(m_primitiveMode.nUseUV)
|
|
{
|
|
UV uv[3];
|
|
uv[0] <<= m_vtxBuffer[2].uv;
|
|
uv[1] <<= m_vtxBuffer[1].uv;
|
|
uv[2] <<= m_vtxBuffer[0].uv;
|
|
|
|
s[0] = uv[0].GetU() / static_cast<float>(m_texWidth);
|
|
s[1] = uv[1].GetU() / static_cast<float>(m_texWidth);
|
|
s[2] = uv[2].GetU() / static_cast<float>(m_texWidth);
|
|
|
|
t[0] = uv[0].GetV() / static_cast<float>(m_texHeight);
|
|
t[1] = uv[1].GetV() / static_cast<float>(m_texHeight);
|
|
t[2] = uv[2].GetV() / static_cast<float>(m_texHeight);
|
|
}
|
|
else
|
|
{
|
|
ST st[3];
|
|
st[0] <<= m_vtxBuffer[2].st;
|
|
st[1] <<= m_vtxBuffer[1].st;
|
|
st[2] <<= m_vtxBuffer[0].st;
|
|
|
|
s[0] = st[0].nS;
|
|
s[1] = st[1].nS;
|
|
s[2] = st[2].nS;
|
|
t[0] = st[0].nT;
|
|
t[1] = st[1].nT;
|
|
t[2] = st[2].nT;
|
|
|
|
q[0] = rgbaq[0].nQ;
|
|
q[1] = rgbaq[1].nQ;
|
|
q[2] = rgbaq[2].nQ;
|
|
}
|
|
}
|
|
|
|
auto color1 = MakeColor(
|
|
rgbaq[0].nR, rgbaq[0].nG,
|
|
rgbaq[0].nB, rgbaq[0].nA);
|
|
|
|
auto color2 = MakeColor(
|
|
rgbaq[1].nR, rgbaq[1].nG,
|
|
rgbaq[1].nB, rgbaq[1].nA);
|
|
|
|
auto color3 = MakeColor(
|
|
rgbaq[2].nR, rgbaq[2].nG,
|
|
rgbaq[2].nB, rgbaq[2].nA);
|
|
|
|
if(m_primitiveMode.nShading == 0)
|
|
{
|
|
//Flat shaded triangles use the last color set
|
|
color1 = color2 = color3;
|
|
}
|
|
|
|
// clang-format off
|
|
CDraw::PRIM_VERTEX vertices[] =
|
|
{
|
|
{ x1, y1, z1, color1, s[0], t[0], q[0], f[0]},
|
|
{ x2, y2, z2, color2, s[1], t[1], q[1], f[1]},
|
|
{ x3, y3, z3, color3, s[2], t[2], q[2], f[2]},
|
|
};
|
|
// clang-format on
|
|
|
|
m_draw->AddVertices(std::begin(vertices), std::end(vertices));
|
|
}
|
|
|
|
void CGSH_Vulkan::Prim_Sprite()
|
|
{
|
|
XYZ pos[2];
|
|
pos[0] <<= m_vtxBuffer[1].position;
|
|
pos[1] <<= m_vtxBuffer[0].position;
|
|
|
|
float x1 = pos[0].GetX(), y1 = pos[0].GetY();
|
|
float x2 = pos[1].GetX(), y2 = pos[1].GetY();
|
|
uint32 z = pos[1].nZ;
|
|
|
|
RGBAQ rgbaq[2];
|
|
rgbaq[0] <<= m_vtxBuffer[1].rgbaq;
|
|
rgbaq[1] <<= m_vtxBuffer[0].rgbaq;
|
|
|
|
x1 -= m_primOfsX;
|
|
x2 -= m_primOfsX;
|
|
|
|
y1 -= m_primOfsY;
|
|
y2 -= m_primOfsY;
|
|
|
|
float s[2] = {0, 0};
|
|
float t[2] = {0, 0};
|
|
|
|
float f = 0;
|
|
|
|
if(m_primitiveMode.nFog)
|
|
{
|
|
f = static_cast<float>(0xFF - m_vtxBuffer[0].fog) / 255.0f;
|
|
}
|
|
|
|
if(m_primitiveMode.nTexture)
|
|
{
|
|
if(m_primitiveMode.nUseUV)
|
|
{
|
|
UV uv[2];
|
|
uv[0] <<= m_vtxBuffer[1].uv;
|
|
uv[1] <<= m_vtxBuffer[0].uv;
|
|
|
|
s[0] = uv[0].GetU() / static_cast<float>(m_texWidth);
|
|
s[1] = uv[1].GetU() / static_cast<float>(m_texWidth);
|
|
|
|
t[0] = uv[0].GetV() / static_cast<float>(m_texHeight);
|
|
t[1] = uv[1].GetV() / static_cast<float>(m_texHeight);
|
|
}
|
|
else
|
|
{
|
|
ST st[2];
|
|
|
|
st[0] <<= m_vtxBuffer[1].st;
|
|
st[1] <<= m_vtxBuffer[0].st;
|
|
|
|
float q1 = rgbaq[1].nQ;
|
|
float q2 = rgbaq[0].nQ;
|
|
if(q1 == 0) q1 = 1;
|
|
if(q2 == 0) q2 = 1;
|
|
|
|
s[0] = st[0].nS / q1;
|
|
s[1] = st[1].nS / q2;
|
|
|
|
t[0] = st[0].nT / q1;
|
|
t[1] = st[1].nT / q2;
|
|
}
|
|
}
|
|
|
|
auto color = MakeColor(
|
|
rgbaq[1].nR, rgbaq[1].nG,
|
|
rgbaq[1].nB, rgbaq[1].nA);
|
|
|
|
// clang-format off
|
|
CDraw::PRIM_VERTEX vertices[] =
|
|
{
|
|
{x1, y1, z, color, s[0], t[0], 1, f},
|
|
{x2, y1, z, color, s[1], t[0], 1, f},
|
|
{x1, y2, z, color, s[0], t[1], 1, f},
|
|
|
|
{x1, y2, z, color, s[0], t[1], 1, f},
|
|
{x2, y1, z, color, s[1], t[0], 1, f},
|
|
{x2, y2, z, color, s[1], t[1], 1, f},
|
|
};
|
|
// clang-format on
|
|
|
|
{
|
|
const auto topLeftCorner = vertices;
|
|
const auto bottomRightCorner = vertices + 5;
|
|
CGsSpriteRect rect(topLeftCorner->x, topLeftCorner->y, bottomRightCorner->x, bottomRightCorner->y);
|
|
CheckSpriteCachedClutInvalidation(rect);
|
|
}
|
|
|
|
m_draw->AddVertices(std::begin(vertices), std::end(vertices));
|
|
}
|
|
|
|
int32 CGSH_Vulkan::FindCachedClut(const CLUTKEY& key) const
|
|
{
|
|
for(uint32 i = 0; i < CLUT_CACHE_SIZE; i++)
|
|
{
|
|
if(m_clutStates[i] == key)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CGSH_Vulkan::CheckSpriteCachedClutInvalidation(const CGsSpriteRect& rect)
|
|
{
|
|
//This is specific to Gran Turismo 4, but could be expanded to work with other games
|
|
//GT4 writes to an area in GS RAM then uses this as a CLUT in one of its post-processing effects.
|
|
//We flush the whole CLUT cache if we find a sprite draw that writes to any previous location of cached cluts
|
|
if((rect.left == 0) && (rect.top == 0) && (rect.GetWidth() == 8) && (rect.GetHeight() == 16))
|
|
{
|
|
bool foundClut = false;
|
|
for(uint32 i = 0; i < CLUT_CACHE_SIZE; i++)
|
|
{
|
|
uint32 cbpBasePtr = m_clutStates[i].cbp * 0x100;
|
|
if(cbpBasePtr == m_fbBasePtr)
|
|
{
|
|
foundClut = true;
|
|
break;
|
|
}
|
|
}
|
|
if(foundClut)
|
|
{
|
|
memset(&m_clutStates, 0, sizeof(m_clutStates));
|
|
m_draw->FlushVertices();
|
|
}
|
|
}
|
|
}
|
|
|
|
CGSH_Vulkan::CLUTKEY CGSH_Vulkan::MakeCachedClutKey(const TEX0& tex0, const TEXCLUT& texClut)
|
|
{
|
|
auto clutKey = make_convertible<CLUTKEY>(0);
|
|
clutKey.idx4 = CGsPixelFormats::IsPsmIDTEX4(tex0.nPsm);
|
|
clutKey.cbp = tex0.nCBP;
|
|
clutKey.cpsm = tex0.nCPSM;
|
|
clutKey.csm = tex0.nCSM;
|
|
clutKey.csa = tex0.nCSA;
|
|
clutKey.cbw = texClut.nCBW;
|
|
clutKey.cou = texClut.nCOU;
|
|
clutKey.cov = texClut.nCOV;
|
|
return clutKey;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// Other Functions
|
|
/////////////////////////////////////////////////////////////
|
|
|
|
void CGSH_Vulkan::WriteRegisterImpl(uint8 registerId, uint64 data)
|
|
{
|
|
//Some games such as Silent Hill 2 don't finish their transfers
|
|
//completely: make sure we push the data to the GS's RAM nevertheless.
|
|
if(!m_xferBuffer.empty() && (registerId != GS_REG_HWREG))
|
|
{
|
|
ProcessHostToLocalTransfer();
|
|
}
|
|
|
|
CGSHandler::WriteRegisterImpl(registerId, data);
|
|
|
|
switch(registerId)
|
|
{
|
|
case GS_REG_PRIM:
|
|
//Check if previous primitive set was a line
|
|
if(m_primitiveType == PRIM_LINE)
|
|
{
|
|
//Virtua Fighter 2, Sega Rally 95 (and others):
|
|
//Draws lines and uses result as CLUT for next draw calls.
|
|
|
|
//Optimization: We check if the last drawn line UV matches what might currently be in the CLUT
|
|
//If it's different, we force reset all saved CLUTs, otherwise, we assume we can keep
|
|
//what we had previously. This helps reducing the number of compute dispatch needed to update the CLUT
|
|
|
|
//Optimization lead: The game draws lines to update the CLUT area very often, but a lot of times
|
|
//the result doesn't seem to be used. We could probably save a lot by not drawing these lines.
|
|
|
|
if(
|
|
(m_clutLineU != m_lastLineU) ||
|
|
(m_clutLineV != m_lastLineV))
|
|
{
|
|
memset(&m_clutStates, 0, sizeof(m_clutStates));
|
|
}
|
|
m_clutLineU = m_lastLineU;
|
|
m_clutLineV = m_lastLineV;
|
|
}
|
|
m_pendingPrim = true;
|
|
m_pendingPrimValue = data;
|
|
break;
|
|
|
|
case GS_REG_XYZ2:
|
|
case GS_REG_XYZ3:
|
|
case GS_REG_XYZF2:
|
|
case GS_REG_XYZF3:
|
|
VertexKick(registerId, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGSH_Vulkan::ProcessHostToLocalTransfer()
|
|
{
|
|
//Flush previous cached info
|
|
memset(&m_clutStates, 0, sizeof(m_clutStates));
|
|
m_draw->FlushRenderPass();
|
|
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
|
|
m_transferHost->Params.bufAddress = bltBuf.GetDstPtr();
|
|
m_transferHost->Params.bufWidth = bltBuf.GetDstWidth();
|
|
m_transferHost->Params.rrw = trxReg.nRRW;
|
|
m_transferHost->Params.dsax = trxPos.nDSAX;
|
|
m_transferHost->Params.dsay = trxPos.nDSAY;
|
|
|
|
auto pipelineCaps = make_convertible<CTransferHost::PIPELINE_CAPS>(0);
|
|
pipelineCaps.dstFormat = bltBuf.nDstPsm;
|
|
|
|
m_transferHost->SetPipelineCaps(pipelineCaps);
|
|
m_transferHost->DoTransfer(m_xferBuffer);
|
|
|
|
m_xferBuffer.clear();
|
|
}
|
|
|
|
void CGSH_Vulkan::ProcessLocalToHostTransfer()
|
|
{
|
|
bool readsEnabled = CAppConfig::GetInstance().GetPreferenceBoolean(PREF_CGSHANDLER_GS_RAM_READS_ENABLED);
|
|
if(readsEnabled)
|
|
{
|
|
m_draw->FlushRenderPass();
|
|
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
|
|
m_xferHistory.insert(std::make_pair(bltBuf, LOCAL_TO_HOST_XFER_HISTORY{}));
|
|
auto transfer = m_xferHistory.find(bltBuf);
|
|
transfer->second.MarkUsed();
|
|
|
|
auto [copyBase, copySize] = GsTransfer::GetSrcRange(bltBuf, trxReg, trxPos);
|
|
|
|
//Some games do that, example: Star Ocean 3
|
|
assert((copyBase + copySize) <= RAMSIZE);
|
|
if(copyBase >= RAMSIZE)
|
|
{
|
|
//Huh, something really wierd happened
|
|
return;
|
|
}
|
|
if((copyBase + copySize) > RAMSIZE)
|
|
{
|
|
copySize = RAMSIZE - copyBase;
|
|
}
|
|
|
|
auto& srcBuffer = m_context->memoryBuffer;
|
|
auto& dstBuffer = m_context->memoryBufferTransfer;
|
|
|
|
auto commandBuffer = m_frameCommandBuffer->GetCommandBuffer();
|
|
|
|
{
|
|
auto memoryBarrier = Framework::Vulkan::MemoryBarrier();
|
|
memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
memoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
m_context->device.vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
|
|
0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
|
|
}
|
|
|
|
{
|
|
VkBufferCopy bufferCopy = {};
|
|
bufferCopy.size = copySize;
|
|
bufferCopy.dstOffset = copyBase;
|
|
bufferCopy.srcOffset = copyBase;
|
|
m_context->device.vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &bufferCopy);
|
|
}
|
|
|
|
if(!transfer->second.IsRecurring())
|
|
{
|
|
m_frameCommandBuffer->Flush();
|
|
m_context->device.vkQueueWaitIdle(m_context->queue);
|
|
}
|
|
|
|
void* bufferPtr = nullptr;
|
|
auto result = m_context->device.vkMapMemory(m_context->device, dstBuffer.GetMemory(), copyBase, copySize, 0, &bufferPtr);
|
|
CHECKVULKANERROR(result);
|
|
|
|
memcpy(m_memoryCache + copyBase, bufferPtr, copySize);
|
|
|
|
m_context->device.vkUnmapMemory(m_context->device, dstBuffer.GetMemory());
|
|
}
|
|
}
|
|
|
|
void CGSH_Vulkan::ProcessLocalToLocalTransfer()
|
|
{
|
|
//Flush previous cached info
|
|
memset(&m_clutStates, 0, sizeof(m_clutStates));
|
|
m_draw->FlushRenderPass();
|
|
|
|
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
|
|
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
|
|
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
|
|
|
|
assert(trxPos.nDIR == 0);
|
|
|
|
m_transferLocal->Params.srcBufAddress = bltBuf.GetSrcPtr();
|
|
m_transferLocal->Params.srcBufWidth = bltBuf.GetSrcWidth();
|
|
m_transferLocal->Params.dstBufAddress = bltBuf.GetDstPtr();
|
|
m_transferLocal->Params.dstBufWidth = bltBuf.GetDstWidth();
|
|
m_transferLocal->Params.ssax = trxPos.nSSAX;
|
|
m_transferLocal->Params.ssay = trxPos.nSSAY;
|
|
m_transferLocal->Params.dsax = trxPos.nDSAX;
|
|
m_transferLocal->Params.dsay = trxPos.nDSAY;
|
|
m_transferLocal->Params.rrw = trxReg.nRRW;
|
|
m_transferLocal->Params.rrh = trxReg.nRRH;
|
|
|
|
auto pipelineCaps = make_convertible<CTransferLocal::PIPELINE_CAPS>(0);
|
|
pipelineCaps.srcFormat = bltBuf.nSrcPsm;
|
|
pipelineCaps.dstFormat = bltBuf.nDstPsm;
|
|
|
|
if(bltBuf.GetSrcPtr() == bltBuf.GetDstPtr())
|
|
{
|
|
//If src and dst pointers are the same, we need to copy the memory area in our memoryBufferCopy.
|
|
//This is needed for Tairyou Jigoku for a scrolling effect in the sewer level.
|
|
//TODO: Handle overlapping regions (even if memory addresses don't match)
|
|
|
|
auto [transferAddress, transferSize] = GsTransfer::GetSrcRange(bltBuf, trxReg, trxPos);
|
|
|
|
auto commandBuffer = m_frameCommandBuffer->GetCommandBuffer();
|
|
|
|
VkBufferCopy bufferCopy = {};
|
|
bufferCopy.srcOffset = transferAddress;
|
|
bufferCopy.dstOffset = transferAddress;
|
|
bufferCopy.size = transferSize;
|
|
|
|
m_context->device.vkCmdCopyBuffer(commandBuffer, m_context->memoryBuffer, m_context->memoryBufferCopy, 1, &bufferCopy);
|
|
|
|
pipelineCaps.srcUseMemoryCopy = true;
|
|
}
|
|
|
|
m_transferLocal->SetPipelineCaps(pipelineCaps);
|
|
m_transferLocal->DoTransfer();
|
|
}
|
|
|
|
void CGSH_Vulkan::ProcessClutTransfer(uint32 csa, uint32)
|
|
{
|
|
}
|
|
|
|
void CGSH_Vulkan::BeginTransferWrite()
|
|
{
|
|
assert(m_xferBuffer.empty());
|
|
m_xferBuffer.clear();
|
|
}
|
|
|
|
void CGSH_Vulkan::TransferWrite(const uint8* imageData, uint32 length)
|
|
{
|
|
m_xferBuffer.insert(m_xferBuffer.end(), imageData, imageData + length);
|
|
}
|
|
|
|
void CGSH_Vulkan::WriteBackMemoryCache()
|
|
{
|
|
m_frameCommandBuffer->Flush();
|
|
m_context->device.vkQueueWaitIdle(m_context->queue);
|
|
|
|
m_context->memoryBuffer.Write(m_context->queue, m_context->commandBufferPool,
|
|
m_context->physicalDeviceMemoryProperties, m_memoryCache);
|
|
}
|
|
|
|
void CGSH_Vulkan::SyncMemoryCache()
|
|
{
|
|
m_frameCommandBuffer->Flush();
|
|
m_context->device.vkQueueWaitIdle(m_context->queue);
|
|
|
|
m_context->memoryBuffer.Read(m_context->queue, m_context->commandBufferPool,
|
|
m_context->physicalDeviceMemoryProperties, m_memoryCache);
|
|
}
|
|
|
|
void CGSH_Vulkan::SyncCLUT(const TEX0& tex0)
|
|
{
|
|
if(!CGsPixelFormats::IsPsmIDTEX(tex0.nPsm)) return;
|
|
if(!ProcessCLD(tex0)) return;
|
|
|
|
auto texClut = make_convertible<TEXCLUT>(m_nReg[GS_REG_TEXCLUT]);
|
|
|
|
auto clutKey = MakeCachedClutKey(tex0, texClut);
|
|
int32 clutCacheIndex = FindCachedClut(clutKey);
|
|
if(clutCacheIndex == -1)
|
|
{
|
|
clutCacheIndex = m_nextClutCacheIndex++;
|
|
m_nextClutCacheIndex %= CLUT_CACHE_SIZE;
|
|
m_clutStates[clutCacheIndex] = clutKey;
|
|
|
|
m_draw->FlushRenderPass();
|
|
uint32 clutBufferOffset = sizeof(uint32) * CLUTENTRYCOUNT * clutCacheIndex;
|
|
m_clutLoad->DoClutLoad(clutBufferOffset, tex0, texClut);
|
|
}
|
|
|
|
uint32 clutBufferOffset = sizeof(uint32) * CLUTENTRYCOUNT * clutCacheIndex;
|
|
m_draw->SetClutBufferOffset(clutBufferOffset);
|
|
}
|
|
|
|
uint8* CGSH_Vulkan::GetRam() const
|
|
{
|
|
return m_memoryCache;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetScreenshot()
|
|
{
|
|
return Framework::CBitmap();
|
|
}
|
|
|
|
bool CGSH_Vulkan::GetDepthTestingEnabled() const
|
|
{
|
|
return m_depthTestingEnabled;
|
|
}
|
|
|
|
void CGSH_Vulkan::SetDepthTestingEnabled(bool depthTestingEnabled)
|
|
{
|
|
m_depthTestingEnabled = depthTestingEnabled;
|
|
}
|
|
|
|
bool CGSH_Vulkan::GetAlphaBlendingEnabled() const
|
|
{
|
|
return m_alphaBlendingEnabled;
|
|
}
|
|
|
|
void CGSH_Vulkan::SetAlphaBlendingEnabled(bool alphaBlendingEnabled)
|
|
{
|
|
m_alphaBlendingEnabled = alphaBlendingEnabled;
|
|
}
|
|
|
|
bool CGSH_Vulkan::GetAlphaTestingEnabled() const
|
|
{
|
|
return m_alphaTestingEnabled;
|
|
}
|
|
|
|
void CGSH_Vulkan::SetAlphaTestingEnabled(bool alphaTestingEnabled)
|
|
{
|
|
m_alphaTestingEnabled = alphaTestingEnabled;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetFramebuffer(uint64 frameReg)
|
|
{
|
|
Framework::CBitmap result;
|
|
SendGSCall([&]() { result = GetFramebufferImpl(frameReg); }, true);
|
|
return result;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetDepthbuffer(uint64 frameReg, uint64 zbufReg)
|
|
{
|
|
Framework::CBitmap result;
|
|
SendGSCall([&]() { result = GetDepthbufferImpl(frameReg, zbufReg); }, true);
|
|
return result;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetTexture(uint64 tex0Reg, uint32 maxMip, uint64 miptbp1Reg, uint64 miptbp2Reg, uint32 mipLevel)
|
|
{
|
|
Framework::CBitmap result;
|
|
SendGSCall([&]() { result = GetTextureImpl(tex0Reg, maxMip, miptbp1Reg, miptbp2Reg, mipLevel); }, true);
|
|
return result;
|
|
}
|
|
|
|
int CGSH_Vulkan::GetFramebufferScale()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
const CGSHandler::VERTEX* CGSH_Vulkan::GetInputVertices() const
|
|
{
|
|
return m_vtxBuffer;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetFramebufferImpl(uint64 frameReg)
|
|
{
|
|
auto frame = make_convertible<FRAME>(frameReg);
|
|
|
|
SyncMemoryCache();
|
|
|
|
uint32 frameWidth = frame.GetWidth();
|
|
uint32 frameHeight = 1024;
|
|
Framework::CBitmap bitmap;
|
|
|
|
switch(frame.nPsm)
|
|
{
|
|
case PSMCT32:
|
|
{
|
|
bitmap = ReadImage32<CGsPixelFormats::CPixelIndexorPSMCT32>(GetRam(), frame.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
case PSMCT24:
|
|
{
|
|
bitmap = ReadImage32<CGsPixelFormats::CPixelIndexorPSMCT32, 0x00FFFFFF>(GetRam(), frame.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
case PSMCT16:
|
|
{
|
|
bitmap = ReadImage16<CGsPixelFormats::CPixelIndexorPSMCT16>(GetRam(), frame.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
case PSMCT16S:
|
|
{
|
|
bitmap = ReadImage16<CGsPixelFormats::CPixelIndexorPSMCT16S>(GetRam(), frame.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetDepthbufferImpl(uint64 frameReg, uint64 zbufReg)
|
|
{
|
|
auto frame = make_convertible<FRAME>(frameReg);
|
|
auto zbuf = make_convertible<ZBUF>(zbufReg);
|
|
|
|
SyncMemoryCache();
|
|
|
|
uint32 frameWidth = frame.GetWidth();
|
|
uint32 frameHeight = 1024;
|
|
Framework::CBitmap bitmap;
|
|
|
|
switch(zbuf.nPsm | 0x30)
|
|
{
|
|
case PSMZ32:
|
|
{
|
|
bitmap = ReadImage32<CGsPixelFormats::CPixelIndexorPSMZ32>(GetRam(), zbuf.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
case PSMZ24:
|
|
{
|
|
bitmap = ReadImage32<CGsPixelFormats::CPixelIndexorPSMZ32, 0x00FFFFFF>(GetRam(), zbuf.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
case PSMZ16S:
|
|
{
|
|
bitmap = ReadImage16<CGsPixelFormats::CPixelIndexorPSMZ16S>(GetRam(), zbuf.GetBasePtr(),
|
|
frame.nWidth, frameWidth, frameHeight);
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
Framework::CBitmap CGSH_Vulkan::GetTextureImpl(uint64 tex0Reg, uint32 maxMip, uint64 miptbp1Reg, uint64 miptbp2Reg, uint32 mipLevel)
|
|
{
|
|
auto tex0 = make_convertible<TEX0>(tex0Reg);
|
|
auto miptbp1 = make_convertible<MIPTBP1>(miptbp1Reg);
|
|
auto miptbp2 = make_convertible<MIPTBP2>(miptbp2Reg);
|
|
|
|
auto width = std::max<uint32>(tex0.GetWidth() >> mipLevel, 1);
|
|
auto height = std::max<uint32>(tex0.GetHeight() >> mipLevel, 1);
|
|
auto [tbp, tbw] =
|
|
[&]() {
|
|
switch(mipLevel)
|
|
{
|
|
default:
|
|
assert(false);
|
|
[[fallthrough]];
|
|
case 0:
|
|
return std::make_pair(tex0.GetBufPtr(), tex0.GetBufWidth());
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
return GetMipLevelInfo(mipLevel, miptbp1, miptbp2);
|
|
}
|
|
}();
|
|
assert((tbw & 0x3F) == 0);
|
|
tbw /= 64;
|
|
|
|
SyncMemoryCache();
|
|
|
|
Framework::CBitmap bitmap;
|
|
|
|
switch(tex0.nPsm)
|
|
{
|
|
case PSMCT32:
|
|
bitmap = ReadImage32<CGsPixelFormats::CPixelIndexorPSMCT32>(GetRam(), tbp, tbw, width, height);
|
|
break;
|
|
case PSMCT24:
|
|
bitmap = ReadImage32<CGsPixelFormats::CPixelIndexorPSMCT32, 0x00FFFFFF>(GetRam(), tbp, tbw, width, height);
|
|
break;
|
|
case PSMCT16:
|
|
bitmap = ReadImage16<CGsPixelFormats::CPixelIndexorPSMCT16>(GetRam(), tbp, tbw, width, height);
|
|
break;
|
|
case PSMCT16S:
|
|
bitmap = ReadImage16<CGsPixelFormats::CPixelIndexorPSMCT16S>(GetRam(), tbp, tbw, width, height);
|
|
break;
|
|
case PSMT8:
|
|
bitmap = ReadImage8<CGsPixelFormats::CPixelIndexorPSMT8>(GetRam(), tbp, tbw, width, height);
|
|
break;
|
|
case PSMT4:
|
|
bitmap = ReadImage8<CGsPixelFormats::CPixelIndexorPSMT4>(GetRam(), tbp, tbw, width, height);
|
|
break;
|
|
}
|
|
|
|
return bitmap;
|
|
}
|