Compare commits

...
19 Commits
Author SHA1 Message Date
refractionpcsx2 4cb6c94693 CI/Docs: Update validation and document 2022-04-03 08:50:09 +01:00
refractionpcsx2 93c78a27b3 GS: Add interlace override to gamedb, revert earlier change 2022-04-03 08:50:09 +01:00
Connor McLaughlin 50452848e7 BiosTools: Bounds check strings in LoadBiosVersion() 2022-04-03 07:57:42 +01:00
Connor McLaughlin 4f70fd9583 VMManager: Make EE rec toggles reliable
Before, it'd swap out the Cpu while it was executing...
2022-04-03 07:57:02 +01:00
Connor McLaughlin e6c8354ec8 EERec: Purge PCSX2_SEH define
No longer needed since we don't have 32-bit.
2022-04-03 07:57:02 +01:00
Connor McLaughlin a1e77002c3 iR5900: Exit on NeedsReset not IsReset
This is an edge case which can be hit in Goemon, but also when changing
settings in Qt, as they will be applied at guest vsync time, which is
called within the JIT.

Also, do the reset before entering JIT code, rather than when the first
block is compiled.
2022-04-03 07:57:02 +01:00
Connor McLaughlin 099cd52381 gitignore: Remove unused portaudio files 2022-04-03 06:09:09 +02:00
lightningterror 0d1675fbaa GS-hw: Purge custom resolution entirely.
Code debt, not used anymore, get rid of it.
2022-04-02 16:54:26 +02:00
lightningterror f021da5627 GS-tc-hw: Fix Normal Half pixel offset on 7x upscale. 2022-04-01 13:58:54 +02:00
refractionpcsx2 906480b8e2 GS: Only flush on PRIM if a new draw is requested 2022-04-01 10:48:22 +01:00
refractionpcsx2 38b83af714 GS: Improve interlace handling and odd frames 2022-04-01 10:48:01 +01:00
Tyler Wilding 05dab12b7c releases: simplify discord announcements, and link to the right page 2022-04-01 10:45:59 +01:00
TellowKrinkle c0628a64a6 GS: Add Metal renderer to settings 2022-03-31 23:51:27 -05:00
TellowKrinkle 5ecaa9459d GS: Add Metal renderer 2022-03-31 23:51:27 -05:00
TellowKrinkle 24b2277206 GS: Give names to blend enums 2022-03-31 23:51:27 -05:00
TellowKrinkle 8e60d2c72e GS: Rename Int32 texture to PrimID
Not always Int32
2022-03-31 23:51:27 -05:00
TellowKrinkle cfe2f9e6b4 Common: Add helpers for enum classes 2022-03-31 23:51:27 -05:00
TellowKrinkle 0a11b5898a CMake: Disable PCH for mm files 2022-03-31 23:51:27 -05:00
TellowKrinkle be49de6fa0 Config: Format Pcsx2Config 2022-03-31 23:51:27 -05:00
59 changed files with 5577 additions and 430 deletions
@@ -8,53 +8,17 @@ if (!releaseInfo.prerelease) {
process.exit(0);
}
const assets = releaseInfo.assets;
let windowsAssetLinks = "";
let linuxAssetLinks = "";
for (var i = 0; i < assets.length; i++) {
let asset = assets[i];
if (asset.name.includes("symbols")) {
continue;
}
if (asset.name.includes("windows")) {
let friendlyName = asset.name;
try {
friendlyName = asset.name.split("windows-")[1].split(".7z")[0].replace("-", " ");
} catch (e) {
console.log(e);
}
windowsAssetLinks += `- [${friendlyName}](${asset.browser_download_url})\n`
} else if (asset.name.includes("linux")) {
let friendlyName = asset.name;
try {
friendlyName = asset.name.split("linux-")[1].split(".AppImage")[0].replace("-", " ");
} catch (e) {
console.log(e);
}
linuxAssetLinks += `- [${friendlyName}](${asset.browser_download_url})\n`
}
}
// Publish Webhook
const embed = new MessageEmbed()
.setColor('#FF8000')
.setTitle('New PCSX2 Nightly Build Available!')
.setDescription("To download the latest or previous builds, [visit the official downloads page](https://pcsx2.net/downloads/).")
.addFields(
{ name: 'Version', value: releaseInfo.tag_name, inline: true },
{ name: 'Release Link', value: `[Github Release](${releaseInfo.html_url})`, inline: true },
{ name: 'Installation Steps', value: '[See Here](https://github.com/PCSX2/pcsx2/wiki/Nightly-Build-Usage-Guide)', inline: true },
{ name: 'Included Changes', value: releaseInfo.body, inline: false },
{ name: 'Previous Builds', value: "[See Here](https://pcsx2.github.io/downloads.html#nightly-anchor)", inline: false }
{ name: 'Included Changes', value: releaseInfo.body, inline: false }
);
if (windowsAssetLinks != "") {
embed.addField('Windows Downloads', windowsAssetLinks, false);
}
if (linuxAssetLinks != "") {
embed.addField('Linux Downloads', linuxAssetLinks, false);
}
const webhookClient = new WebhookClient({ url: process.env.DISCORD_BUILD_WEBHOOK });
await webhookClient.send({
embeds: [embed],
@@ -58,6 +58,7 @@ allowed_gs_hw_fixes = [
"halfPixelOffset",
"roundSprite",
"texturePreloading",
"interlace",
]
gs_hw_fix_ranges = {
"mipmap": (0, 2),
@@ -66,6 +67,7 @@ gs_hw_fix_ranges = {
"skipDrawEnd": (0, 100000),
"halfPixelOffset": (0, 3),
"roundSprite": (0, 2),
"interlace": (0, 7),
}
allowed_speed_hacks = ["mvuFlagSpeedHack", "InstantVU1SpeedHack", "MTVUSpeedHack"]
# Patches are allowed to have a 'default' key or a crc-32 key, followed by
-14
View File
@@ -95,20 +95,6 @@ oprofile_data/
/ipch
!/3rdparty/libjpeg/change.log
/3rdparty/portaudio/portaudio-2.0.pc
/3rdparty/portaudio/bin
/3rdparty/portaudio/bin-*
/3rdparty/portaudio/autom4te.cache
/3rdparty/portaudio/libtool
/3rdparty/portaudio/config.*
/3rdparty/portaudio/lib-stamp
/3rdparty/portaudio/Makefile
/3rdparty/portaudio/bindings
/3rdparty/portaudio/test
/3rdparty/portaudio/testcvs
/3rdparty/portaudio/src/hostapi/asio/ASIOSDK/common
/3rdparty/portaudio/src/hostapi/asio/ASIOSDK/host
/3rdparty/portaudio/src/hostapi/wasapi/mingw-include
/3rdparty/**/include/wx/setup.h
/3rdparty/**/wx/msw/rcdefs.h
/pcsx2/gui/Resources/*.h
+16
View File
@@ -2744,6 +2744,8 @@ SCES-51159:
SCES-51164:
name: "Mark of Kri, The"
region: "PAL-M5"
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SCES-51176:
name: "Disney's Treasure Planet"
region: "PAL-M4"
@@ -6179,6 +6181,8 @@ SCUS-97140:
name: "Mark of Kri, The"
region: "NTSC-U"
compat: 5
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
patches:
DBD09DD4:
content: |-
@@ -6399,6 +6403,8 @@ SCUS-97200:
SCUS-97201:
name: "Mark of Kri, The"
region: "NTSC-U"
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SCUS-97203:
name: "Wild ARMs 3"
region: "NTSC-U"
@@ -6477,6 +6483,8 @@ SCUS-97220:
SCUS-97222:
name: "Mark of Kri [Demo]"
region: "NTSC-U"
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SCUS-97223:
name: "NFL GameDay 2003 [Demo]"
region: "NTSC-U"
@@ -15808,6 +15816,8 @@ SLES-53621:
name: "Wallace & Grommit - The Curse of the Were Rabbit"
region: "PAL-M5"
compat: 5
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SLES-53623:
name: "Spongebob SquarePants - Battle for Bikini Bottom"
region: "PAL-F"
@@ -24763,6 +24773,8 @@ SLPM-65309:
SLPM-65310:
name: "Mark of Kri, The"
region: "NTSC-J"
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SLPM-65311:
name: "Violet no Atelier - Gramnad no Renkinjutsushi"
region: "NTSC-J"
@@ -28059,6 +28071,8 @@ SLPM-66325:
SLPM-66327:
name: "Wallace and Gromit - The Curse of the Were-Rabbit"
region: "NTSC-J"
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SLPM-66328:
name: "Call of Duty 2 - Big Red One"
region: "NTSC-J"
@@ -41417,6 +41431,8 @@ SLUS-21312:
name: "Wallace & Gromit - The Curse of the Were-Rabbit"
region: "NTSC-U"
compat: 5
gsHWFixes:
interlace: 3 # Game requires bob delinterlacing when auto
SLUS-21313:
name: "Friends - The One with all the Trivia"
region: "NTSC-U"
+23 -10
View File
@@ -1,4 +1,4 @@
#if defined(SHADER_MODEL) || defined(FXAA_GLSL_130) || defined(FXAA_GLSL_VK)
#if defined(SHADER_MODEL) || defined(FXAA_GLSL_130) || defined(FXAA_GLSL_VK) || defined(__METAL_VERSION__)
#ifndef FXAA_GLSL_130
#define FXAA_GLSL_130 0
@@ -47,6 +47,8 @@ struct PS_OUTPUT
float4 c : SV_Target0;
};
#elif defined(__METAL_VERSION__)
static constexpr sampler MAIN_SAMPLER(coord::normalized, address::clamp_to_edge, filter::linear);
#endif
/*------------------------------------------------------------------------------
@@ -63,6 +65,9 @@ struct PS_OUTPUT
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
#define FXAA_GATHER4_ALPHA 1
#elif defined(__METAL_VERSION__)
#define FXAA_GATHER4_ALPHA 1
#endif
#if (FXAA_HLSL_5 == 1)
@@ -98,6 +103,14 @@ struct FxaaTex { SamplerState smpl; Texture2D tex; };
#define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3)
#endif
#elif defined(__METAL_VERSION__)
#define FxaaTex texture2d<float>
#define FxaaTexTop(t, p) t.sample(MAIN_SAMPLER, p)
#define FxaaTexOff(t, p, o, r) t.sample(MAIN_SAMPLER, p, o)
#define FxaaTexAlpha4(t, p) t.gather(MAIN_SAMPLER, p, 0, component::w)
#define FxaaTexOffAlpha4(t, p, o) t.gather(MAIN_SAMPLER, p, o, component::w)
#define FxaaDiscard discard_fragment()
#define FxaaSat(x) saturate(x)
#endif
#define FxaaEdgeThreshold 0.063
@@ -151,14 +164,8 @@ float3 LinearToRGBGamma(float3 color, float gamma)
return color;
}
float4 PreGammaPass(float4 color, float2 uv0)
float4 PreGammaPass(float4 color)
{
#if (SHADER_MODEL >= 0x400)
color = Texture.Sample(TextureSampler, uv0);
#elif (FXAA_GLSL_130 == 1)
color = texture(TextureSampler, uv0);
#endif
const float GammaConst = 2.233;
color.rgb = RGBGammaToLinear(color.rgb, GammaConst);
color.rgb = LinearToRGBGamma(color.rgb, GammaConst);
@@ -483,6 +490,8 @@ float4 FxaaPixelShader(float2 pos, FxaaTex tex, float2 fxaaRcpFrame, float fxaaS
float4 FxaaPass(float4 FxaaColor, float2 uv0)
#elif (SHADER_MODEL >= 0x400)
float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
#elif defined(__METAL_VERSION__)
float4 FxaaPass(float4 FxaaColor, float2 uv0, texture2d<float> tex)
#endif
{
@@ -498,6 +507,9 @@ float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
vec2 PixelSize = textureSize(TextureSampler, 0);
FxaaColor = FxaaPixelShader(uv0, TextureSampler, 1.0/PixelSize.xy, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#elif defined(__METAL_VERSION__)
float2 PixelSize = float2(tex.get_width(), tex.get_height());
FxaaColor = FxaaPixelShader(uv0, tex, 1.f/PixelSize, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
#endif
return FxaaColor;
@@ -511,7 +523,7 @@ float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
void main()
{
vec4 color = texture(TextureSampler, PSin_t);
color = PreGammaPass(color, PSin_t);
color = PreGammaPass(color);
color = FxaaPass(color, PSin_t);
SV_Target0 = color;
@@ -524,7 +536,7 @@ PS_OUTPUT ps_main(VS_OUTPUT input)
float4 color = Texture.Sample(TextureSampler, input.t);
color = PreGammaPass(color, input.t);
color = PreGammaPass(color);
color = FxaaPass(color, input.t);
output.c = color;
@@ -532,6 +544,7 @@ PS_OUTPUT ps_main(VS_OUTPUT input)
return output;
}
// Metal main function in in fxaa.metal
#endif
#endif
+7 -2
View File
@@ -305,9 +305,14 @@ endif()
# MacOS-specific things
#-------------------------------------------------------------------------------
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13)
if(NOT CMAKE_GENERATOR MATCHES "Xcode")
# Assume Xcode builds aren't being used for distribution
# Helpful because Xcode builds don't build multiple metallibs for different macOS versions
# Also helpful because Xcode's interactive shader debugger requires apps be built for the latest macOS
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13)
endif()
if (APPLE AND ${CMAKE_OSX_DEPLOYMENT_TARGET} VERSION_LESS 10.14 AND NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 9)
if (APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET AND "${CMAKE_OSX_DEPLOYMENT_TARGET}" VERSION_LESS 10.14 AND NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 9)
# Older versions of the macOS stdlib don't have operator new(size_t, align_val_t)
# Disable use of them with this flag
# Not great, but also no worse that what we were getting before we turned on C++17
+4 -2
View File
@@ -72,6 +72,7 @@ target_sources(common PRIVATE
boost_spsc_queue.hpp
Console.h
Dependencies.h
EnumOps.h
EventSource.h
Exceptions.h
FastJmp.h
@@ -85,6 +86,7 @@ target_sources(common PRIVATE
MemcpyFast.h
MemsetFast.inl
MD5Digest.h
MRCHelpers.h
Path.h
PageFaultSource.h
PrecompiledHeader.h
@@ -184,8 +186,8 @@ elseif(APPLE)
GL/ContextAGL.h
)
set_source_files_properties(GL/ContextAGL.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
target_compile_options(common PUBLIC -fobjc-arc)
target_link_options(common PUBLIC -fobjc-link-runtime)
target_compile_options(common PRIVATE -fobjc-arc)
target_link_options(common PRIVATE -fobjc-link-runtime)
else()
if(X11_API OR WAYLAND_API)
target_sources(common PRIVATE
+96
View File
@@ -0,0 +1,96 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <type_traits>
namespace detail
{
/// Marks an enum as supporting boolean operators
template <typename T>
struct enum_is_flags : public std::false_type {};
/// For return types that should be convertible to bool
template <typename Enum>
struct enum_bool_helper
{
Enum value;
constexpr enum_bool_helper(Enum value): value(value) {}
constexpr operator Enum() const { return value; }
constexpr operator bool() const { return static_cast<bool>(static_cast<typename std::underlying_type<Enum>::type>(value)); }
};
};
#define MARK_ENUM_AS_FLAGS(T) template<> struct detail::enum_is_flags<T> : public std::true_type {}
template <typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum>::type
operator|(Enum lhs, Enum rhs) noexcept
{
using underlying = typename std::underlying_type<Enum>::type;
return static_cast<Enum>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
}
template <typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, detail::enum_bool_helper<Enum>>::type
operator&(Enum lhs, Enum rhs) noexcept
{
using underlying = typename std::underlying_type<Enum>::type;
return static_cast<Enum>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
}
template <typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum>::type
operator^(Enum lhs, Enum rhs) noexcept
{
using underlying = typename std::underlying_type<Enum>::type;
return static_cast<Enum>(static_cast<underlying>(lhs) ^ static_cast<underlying>(rhs));
}
template <typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum&>::type
operator|=(Enum& lhs, Enum rhs) noexcept
{
return lhs = lhs | rhs;
}
template <typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum&>::type
operator&=(Enum& lhs, Enum rhs) noexcept
{
return lhs = lhs & rhs;
}
template <typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum&>::type
operator^=(Enum& lhs, Enum rhs) noexcept
{
return lhs = lhs ^ rhs;
}
template<typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, bool>::type
operator!(Enum e) noexcept
{
return !static_cast<typename std::underlying_type<Enum>::type>(e);
}
template<typename Enum>
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum>::type
operator~(Enum e) noexcept
{
return static_cast<Enum>(~static_cast<typename std::underlying_type<Enum>::type>(e));
}
+97
View File
@@ -0,0 +1,97 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __OBJC__
#error This header is for use with Objective-C++ only.
#endif
#if __has_feature(objc_arc)
#error This file is for manual reference counting! Compile without -fobjc-arc
#endif
#pragma once
#include <cstddef>
#include <utility>
/// Managed Obj-C pointer
template <typename T>
class MRCOwned
{
T ptr;
MRCOwned(T ptr): ptr(ptr) {}
public:
MRCOwned(): ptr(nullptr) {}
MRCOwned(std::nullptr_t): ptr(nullptr) {}
MRCOwned(MRCOwned&& other)
: ptr(other.ptr)
{
other.ptr = nullptr;
}
MRCOwned(const MRCOwned& other)
: ptr(other.ptr)
{
[ptr retain];
}
~MRCOwned()
{
if (ptr)
[ptr release];
}
operator T() const { return ptr; }
MRCOwned& operator=(const MRCOwned& other)
{
[other.ptr retain];
if (ptr)
[ptr release];
ptr = other.ptr;
return *this;
}
MRCOwned& operator=(MRCOwned&& other)
{
std::swap(ptr, other.ptr);
return *this;
}
void Reset()
{
[ptr release];
ptr = nullptr;
}
T Get() const { return ptr; }
static MRCOwned Transfer(T ptr)
{
return MRCOwned(ptr);
}
static MRCOwned Retain(T ptr)
{
[ptr retain];
return MRCOwned(ptr);
}
};
/// Take ownership of an Obj-C pointer (equivalent to __bridge_transfer)
template<typename T>
static inline MRCOwned<T> MRCTransfer(T ptr)
{
return MRCOwned<T>::Transfer(ptr);
}
/// Retain an Obj-C pointer (equivalent to __bridge)
template<typename T>
static inline MRCOwned<T> MRCRetain(T ptr)
{
return MRCOwned<T>::Retain(ptr);
}
+78 -1
View File
@@ -789,6 +789,14 @@ if(USE_VULKAN)
)
endif()
set(pcsx2GSMetalShaders
GS/Renderers/Metal/convert.metal
GS/Renderers/Metal/merge.metal
GS/Renderers/Metal/interlace.metal
GS/Renderers/Metal/tfx.metal
GS/Renderers/Metal/fxaa.metal
)
if(NOT PCSX2_CORE)
list(APPEND pcsx2GSSources
GS/Window/GSwxDialog.cpp
@@ -1004,6 +1012,26 @@ if(WIN32)
list(APPEND pcsx2FrontendHeaders
Frontend/D3D11HostDisplay.h
)
elseif(APPLE)
list(APPEND pcsx2GSSources
GS/Renderers/Metal/GSDeviceMTL.mm
GS/Renderers/Metal/GSMTLDeviceInfo.mm
GS/Renderers/Metal/GSTextureMTL.mm
)
list(APPEND pcsx2GSHeaders
GS/Renderers/Metal/GSDeviceMTL.h
GS/Renderers/Metal/GSMetalCPPAccessible.h
GS/Renderers/Metal/GSMTLDeviceInfo.h
GS/Renderers/Metal/GSMTLSharedHeader.h
GS/Renderers/Metal/GSMTLShaderCommon.h
GS/Renderers/Metal/GSTextureMTL.h
)
list(APPEND pcsx2FrontendSources
Frontend/MetalHostDisplay.mm
)
list(APPEND pcsx2FrontendHeaders
Frontend/MetalHostDisplay.h
)
endif()
if(PCSX2_CORE)
@@ -1710,6 +1738,49 @@ if(GETTEXT_FOUND AND NOT NO_TRANSLATION AND NOT PCSX2_CORE)
endif()
if (APPLE)
find_library(METAL_LIBRARY Metal)
target_link_libraries(PCSX2 PRIVATE ${METAL_LIBRARY})
if(CMAKE_GENERATOR MATCHES "Xcode")
# If we're generating an xcode project, you can just add the shaders to the main pcsx2 target and xcode will deal with them properly
# This will make sure xcode supplies code completion, etc (if you use a custom command, it won't)
set_target_properties(PCSX2 PROPERTIES
XCODE_ATTRIBUTE_MTL_ENABLE_DEBUG_INFO INCLUDE_SOURCE
)
foreach(shader IN LISTS pcsx2GSMetalShaders)
target_sources(PCSX2 PRIVATE ${shader})
set_source_files_properties(${shader} PROPERTIES LANGUAGE METAL)
endforeach()
else()
function(generateMetallib std target outputName)
set(pcsx2GSMetalShaderOut)
set(flags
-ffast-math
$<$<NOT:$<CONFIG:Release,MinSizeRel>>:-gline-tables-only>
$<$<NOT:$<CONFIG:Release,MinSizeRel>>:-MO>
)
foreach(shader IN LISTS pcsx2GSMetalShaders)
set(shaderOut ${CMAKE_CURRENT_BINARY_DIR}/${outputName}/${shader}.air)
list(APPEND pcsx2GSMetalShaderOut ${shaderOut})
get_filename_component(shaderDir ${shaderOut} DIRECTORY)
add_custom_command(OUTPUT ${shaderOut}
COMMAND ${CMAKE_COMMAND} -E make_directory ${shaderDir}
COMMAND xcrun metal ${flags} -std=${std} -target ${target} -o ${shaderOut} -c ${CMAKE_CURRENT_SOURCE_DIR}/${shader}
DEPENDS ${shader} GS/Renderers/Metal/GSMTLSharedHeader.h GS/Renderers/Metal/GSMTLShaderCommon.h
)
set(metallib ${CMAKE_CURRENT_BINARY_DIR}/${outputName}.metallib)
endforeach()
add_custom_command(OUTPUT ${metallib}
COMMAND xcrun metallib -o ${metallib} ${pcsx2GSMetalShaderOut}
DEPENDS ${pcsx2GSMetalShaderOut}
)
pcsx2_resource(${metallib} ${CMAKE_CURRENT_BINARY_DIR})
endfunction()
generateMetallib(macos-metal2.0 air64-apple-macos10.13 default)
generateMetallib(macos-metal2.2 air64-apple-macos10.15 Metal22)
generateMetallib(macos-metal2.3 air64-apple-macos11.0 Metal23)
endif()
# MacOS defaults to having a maximum protection of the __DATA segment of rw (non-executable)
# We have a bunch of page-sized arrays in bss that we use for jit
# Obviously not being able to make those arrays executable would be a problem
@@ -1737,12 +1808,17 @@ if (APPLE)
)
add_dependencies(pcsx2-postprocess-bundle PCSX2)
# Set the right file types for .inl files in Xcode
get_target_property(PCSX2_SOURCES PCSX2 SOURCES)
foreach(source IN LISTS PCSX2_SOURCES)
# Set the right file types for .inl files in Xcode
if("${source}" MATCHES "\\.(inl|h)$")
set_source_files_properties("${source}" PROPERTIES XCODE_EXPLICIT_FILE_TYPE sourcecode.cpp.h)
endif()
# CMake makefile and ninja generators will attempt to share one PCH for both cpp and mm files
# That's not actually OK
if("${source}" MATCHES "\\.mm$")
set_source_files_properties("${source}" PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif()
endforeach()
endif()
@@ -1761,6 +1837,7 @@ source_group(System/Ps2/DEV9 REGULAR_EXPRESSION DEV9/*)
source_group(System/Ps2/PAD FILES ${pcsx2PADSources} ${pcsx2PADHeaders})
source_group(System/Ps2/SPU2 REGULAR_EXPRESSION SPU2/*)
source_group(System/Ps2/USB REGULAR_EXPRESSION USB/*)
source_group(System/Ps2/GS/Renderers/Metal REGULAR_EXPRESSION GS/Renderers/Metal/*)
# Generated resource files
source_group(Resources/GUI FILES ${pcsx2GuiResources})
+3
View File
@@ -121,6 +121,7 @@ enum class GSRendererType : s8
OGL = 12,
SW = 13,
VK = 14,
Metal = 17,
};
enum class GSInterlaceMode : u8
@@ -392,6 +393,8 @@ struct Pcsx2Config
void LoadSave(SettingsWrapper& wrap);
void ApplySanityCheck();
bool CpusChanged(const CpuOptions& right) const;
bool operator==(const CpuOptions& right) const
{
return OpEqu(sseMXCSR) && OpEqu(sseVUMXCSR) && OpEqu(Recompiler);
+5
View File
@@ -40,6 +40,10 @@ SERIAL-12345: # !required! Serial number for the game, this is how games are loo
- IbitHack
- VUSyncHack
- VUOverflowHack
# The value of the GS Fixes is assumed to be an integer
gsHWFixes:
mipmap: 1
preloadFrameData: 1
# The value of the speedhacks is assumed to be an integer,
# but at the time of writing speedhacks are effectively booleans (0/1)
speedHacks:
@@ -139,6 +143,7 @@ The clamp modes are also numerically based.
* conservativeFramebuffer [`0` or `1`] {Off or On} Default: On (`1`)
* texturePreloading [`0` or `1` or `2`] {None, Partial or Full Hash Cache} Default: None (`0`)
* interlace [Value between `0` to `7`] {Off, WeaveTFF, WeaveBFF, BobTFF, BobBFF, BlendTFF, BlendBFF, Automatic} Default: Automatic (No value, looks up GameDB)
### GS Hardware Renderer Fixes
+84
View File
@@ -0,0 +1,84 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "HostDisplay.h"
#ifndef __OBJC__
#error "This header is for use with Objective-C++ only.
#endif
#ifdef __APPLE__
#include "GS/Renderers/Metal/GSMTLDeviceInfo.h"
#include <AppKit/AppKit.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
class MetalHostDisplay final : public HostDisplay
{
MRCOwned<NSView*> m_view;
MRCOwned<CAMetalLayer*> m_layer;
GSMTLDevice m_dev;
MRCOwned<id<MTLCommandQueue>> m_queue;
MRCOwned<id<MTLTexture>> m_font_tex;
MRCOwned<id<CAMetalDrawable>> m_current_drawable;
MRCOwned<MTLRenderPassDescriptor*> m_pass_desc;
u32 m_capture_start_frame;
void AttachSurfaceOnMainThread();
void DetachSurfaceOnMainThread();
public:
MetalHostDisplay();
~MetalHostDisplay();
RenderAPI GetRenderAPI() const override;
void* GetRenderDevice() const override;
void* GetRenderContext() const override;
void* GetRenderSurface() const override;
bool HasRenderDevice() const override;
bool HasRenderSurface() const override;
bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, VsyncMode vsync, bool threaded_presentation, bool debug_device) override;
bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) override;
bool MakeRenderContextCurrent() override;
bool DoneRenderContextCurrent() override;
void DestroyRenderDevice() override;
void DestroyRenderSurface() override;
bool ChangeRenderWindow(const WindowInfo& wi) override;
bool SupportsFullscreen() const override;
bool IsFullscreen() override;
bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
AdapterAndModeList GetAdapterAndModeList() override;
std::string GetDriverInfo() const override;
void ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override;
void UpdateTexture(id<MTLTexture> texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride);
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override;
bool BeginPresent(bool frame_skip) override;
void EndPresent() override;
void SetVSync(VsyncMode mode) override;
bool CreateImGuiContext() override;
void DestroyImGuiContext() override;
bool UpdateImGuiFontTexture() override;
bool GetHostRefreshRate(float* refresh_rate) override;
};
#endif
+410
View File
@@ -0,0 +1,410 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "MetalHostDisplay.h"
#include "GS/Renderers/Metal/GSMetalCPPAccessible.h"
#include "GS/Renderers/Metal/GSDeviceMTL.h"
#include <imgui.h>
#ifdef __APPLE__
class MetalHostDisplayTexture final : public HostDisplayTexture
{
MRCOwned<id<MTLTexture>> m_tex;
u32 m_width, m_height;
public:
MetalHostDisplayTexture(MRCOwned<id<MTLTexture>> tex, u32 width, u32 height)
: m_tex(std::move(tex))
, m_width(width)
, m_height(height)
{
}
void* GetHandle() const override { return (__bridge void*)m_tex; };
u32 GetWidth() const override { return m_width; }
u32 GetHeight() const override { return m_height; }
};
HostDisplay* MakeMetalHostDisplay()
{
return new MetalHostDisplay();
}
MetalHostDisplay::MetalHostDisplay()
{
}
MetalHostDisplay::~MetalHostDisplay()
{
}
HostDisplay::AdapterAndModeList GetMetalAdapterAndModeList()
{ @autoreleasepool {
HostDisplay::AdapterAndModeList list;
auto devs = MRCTransfer(MTLCopyAllDevices());
for (id<MTLDevice> dev in devs.Get())
list.adapter_names.push_back([[dev name] UTF8String]);
return list;
}}
template <typename Fn>
static void OnMainThread(Fn&& fn)
{
if ([NSThread isMainThread])
fn();
else
dispatch_sync(dispatch_get_main_queue(), fn);
}
HostDisplay::RenderAPI MetalHostDisplay::GetRenderAPI() const
{
return RenderAPI::Metal;
}
void* MetalHostDisplay::GetRenderDevice() const { return const_cast<void*>(static_cast<const void*>(&m_dev)); }
void* MetalHostDisplay::GetRenderContext() const { return (__bridge void*)m_queue; }
void* MetalHostDisplay::GetRenderSurface() const { return (__bridge void*)m_layer; }
bool MetalHostDisplay::HasRenderDevice() const { return m_dev.IsOk(); }
bool MetalHostDisplay::HasRenderSurface() const { return static_cast<bool>(m_layer);}
void MetalHostDisplay::AttachSurfaceOnMainThread()
{
ASSERT([NSThread isMainThread]);
m_view = MRCRetain((__bridge NSView*)m_window_info.window_handle);
[m_view setWantsLayer:YES];
[m_view setLayer:m_layer];
}
void MetalHostDisplay::DetachSurfaceOnMainThread()
{
ASSERT([NSThread isMainThread]);
[m_view setLayer:nullptr];
[m_view setWantsLayer:NO];
m_view = nullptr;
}
bool MetalHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, VsyncMode vsync, bool threaded_presentation, bool debug_device)
{ @autoreleasepool {
m_window_info = wi;
pxAssertRel(!m_dev.dev, "Device already created!");
std::string null_terminated_adapter_name(adapter_name);
NSString* ns_adapter_name = [NSString stringWithUTF8String:null_terminated_adapter_name.c_str()];
auto devs = MRCTransfer(MTLCopyAllDevices());
for (id<MTLDevice> dev in devs.Get())
{
if ([[dev name] isEqualToString:ns_adapter_name])
m_dev = GSMTLDevice(MRCRetain(dev));
}
if (!m_dev.dev)
{
if (!adapter_name.empty())
Console.Warning("Metal: Couldn't find adapter %s, using default", null_terminated_adapter_name.c_str());
m_dev = GSMTLDevice(MRCTransfer(MTLCreateSystemDefaultDevice()));
}
m_queue = MRCTransfer([m_dev.dev newCommandQueue]);
m_pass_desc = MRCTransfer([MTLRenderPassDescriptor new]);
[m_pass_desc colorAttachments][0].loadAction = MTLLoadActionClear;
[m_pass_desc colorAttachments][0].clearColor = MTLClearColorMake(0, 0, 0, 0);
[m_pass_desc colorAttachments][0].storeAction = MTLStoreActionStore;
m_capture_start_frame = 0;
if (char* env = getenv("MTL_CAPTURE"))
{
m_capture_start_frame = atoi(env);
}
if (m_capture_start_frame)
{
Console.WriteLn("Metal will capture frame %u", m_capture_start_frame);
}
if (m_dev.IsOk() && m_queue)
{
OnMainThread([this]
{
m_layer = MRCRetain([CAMetalLayer layer]);
[m_layer setDrawableSize:CGSizeMake(m_window_info.surface_width, m_window_info.surface_height)];
[m_layer setDevice:m_dev.dev];
AttachSurfaceOnMainThread();
});
SetVSync(vsync);
return true;
}
else
return false;
}}
bool MetalHostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device)
{
return true;
}
bool MetalHostDisplay::MakeRenderContextCurrent() { return true; }
bool MetalHostDisplay::DoneRenderContextCurrent() { return true; }
void MetalHostDisplay::DestroyRenderDevice()
{
DestroyRenderSurface();
m_queue = nullptr;
m_dev.Reset();
}
void MetalHostDisplay::DestroyRenderSurface()
{
if (!m_layer)
return;
OnMainThread([this]{ DetachSurfaceOnMainThread(); });
m_layer = nullptr;
}
bool MetalHostDisplay::ChangeRenderWindow(const WindowInfo& wi)
{
OnMainThread([this, &wi]
{
DetachSurfaceOnMainThread();
m_window_info = wi;
AttachSurfaceOnMainThread();
});
return true;
}
bool MetalHostDisplay::SupportsFullscreen() const { return false; }
bool MetalHostDisplay::IsFullscreen() { return false; }
bool MetalHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) { return false; }
HostDisplay::AdapterAndModeList MetalHostDisplay::GetAdapterAndModeList()
{
return GetMetalAdapterAndModeList();
}
std::string MetalHostDisplay::GetDriverInfo() const
{ @autoreleasepool {
std::string desc([[m_dev.dev description] UTF8String]);
desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported");
desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported");
desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported");
desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported");
desc += "\n Shader Version: " + std::string(to_string(m_dev.features.shader_version));
desc += "\n Max Texture Size: " + std::to_string(m_dev.features.max_texsize);
return desc;
}}
void MetalHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{
m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == static_cast<u32>(new_window_width) && m_window_info.surface_height == static_cast<u32>(new_window_height))
return;
m_window_info.surface_width = new_window_width;
m_window_info.surface_height = new_window_height;
@autoreleasepool
{
[m_layer setDrawableSize:CGSizeMake(new_window_width, new_window_height)];
}
}
std::unique_ptr<HostDisplayTexture> MetalHostDisplay::CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic)
{ @autoreleasepool {
MTLTextureDescriptor* desc = [MTLTextureDescriptor
texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
width:width
height:height
mipmapped:false];
[desc setUsage:MTLTextureUsageShaderRead];
[desc setStorageMode:MTLStorageModePrivate];
MRCOwned<id<MTLTexture>> tex = MRCTransfer([m_dev.dev newTextureWithDescriptor:desc]);
if (!tex)
return nullptr; // Something broke yay
[tex setLabel:@"MetalHostDisplay Texture"];
if (data)
UpdateTexture(tex, 0, 0, width, height, data, data_stride);
return std::make_unique<MetalHostDisplayTexture>(std::move(tex), width, height);
}}
void MetalHostDisplay::UpdateTexture(id<MTLTexture> texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride)
{
id<MTLCommandBuffer> cmdbuf = [m_queue commandBuffer];
id<MTLBlitCommandEncoder> enc = [cmdbuf blitCommandEncoder];
size_t bytes = data_stride * height;
MRCOwned<id<MTLBuffer>> buf = MRCTransfer([m_dev.dev newBufferWithLength:bytes options:MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined]);
memcpy([buf contents], data, bytes);
[enc copyFromBuffer:buf
sourceOffset:0
sourceBytesPerRow:data_stride
sourceBytesPerImage:bytes
sourceSize:MTLSizeMake(width, height, 1)
toTexture:texture
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[enc endEncoding];
[cmdbuf commit];
}
void MetalHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride)
{ @autoreleasepool {
UpdateTexture((__bridge id<MTLTexture>)texture->GetHandle(), x, y, width, height, data, data_stride);
}}
static bool s_capture_next = false;
bool MetalHostDisplay::BeginPresent(bool frame_skip)
{ @autoreleasepool {
GSDeviceMTL* dev = static_cast<GSDeviceMTL*>(g_gs_device.get());
if (dev && m_capture_start_frame && dev->FrameNo() == m_capture_start_frame)
s_capture_next = true;
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless || !g_gs_device)
{
ImGui::EndFrame();
return false;
}
id<MTLCommandBuffer> buf = dev->GetRenderCmdBuf();
m_current_drawable = MRCRetain([m_layer nextDrawable]);
dev->EndRenderPass();
if (!m_current_drawable)
{
[buf pushDebugGroup:@"Present Skipped"];
[buf popDebugGroup];
dev->FlushEncoders();
ImGui::EndFrame();
return false;
}
[m_pass_desc colorAttachments][0].texture = [m_current_drawable texture];
id<MTLRenderCommandEncoder> enc = [buf renderCommandEncoderWithDescriptor:m_pass_desc];
[enc setLabel:@"Present"];
dev->m_current_render.encoder = MRCRetain(enc);
return true;
}}
void MetalHostDisplay::EndPresent()
{ @autoreleasepool {
GSDeviceMTL* dev = static_cast<GSDeviceMTL*>(g_gs_device.get());
pxAssertDev(dev && dev->m_current_render.encoder && dev->m_current_render_cmdbuf, "BeginPresent cmdbuf was destroyed");
ImGui::Render();
dev->RenderImGui(ImGui::GetDrawData());
dev->EndRenderPass();
if (m_current_drawable)
[dev->m_current_render_cmdbuf addScheduledHandler:[drawable = std::move(m_current_drawable)](id<MTLCommandBuffer>){
[drawable present];
}];
dev->FlushEncoders();
m_current_drawable = nullptr;
if (m_capture_start_frame)
{
if (@available(macOS 10.15, iOS 13, *))
{
static NSString* const path = @"/tmp/PCSX2MTLCapture.gputrace";
static u32 frames;
if (frames)
{
--frames;
if (!frames)
{
[[MTLCaptureManager sharedCaptureManager] stopCapture];
Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace finished");
[[NSWorkspace sharedWorkspace] selectFile:path
inFileViewerRootedAtPath:@"/tmp/"];
}
}
else if (s_capture_next)
{
s_capture_next = false;
MTLCaptureManager* mgr = [MTLCaptureManager sharedCaptureManager];
if ([mgr supportsDestination:MTLCaptureDestinationGPUTraceDocument])
{
MTLCaptureDescriptor* desc = [[MTLCaptureDescriptor new] autorelease];
[desc setCaptureObject:m_dev.dev];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
[desc setOutputURL:[NSURL fileURLWithPath:path]];
[desc setDestination:MTLCaptureDestinationGPUTraceDocument];
NSError* err = nullptr;
[mgr startCaptureWithDescriptor:desc error:&err];
if (err)
{
Console.Error("Metal Trace Capture failed: %s", [[err localizedDescription] UTF8String]);
}
else
{
Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace started");
frames = 2;
}
}
else
{
Console.Error("Metal Trace Capture Failed: MTLCaptureManager doesn't support GPU trace documents! (Did you forget to run with METAL_CAPTURE_ENABLED=1?)");
}
}
}
}
}}
void MetalHostDisplay::SetVSync(VsyncMode mode)
{
[m_layer setDisplaySyncEnabled:mode != VsyncMode::Off];
m_vsync_mode = mode;
}
bool MetalHostDisplay::CreateImGuiContext()
{
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererName = "pcsx2_imgui_metal";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
return true;
}
void MetalHostDisplay::DestroyImGuiContext()
{
ImGui::GetIO().Fonts->SetTexID(nullptr);
}
bool MetalHostDisplay::UpdateImGuiFontTexture()
{ @autoreleasepool {
u8* data;
int width, height;
ImFontAtlas* fonts = ImGui::GetIO().Fonts;
fonts->GetTexDataAsAlpha8(&data, &width, &height);
MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatA8Unorm width:width height:height mipmapped:false];
[desc setUsage:MTLTextureUsageShaderRead];
[desc setStorageMode:MTLStorageModePrivate];
if (@available(macOS 10.15, *))
if (m_dev.features.texture_swizzle)
[desc setSwizzle:MTLTextureSwizzleChannelsMake(MTLTextureSwizzleOne, MTLTextureSwizzleOne, MTLTextureSwizzleOne, MTLTextureSwizzleAlpha)];
m_font_tex = MRCTransfer([m_dev.dev newTextureWithDescriptor:desc]);
[m_font_tex setLabel:@"ImGui Font"];
UpdateTexture(m_font_tex, 0, 0, width, height, data, width);
fonts->SetTexID((__bridge void*)m_font_tex);
return static_cast<bool>(m_font_tex);
}}
bool MetalHostDisplay::GetHostRefreshRate(float* refresh_rate)
{
OnMainThread([this, refresh_rate]
{
u32 did = [[[[[m_view window] screen] deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
if (CGDisplayModeRef mode = CGDisplayCopyDisplayMode(did))
{
*refresh_rate = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode);
}
else
{
*refresh_rate = 0;
}
});
return *refresh_rate != 0;
}
#endif // __APPLE__
+8 -4
View File
@@ -32,6 +32,7 @@ void gsSetVideoMode(GS_VideoMode mode)
{
gsVideoMode = mode;
UpdateVSyncRate();
CSRreg.FIELD = 1;
}
// Make sure framelimiter options are in sync with GS capabilities.
@@ -84,9 +85,10 @@ static __fi void gsCSRwrite( const tGS_CSR& csr )
//gifUnit.Reset(true); // Don't think gif should be reset...
gifUnit.gsSIGNAL.queued = false;
GetMTGS().SendSimplePacket(GS_RINGTYPE_RESET, 0, 0, 0);
const u32 field = CSRreg.FIELD;
CSRreg.Reset();
GSIMR.reset();
CSRreg.FIELD = field;
}
if(csr.FLUSH)
@@ -326,19 +328,20 @@ __fi u16 gsRead16(u32 mem)
case GS_SIGLBLID:
return *(u16*)PS2GS_BASE(mem);
default: // Only SIGLBLID and CSR are readable, everything else mirrors CSR
return *(u16*)PS2GS_BASE(GS_CSR + (mem & 0xF));
return *(u16*)PS2GS_BASE(GS_CSR + (mem & 0x7));
}
}
__fi u32 gsRead32(u32 mem)
{
GIF_LOG("GS read 32 from %8.8lx value: %8.8lx", mem, *(u32*)PS2GS_BASE(mem));
switch (mem & ~0xF)
{
case GS_SIGLBLID:
return *(u32*)PS2GS_BASE(mem);
default: // Only SIGLBLID and CSR are readable, everything else mirrors CSR
return *(u32*)PS2GS_BASE(GS_CSR + (mem & 0xF));
return *(u32*)PS2GS_BASE(GS_CSR + (mem & 0xC));
}
}
@@ -346,12 +349,13 @@ __fi u64 gsRead64(u32 mem)
{
// fixme - PS2GS_BASE(mem+4) = (g_RealGSMem+(mem + 4 & 0x13ff))
GIF_LOG("GS read 64 from %8.8lx value: %8.8lx_%8.8lx", mem, *(u32*)PS2GS_BASE(mem+4), *(u32*)PS2GS_BASE(mem) );
switch (mem & ~0xF)
{
case GS_SIGLBLID:
return *(u64*)PS2GS_BASE(mem);
default: // Only SIGLBLID and CSR are readable, everything else mirrors CSR
return *(u64*)PS2GS_BASE(GS_CSR + (mem & 0xF));
return *(u64*)PS2GS_BASE(GS_CSR + (mem & 0x8));
}
}
+13 -3
View File
@@ -26,6 +26,7 @@
#include "Renderers/Null/GSRendererNull.h"
#include "Renderers/Null/GSDeviceNull.h"
#include "Renderers/OpenGL/GSDeviceOGL.h"
#include "Renderers/Metal/GSMetalCPPAccessible.h"
#include "Renderers/HW/GSRendererNew.h"
#include "Renderers/HW/GSTextureReplacements.h"
#include "GSLzma.h"
@@ -166,6 +167,10 @@ static HostDisplay::RenderAPI GetAPIForRenderer(GSRendererType renderer)
case GSRendererType::VK:
return HostDisplay::RenderAPI::Vulkan;
#ifdef __APPLE__
case GSRendererType::Metal:
return HostDisplay::RenderAPI::Metal;
#endif
#ifdef _WIN32
case GSRendererType::DX11:
case GSRendererType::SW:
@@ -189,7 +194,11 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
g_gs_device = std::make_unique<GSDevice11>();
break;
#endif
#ifdef __APPLE__
case HostDisplay::RenderAPI::Metal:
g_gs_device = std::unique_ptr<GSDevice>(MakeGSDeviceMTL());
break;
#endif
case HostDisplay::RenderAPI::OpenGL:
case HostDisplay::RenderAPI::OpenGLES:
g_gs_device = std::make_unique<GSDeviceOGL>();
@@ -1180,6 +1189,9 @@ void GSApp::Init()
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::Auto), "Automatic", ""));
#ifdef _WIN32
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::DX11), "Direct3D 11", ""));
#endif
#ifdef __APPLE__
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::Metal), "Metal", ""));
#endif
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL), "OpenGL", ""));
#ifdef ENABLE_VULKAN
@@ -1357,8 +1369,6 @@ void GSApp::Init()
m_default_configuration["PrecacheTextureReplacements"] = "0";
m_default_configuration["preload_frame_with_gs_data"] = "0";
m_default_configuration["Renderer"] = std::to_string(static_cast<int>(GSRendererType::Auto));
m_default_configuration["resx"] = "1024";
m_default_configuration["resy"] = "1024";
m_default_configuration["save"] = "0";
m_default_configuration["savef"] = "0";
m_default_configuration["savel"] = "5000";
+2 -1
View File
@@ -18,7 +18,8 @@
// clang-format off
// MacOS headers define PAGE_SIZE to the size of an x86 page
#ifdef PAGE_SIZE
#ifdef __APPLE__
#include <mach/vm_page_size.h>
#undef PAGE_SIZE
#endif
+94 -42
View File
@@ -134,6 +134,7 @@ GSState::GSState()
PRIM = &m_env.PRIM;
//CSR->rREV = 0x20;
m_env.PRMODECONT.AC = 1;
m_last_prim.U32[0] = PRIM->U32[0];
Reset();
@@ -650,6 +651,15 @@ void GSState::DumpVertices(const std::string& filename)
file.close();
}
__inline void GSState::CheckFlushes()
{
if (m_primflush)
Flush();
if ((m_context->FRAME.FBMSK & GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk) != GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk)
m_mem.m_clut.Invalidate(m_context->FRAME.Block());
}
void GSState::GIFPackedRegHandlerNull(const GIFPackedReg* RESTRICT r)
{
}
@@ -707,6 +717,8 @@ void GSState::GIFPackedRegHandlerUV_Hack(const GIFPackedReg* RESTRICT r)
template <u32 prim, u32 adc, bool auto_flush, bool index_swap>
void GSState::GIFPackedRegHandlerXYZF2(const GIFPackedReg* RESTRICT r)
{
CheckFlushes();
GSVector4i xy = GSVector4i::loadl(&r->U64[0]);
GSVector4i zf = GSVector4i::loadl(&r->U64[1]);
@@ -721,6 +733,8 @@ void GSState::GIFPackedRegHandlerXYZF2(const GIFPackedReg* RESTRICT r)
template <u32 prim, u32 adc, bool auto_flush, bool index_swap>
void GSState::GIFPackedRegHandlerXYZ2(const GIFPackedReg* RESTRICT r)
{
CheckFlushes();
const GSVector4i xy = GSVector4i::loadl(&r->U64[0]);
const GSVector4i z = GSVector4i::loadl(&r->U64[1]);
const GSVector4i xyz = xy.upl16(xy.srl<4>()).upl32(z);
@@ -749,6 +763,8 @@ void GSState::GIFPackedRegHandlerSTQRGBAXYZF2(const GIFPackedReg* RESTRICT r, u3
{
ASSERT(size > 0 && size % 3 == 0);
CheckFlushes();
const GIFPackedReg* RESTRICT r_end = r + size;
while (r < r_end)
@@ -781,6 +797,8 @@ void GSState::GIFPackedRegHandlerSTQRGBAXYZ2(const GIFPackedReg* RESTRICT r, u32
{
ASSERT(size > 0 && size % 3 == 0);
CheckFlushes();
const GIFPackedReg* RESTRICT r_end = r + size;
while (r < r_end)
@@ -817,19 +835,6 @@ void GSState::GIFRegHandlerNull(const GIFReg* RESTRICT r)
__forceinline void GSState::ApplyPRIM(u32 prim)
{
if (GSUtil::GetPrimClass(m_env.PRIM.PRIM) == GSUtil::GetPrimClass(prim & 7)) // NOTE: assume strips/fans are converted to lists
{
u32 prim_mask = 0x7f8;
if (GSConfig.UseHardwareRenderer() && GSUtil::GetPrimClass(prim & 7) == GS_TRIANGLE_CLASS)
prim_mask &= ~0x80; // Mask out AA1.
if (m_env.PRMODECONT.AC == 1 && (m_env.PRIM.U32[0] ^ prim) & prim_mask) // all fields except PRIM
Flush();
}
else
{
Flush();
}
if (m_env.PRMODECONT.AC == 1)
{
@@ -842,12 +847,29 @@ __forceinline void GSState::ApplyPRIM(u32 prim)
m_env.PRIM.PRIM = prim & 0x7;
}
m_primflush = false;
u32 prim_mask = 0x7ff;
// Same class of draw so we don't need to flush
if (GSUtil::GetPrimClass(m_last_prim.PRIM) == GSUtil::GetPrimClass(m_env.PRIM.PRIM))
prim_mask &= ~0x7;
if (GSConfig.UseHardwareRenderer() && GSUtil::GetPrimClass(prim & 7) == GS_TRIANGLE_CLASS)
prim_mask &= ~0x80; // Mask out AA1.
if ((m_last_prim.U32[0] ^ m_env.PRIM.U32[0]) & prim_mask)
m_primflush = true;
UpdateVertexKick();
ASSERT(m_index.tail == 0 || !g_gs_device->Features().provoking_vertex_last || m_index.buff[m_index.tail - 1] + 1 == m_vertex.next);
if (m_index.tail == 0)
{
m_vertex.next = 0;
m_primflush = false;
m_last_prim.U32[0] = m_env.PRIM.U32[0];
}
m_vertex.head = m_vertex.tail = m_vertex.next; // remove unused vertices from the end of the vertex buffer
}
@@ -898,6 +920,8 @@ void GSState::GIFRegHandlerUV_Hack(const GIFReg* RESTRICT r)
template <u32 prim, u32 adc, bool auto_flush, bool index_swap>
void GSState::GIFRegHandlerXYZF2(const GIFReg* RESTRICT r)
{
CheckFlushes();
GSVector4i xyzf = GSVector4i::loadl(&r->XYZF);
GSVector4i xyz = xyzf & (GSVector4i::xffffffff().upl32(GSVector4i::x00ffffff()));
GSVector4i uvf = GSVector4i::load((int)m_v.UV).upl32(xyzf.srl32(24).srl<4>());
@@ -910,6 +934,8 @@ void GSState::GIFRegHandlerXYZF2(const GIFReg* RESTRICT r)
template <u32 prim, u32 adc, bool auto_flush, bool index_swap>
void GSState::GIFRegHandlerXYZ2(const GIFReg* RESTRICT r)
{
CheckFlushes();
m_v.m[1] = GSVector4i::load(&r->XYZ, &m_v.UV);
VertexKick<prim, auto_flush, index_swap>(adc);
@@ -940,7 +966,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0)
constexpr u64 mask = 0x1f78001fffffffffull; // TBP0 TBW PSM TW TH TCC TFX CPSM CSA
if (wt || PRIM->CTXT == i && ((TEX0.U64 ^ m_env.CTXT[i].TEX0.U64) & mask))
if (wt || (PRIM->CTXT == i || m_primflush) && ((TEX0.U64 ^ m_env.CTXT[i].TEX0.U64) & mask))
Flush();
TEX0.CPSM &= 0xa; // 1010b
@@ -1074,7 +1100,7 @@ void GSState::GIFRegHandlerCLAMP(const GIFReg* RESTRICT r)
{
GL_REG("CLAMP_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
if (PRIM->CTXT == i && r->CLAMP != m_env.CTXT[i].CLAMP)
if ((PRIM->CTXT == i || m_primflush) && r->CLAMP != m_env.CTXT[i].CLAMP)
Flush();
m_env.CTXT[i].CLAMP = (GSVector4i)r->CLAMP;
@@ -1094,7 +1120,7 @@ void GSState::GIFRegHandlerTEX1(const GIFReg* RESTRICT r)
{
GL_REG("TEX1_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
if (PRIM->CTXT == i && r->TEX1 != m_env.CTXT[i].TEX1)
if ((PRIM->CTXT == i || m_primflush) && r->TEX1 != m_env.CTXT[i].TEX1)
Flush();
m_env.CTXT[i].TEX1 = (GSVector4i)r->TEX1;
@@ -1148,24 +1174,33 @@ void GSState::GIFRegHandlerPRMODE(const GIFReg* RESTRICT r)
{
GL_REG("PRMODE = 0x%x_%x", r->U32[1], r->U32[0]);
if (!m_env.PRMODECONT.AC)
{
u32 prim_mask = 0x7f8;
if (GSConfig.UseHardwareRenderer() && GSUtil::GetPrimClass(m_env.PRIM.PRIM) == GS_TRIANGLE_CLASS)
prim_mask &= ~0x80; // Mask out AA1.
if ((m_env.PRIM.U32[0] ^ r->PRMODE.U32[0]) & prim_mask)
Flush();
}
else
{
// We're in PRIM mode, need to ignore any writes
if (m_env.PRMODECONT.AC)
return;
}
const u32 _PRIM = m_env.PRIM.PRIM;
m_env.PRIM = (GSVector4i)r->PRMODE;
m_env.PRIM.PRIM = _PRIM;
u32 prim_mask = 0x7ff;
// Same class of draw so we don't need to flush
if (GSUtil::GetPrimClass(m_last_prim.PRIM) == GSUtil::GetPrimClass(m_env.PRIM.PRIM))
prim_mask &= ~0x7;
if (GSConfig.UseHardwareRenderer() && GSUtil::GetPrimClass(m_env.PRIM.PRIM) == GS_TRIANGLE_CLASS)
prim_mask &= ~0x80; // Mask out AA1.
m_primflush = false;
if ((m_last_prim.U32[0] ^ m_env.PRIM.U32[0]) & prim_mask)
m_primflush = true;
if (m_index.tail == 0)
{
m_primflush = false;
m_last_prim.U32[0] = m_env.PRIM.U32[0];
}
UpdateContext();
}
@@ -1194,7 +1229,7 @@ void GSState::GIFRegHandlerMIPTBP1(const GIFReg* RESTRICT r)
{
GL_REG("MIPTBP1_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
if (PRIM->CTXT == i && r->MIPTBP1 != m_env.CTXT[i].MIPTBP1)
if ((PRIM->CTXT == i || m_primflush) && r->MIPTBP1 != m_env.CTXT[i].MIPTBP1)
Flush();
m_env.CTXT[i].MIPTBP1 = (GSVector4i)r->MIPTBP1;
@@ -1205,7 +1240,7 @@ void GSState::GIFRegHandlerMIPTBP2(const GIFReg* RESTRICT r)
{
GL_REG("MIPTBP2_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
if (PRIM->CTXT == i && r->MIPTBP2 != m_env.CTXT[i].MIPTBP2)
if ((PRIM->CTXT == i || m_primflush) && r->MIPTBP2 != m_env.CTXT[i].MIPTBP2)
Flush();
m_env.CTXT[i].MIPTBP2 = (GSVector4i)r->MIPTBP2;
@@ -1244,7 +1279,7 @@ void GSState::GIFRegHandlerTEXFLUSH(const GIFReg* RESTRICT r)
template <int i>
void GSState::GIFRegHandlerSCISSOR(const GIFReg* RESTRICT r)
{
if (PRIM->CTXT == i && r->SCISSOR != m_env.CTXT[i].SCISSOR)
if ((PRIM->CTXT == i || m_primflush) && r->SCISSOR != m_env.CTXT[i].SCISSOR)
Flush();
m_env.CTXT[i].SCISSOR = (GSVector4i)r->SCISSOR;
@@ -1258,7 +1293,7 @@ template <int i>
void GSState::GIFRegHandlerALPHA(const GIFReg* RESTRICT r)
{
GL_REG("ALPHA = 0x%x_%x", r->U32[1], r->U32[0]);
if (PRIM->CTXT == i && r->ALPHA != m_env.CTXT[i].ALPHA)
if ((PRIM->CTXT == i || m_primflush) && r->ALPHA != m_env.CTXT[i].ALPHA)
Flush();
m_env.CTXT[i].ALPHA = (GSVector4i)r->ALPHA;
@@ -1307,7 +1342,7 @@ void GSState::GIFRegHandlerCOLCLAMP(const GIFReg* RESTRICT r)
template <int i>
void GSState::GIFRegHandlerTEST(const GIFReg* RESTRICT r)
{
if (PRIM->CTXT == i && r->TEST != m_env.CTXT[i].TEST)
if ((PRIM->CTXT == i || m_primflush) && r->TEST != m_env.CTXT[i].TEST)
Flush();
m_env.CTXT[i].TEST = (GSVector4i)r->TEST;
@@ -1324,7 +1359,7 @@ void GSState::GIFRegHandlerPABE(const GIFReg* RESTRICT r)
template <int i>
void GSState::GIFRegHandlerFBA(const GIFReg* RESTRICT r)
{
if (PRIM->CTXT == i && r->FBA != m_env.CTXT[i].FBA)
if ((PRIM->CTXT == i || m_primflush) && r->FBA != m_env.CTXT[i].FBA)
Flush();
m_env.CTXT[i].FBA = (GSVector4i)r->FBA;
@@ -1340,7 +1375,7 @@ void GSState::GIFRegHandlerFRAME(const GIFReg* RESTRICT r)
// However there is some issues so even software mode is incorrect on PCSX2, but this works better..
NewFrame.FBW = std::clamp(NewFrame.FBW, 1U, 32U);
if (PRIM->CTXT == i && NewFrame != m_env.CTXT[i].FRAME)
if ((PRIM->CTXT == i || m_primflush) && NewFrame != m_env.CTXT[i].FRAME)
Flush();
if ((NewFrame.PSM & 0x30) == 0x30)
@@ -1399,7 +1434,7 @@ void GSState::GIFRegHandlerZBUF(const GIFReg* RESTRICT r)
else
ZBUF.PSM |= 0x30;
if (PRIM->CTXT == i && ZBUF != m_env.CTXT[i].ZBUF)
if ((PRIM->CTXT == i || m_primflush) && ZBUF != m_env.CTXT[i].ZBUF)
Flush();
if ((m_env.CTXT[i].ZBUF.U32[0] ^ ZBUF.U32[0]) & 0x3f0001ff) // ZBP PSM
@@ -1491,7 +1526,6 @@ void GSState::GIFRegHandlerHWREG(const GIFReg* RESTRICT r)
void GSState::Flush()
{
FlushWrite();
FlushPrim();
}
@@ -1522,6 +1556,15 @@ void GSState::FlushWrite()
void GSState::FlushPrim()
{
const u32 new_prim = PRIM->U32[0];
if (m_primflush)
{
// We need to restore the old PRIM and update the Context (in case it changed)
m_env.PRIM.U32[0] = m_last_prim.U32[0];
UpdateContext();
}
if (m_index.tail > 0)
{
GL_REG("FlushPrim ctxt %d", PRIM->CTXT);
@@ -1554,10 +1597,12 @@ void GSState::FlushPrim()
case GS_LINELIST:
case GS_LINESTRIP:
case GS_SPRITE:
unused = 1;
buff[0] = m_vertex.buff[tail - 1];
case GS_TRIANGLELIST:
case GS_TRIANGLESTRIP:
unused = tail - head;
memcpy(buff, &m_vertex.buff[head], sizeof(GSVertex) * unused);
unused = std::min<size_t>(tail - head, 2);
memcpy(buff, &m_vertex.buff[tail - unused], sizeof(GSVertex) * 2);
break;
case GS_TRIANGLEFAN:
buff[0] = m_vertex.buff[head];
@@ -1626,6 +1671,16 @@ void GSState::FlushPrim()
m_vertex.next = 0;
}
}
if (m_primflush)
{
// Restore the backup
PRIM->U32[0] = new_prim;
UpdateContext();
}
m_primflush = false;
m_last_prim.U32[0] = new_prim;
}
void GSState::Write(const u8* mem, int len)
@@ -2730,9 +2785,6 @@ __forceinline void GSState::VertexKick(u32 skip)
break;
}
if ((m_context->FRAME.FBMSK & GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk) != GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk)
m_mem.m_clut.Invalidate(m_context->FRAME.Block());
if (auto_flush && m_index.tail >= n)
HandleAutoFlush();
+4
View File
@@ -51,6 +51,8 @@ class GSState : public GSAlignedClass<32>
GIFPackedRegHandler m_fpGIFPackedRegHandlers[16];
GIFPackedRegHandler m_fpGIFPackedRegHandlerXYZ[8][4];
void CheckFlushes();
void GIFPackedRegHandlerNull(const GIFPackedReg* RESTRICT r);
void GIFPackedRegHandlerRGBA(const GIFPackedReg* RESTRICT r);
void GIFPackedRegHandlerSTQ(const GIFPackedReg* RESTRICT r);
@@ -232,6 +234,8 @@ public:
bool m_NTSC_Saturation;
bool m_nativeres;
bool m_mipmap;
bool m_primflush;
GIFRegPRIM m_last_prim;
static int s_n;
bool s_dump;
+3
View File
@@ -190,6 +190,9 @@ CRCHackLevel GSUtil::GetRecommendedCRCHackLevel(GSRendererType type)
GSRendererType GSUtil::GetPreferredRenderer()
{
#ifdef __APPLE__
return GSRendererType::Metal;
#endif
#ifdef _WIN32
if (D3D::ShouldPreferD3D())
return GSRendererType::DX11;
+9 -8
View File
@@ -593,18 +593,16 @@ public:
memset(this, 0, sizeof(*this));
}
};
// clang-format on
// clang-format off
enum : u8
enum BlendFactor : u8
{
// HW blend factors
SRC_COLOR, INV_SRC_COLOR, DST_COLOR, INV_DST_COLOR,
SRC1_COLOR, INV_SRC1_COLOR, SRC_ALPHA, INV_SRC_ALPHA,
DST_ALPHA, INV_DST_ALPHA, SRC1_ALPHA, INV_SRC1_ALPHA,
CONST_COLOR, INV_CONST_COLOR, CONST_ONE, CONST_ZERO,
SRC_COLOR, INV_SRC_COLOR, DST_COLOR, INV_DST_COLOR,
SRC1_COLOR, INV_SRC1_COLOR, SRC_ALPHA, INV_SRC_ALPHA,
DST_ALPHA, INV_DST_ALPHA, SRC1_ALPHA, INV_SRC1_ALPHA,
CONST_COLOR, INV_CONST_COLOR, CONST_ONE, CONST_ZERO,
};
enum : u8
enum BlendOp : u8
{
// HW blend operations
OP_ADD, OP_SUBTRACT, OP_REV_SUBTRACT
@@ -799,4 +797,7 @@ struct GSAdapter
#endif
};
template <>
struct std::hash<GSHWDrawConfig::PSSelector> : public GSHWDrawConfig::PSSelectorHash {};
extern std::unique_ptr<GSDevice> g_gs_device;
+9 -6
View File
@@ -264,18 +264,21 @@ bool GSRenderer::Merge(int field)
if (m_regs->SMODE2.INT && GSConfig.InterlaceMode != GSInterlaceMode::Off)
{
const bool scanmask = (m_scanmask_used && scanmsk_frame) && GSConfig.InterlaceMode == GSInterlaceMode::Automatic;
if (GSConfig.InterlaceMode == GSInterlaceMode::Automatic && (m_regs->SMODE2.FFMD)) // Auto interlace enabled / Odd frame interlace setting
{
int field2 = 0;
int mode = 2;
constexpr int field2 = 1;
constexpr int mode = 2;
g_gs_device->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y);
}
else
{
bool scanmask = (m_scanmask_used && scanmsk_frame) && GSConfig.InterlaceMode == GSInterlaceMode::Automatic;
int field2 = 1 - ((static_cast<int>(GSConfig.InterlaceMode) - 1) & 1);
const int field2 = scanmask ? 0 : 1 - ((static_cast<int>(GSConfig.InterlaceMode) - 1) & 1);
const int offset = tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y;
int mode = scanmask ? 2 : (static_cast<int>(GSConfig.InterlaceMode) - 1) >> 1;
g_gs_device->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y);
g_gs_device->Interlace(ds, field ^ field2, mode, offset);
}
}
@@ -429,7 +432,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
g_gs_device->AgePool();
const bool blank_frame = !Merge(field ? 1 : 0);
const bool blank_frame = !Merge(field);
const bool skip_frame = m_frameskip;
if (blank_frame || skip_frame)
-1
View File
@@ -49,7 +49,6 @@ public:
virtual void KeyEvent(const HostKeyEvent& e);
virtual bool CanUpscale() { return false; }
virtual int GetUpscaleMultiplier() { return 1; }
virtual GSVector2i GetCustomResolution() { return GSVector2i(0, 0); }
virtual GSVector2 GetTextureScaleFactor() { return { 1.0f, 1.0f }; }
GSVector2i GetInternalResolution();
+1 -1
View File
@@ -46,7 +46,7 @@ public:
UNorm8, ///< A8UNorm texture for paletted textures and the OSD font
UInt16, ///< UInt16 texture for reading back 16-bit depth
UInt32, ///< UInt32 texture for reading back 24 and 32-bit depth
Int32, ///< Int32 texture for date emulation
PrimID, ///< Prim ID tracking texture for date emulation
BC1, ///< BC1, aka DXT1 compressed texture for replacements
BC2, ///< BC2, aka DXT2/3 compressed texture for replacements
BC3, ///< BC3, aka DXT4/5 compressed texture for replacements
+2 -2
View File
@@ -155,7 +155,7 @@ bool GSDevice11::Create(HostDisplay* display)
}
ShaderMacro sm_convert(m_shader_cache.GetFeatureLevel());
sm_convert.AddMacro("PS_SCALE_FACTOR", std::max(1u, GSConfig.UpscaleMultiplier));
sm_convert.AddMacro("PS_SCALE_FACTOR", GSConfig.UpscaleMultiplier);
D3D_SHADER_MACRO* sm_convert_ptr = sm_convert.GetPtr();
@@ -451,7 +451,7 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height
case GSTexture::Format::UNorm8: dxformat = DXGI_FORMAT_A8_UNORM; break;
case GSTexture::Format::UInt16: dxformat = DXGI_FORMAT_R16_UINT; break;
case GSTexture::Format::UInt32: dxformat = DXGI_FORMAT_R32_UINT; break;
case GSTexture::Format::Int32: dxformat = DXGI_FORMAT_R32_SINT; break;
case GSTexture::Format::PrimID: dxformat = DXGI_FORMAT_R32_SINT; break;
case GSTexture::Format::BC1: dxformat = DXGI_FORMAT_BC1_UNORM; break;
case GSTexture::Format::BC2: dxformat = DXGI_FORMAT_BC2_UNORM; break;
case GSTexture::Format::BC3: dxformat = DXGI_FORMAT_BC3_UNORM; break;
+1 -1
View File
@@ -155,7 +155,7 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
{
ShaderMacro sm(m_shader_cache.GetFeatureLevel());
sm.AddMacro("PS_SCALE_FACTOR", std::max(1u, GSConfig.UpscaleMultiplier));
sm.AddMacro("PS_SCALE_FACTOR", GSConfig.UpscaleMultiplier);
sm.AddMacro("PS_FST", sel.fst);
sm.AddMacro("PS_WMS", sel.wms);
sm.AddMacro("PS_WMT", sel.wmt);
+5 -71
View File
@@ -23,8 +23,6 @@ GSRendererHW::GSRendererHW()
: GSRenderer()
, m_width(default_rt_size.x)
, m_height(default_rt_size.y)
, m_custom_width(1024)
, m_custom_height(1024)
, m_tc(new GSTextureCache(this))
, m_src(nullptr)
, m_userhacks_tcoffset(false)
@@ -37,24 +35,12 @@ GSRendererHW::GSRendererHW()
m_mipmap = (GSConfig.HWMipmap >= HWMipmapLevel::Basic);
SetTCOffset();
if (!GSConfig.UpscaleMultiplier) // Custom Resolution
{
m_custom_width = m_width = theApp.GetConfigI("resx");
m_custom_height = m_height = theApp.GetConfigI("resy");
}
m_dump_root = root_hw;
GSTextureReplacements::Initialize(m_tc);
}
void GSRendererHW::SetScaling()
{
if (!GSConfig.UpscaleMultiplier)
{
CustomResolutionScaling();
return;
}
const GSVector2i crtc_size(GetDisplayRect().width(), GetDisplayRect().height());
// Details of (potential) perf impact of a big framebuffer
@@ -116,42 +102,6 @@ void GSRendererHW::SetScaling()
printf("Frame buffer size set to %dx%d (%dx%d)\n", fb_width, fb_height, m_width, m_height);
}
void GSRendererHW::CustomResolutionScaling()
{
const int crtc_width = GetDisplayRect().width();
const int crtc_height = GetDisplayRect().height();
GSVector2 scaling_ratio;
scaling_ratio.x = std::ceil(static_cast<float>(m_custom_width) / crtc_width);
scaling_ratio.y = std::ceil(static_cast<float>(m_custom_height) / crtc_height);
// Avoid using a scissor value which is too high, developers can even leave the scissor to max (2047)
// at some cases when they don't want to limit the rendering size. Our assumption is that developers
// set the scissor to the actual data in the buffer. Let's use the scissoring value only at such cases
const int scissor_width = std::min(640, static_cast<int>(m_context->SCISSOR.SCAX1 - m_context->SCISSOR.SCAX0) + 1);
const int scissor_height = std::min(640, static_cast<int>(m_context->SCISSOR.SCAY1 - m_context->SCISSOR.SCAY0) + 1);
GSVector2i scissored_buffer_size;
//TODO: SCAX is not used yet, not sure if it's worth considering the horizontal scissor? dunno where it helps yet.
// the ICO testcase is there to show that vertical scissor is helpful on the double scan mode games.
scissored_buffer_size.x = std::max(crtc_width, scissor_width);
scissored_buffer_size.y = std::max(crtc_height, scissor_height);
// We also consider for potential scissor sizes which are around
// the size of the actual image data stored. (Helps ICO to properly scale to right size by help of the
// scissoring values) Display rectangle has a height of 256 but scissor has a height of 512 which seems to
// be the real buffer size. Not sure if the width one is needed, need to check it on some random data before enabling it.
// int framebuffer_width = static_cast<int>(std::round(scissored_buffer_size.x * scaling_ratio.x));
const int framebuffer_height = static_cast<int>(std::round(scissored_buffer_size.y * scaling_ratio.y));
if (m_width >= m_custom_width && m_height >= framebuffer_height)
return;
m_tc->RemovePartial();
m_width = std::max(m_width, default_rt_size.x);
m_height = std::max(framebuffer_height, default_rt_size.y);
printf("Frame buffer size set to %dx%d (%dx%d)\n", scissored_buffer_size.x, scissored_buffer_size.y, m_width, m_height);
}
void GSRendererHW::SetTCOffset()
{
m_userhacks_tcoffset_x = std::max<s32>(GSConfig.UserHacks_TCOffsetX, 0) / -1000.0f;
@@ -201,11 +151,6 @@ int GSRendererHW::GetUpscaleMultiplier()
return GSConfig.UpscaleMultiplier;
}
GSVector2i GSRendererHW::GetCustomResolution()
{
return GSVector2i(m_custom_width, m_custom_height);
}
void GSRendererHW::Reset()
{
// TODO: GSreset can come from the main thread too => crash
@@ -230,9 +175,8 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
m_tc->RemoveAll();
// Reset RT size.
const bool custom_res = !GetUpscaleMultiplier();
m_width = custom_res ? m_custom_width : default_rt_size.x;
m_height = custom_res ? m_custom_height : default_rt_size.y;
m_width = default_rt_size.x;
m_height = default_rt_size.y;
m_reset = false;
}
@@ -760,20 +704,10 @@ GSVector2 GSRendererHW::GetTextureScaleFactor(const bool force_upscaling)
if (force_upscaling || CanUpscale())
{
const int multiplier = GetUpscaleMultiplier();
if (multiplier == 0)
{
// Custom resolution.
const GSVector4i display_rect = GetDisplayRect();
const GSVector2i requested_resolution = GetCustomResolution();
scale_factor.x = static_cast<float>(requested_resolution.x) / display_rect.width();
scale_factor.y = static_cast<float>(requested_resolution.y) / display_rect.height();
}
else
{
scale_factor.x = multiplier;
scale_factor.y = multiplier;
}
scale_factor.x = multiplier;
scale_factor.y = multiplier;
}
return scale_factor;
}
-4
View File
@@ -25,8 +25,6 @@ class GSRendererHW : public GSRenderer
private:
int m_width;
int m_height;
int m_custom_width;
int m_custom_height;
static constexpr float SSR_UV_TOLERANCE = 1.0f;
@@ -151,7 +149,6 @@ protected:
bool m_reset;
GSVector2i m_lod; // Min & Max level of detail
void CustomResolutionScaling();
public:
GSRendererHW();
@@ -164,7 +161,6 @@ public:
void SetGameCRC(u32 crc, int options) override;
bool CanUpscale() override;
int GetUpscaleMultiplier() override;
GSVector2i GetCustomResolution() override;
void SetScaling();
void Lines2Sprites();
void EmulateAtst(GSVector4& FogColor_AREF, u8& atst, const bool pass_2);
+1 -8
View File
@@ -1406,19 +1406,12 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
{
switch(m_renderer->GetUpscaleMultiplier())
{
case 0: //Custom Resolution
{
const float offset = 0.2f;
modx = dst->m_texture->GetScale().x + offset;
mody = dst->m_texture->GetScale().y + offset;
dst->m_texture->LikelyOffset = true;
break;
}
case 2: modx = 2.2f; mody = 2.2f; dst->m_texture->LikelyOffset = true; break;
case 3: modx = 3.1f; mody = 3.1f; dst->m_texture->LikelyOffset = true; break;
case 4: modx = 4.2f; mody = 4.2f; dst->m_texture->LikelyOffset = true; break;
case 5: modx = 5.3f; mody = 5.3f; dst->m_texture->LikelyOffset = true; break;
case 6: modx = 6.2f; mody = 6.2f; dst->m_texture->LikelyOffset = true; break;
case 7: modx = 7.1f; mody = 7.1f; dst->m_texture->LikelyOffset = true; break;
case 8: modx = 8.2f; mody = 8.2f; dst->m_texture->LikelyOffset = true; break;
default: modx = 0.0f; mody = 0.0f; dst->m_texture->LikelyOffset = false; break;
}
+398
View File
@@ -0,0 +1,398 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "GS/Renderers/Common/GSDevice.h"
#ifndef __OBJC__
#error "This header is for use with Objective-C++ only.
#endif
#ifdef __APPLE__
#include "common/HashCombine.h"
#include "common/MRCHelpers.h"
#include "GS/GS.h"
#include "GSMTLDeviceInfo.h"
#include "GSMTLSharedHeader.h"
#include <AppKit/AppKit.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
#include <atomic>
#include <memory>
#include <unordered_map>
struct PipelineSelectorExtrasMTL
{
union
{
struct
{
GSTexture::Format rt : 4;
u8 writemask : 4;
GSDevice::BlendFactor src_factor : 4;
GSDevice::BlendFactor dst_factor : 4;
GSDevice::BlendOp blend_op : 2;
bool blend_enable : 1;
bool has_depth : 1;
bool has_stencil : 1;
};
u8 _key[3];
};
u32 fullkey() { return _key[0] | (_key[1] << 8) | (_key[2] << 16); }
PipelineSelectorExtrasMTL(): _key{} {}
PipelineSelectorExtrasMTL(GSHWDrawConfig::BlendState blend, GSTexture* rt, GSHWDrawConfig::ColorMaskSelector cms, bool has_depth, bool has_stencil)
: _key{}
{
this->rt = rt ? rt->GetFormat() : GSTexture::Format::Invalid;
MTLColorWriteMask mask = MTLColorWriteMaskNone;
if (cms.wr) mask |= MTLColorWriteMaskRed;
if (cms.wg) mask |= MTLColorWriteMaskGreen;
if (cms.wb) mask |= MTLColorWriteMaskBlue;
if (cms.wa) mask |= MTLColorWriteMaskAlpha;
this->writemask = mask;
this->src_factor = static_cast<GSDevice::BlendFactor>(blend.src_factor);
this->dst_factor = static_cast<GSDevice::BlendFactor>(blend.dst_factor);
this->blend_op = static_cast<GSDevice::BlendOp>(blend.op);
this->blend_enable = blend.enable;
this->has_depth = has_depth;
this->has_stencil = has_stencil;
}
};
struct PipelineSelectorMTL
{
GSHWDrawConfig::PSSelector ps;
PipelineSelectorExtrasMTL extras;
GSHWDrawConfig::VSSelector vs;
PipelineSelectorMTL()
{
memset(this, 0, sizeof(*this));
}
PipelineSelectorMTL(GSHWDrawConfig::VSSelector vs, GSHWDrawConfig::PSSelector ps, PipelineSelectorExtrasMTL extras)
{
memset(this, 0, sizeof(*this));
this->vs = vs;
this->ps = ps;
this->extras = extras;
}
PipelineSelectorMTL(const PipelineSelectorMTL& other)
{
memcpy(this, &other, sizeof(other));
}
PipelineSelectorMTL& operator=(const PipelineSelectorMTL& other)
{
memcpy(this, &other, sizeof(other));
return *this;
}
bool operator==(const PipelineSelectorMTL& other) const
{
return BitEqual(*this, other);
}
};
static_assert(sizeof(PipelineSelectorMTL) == 16);
template <>
struct std::hash<PipelineSelectorMTL>
{
size_t operator()(const PipelineSelectorMTL& sel) const
{
size_t h = 0;
size_t pieces[(sizeof(PipelineSelectorMTL) + sizeof(size_t) - 1) / sizeof(size_t)] = {};
memcpy(pieces, &sel, sizeof(PipelineSelectorMTL));
for (auto& piece : pieces)
HashCombine(h, piece);
return h;
}
};
class GSScopedDebugGroupMTL
{
id<MTLCommandBuffer> m_buffer;
public:
GSScopedDebugGroupMTL(id<MTLCommandBuffer> buffer, NSString* name): m_buffer(buffer)
{
[m_buffer pushDebugGroup:name];
}
~GSScopedDebugGroupMTL()
{
[m_buffer popDebugGroup];
}
};
struct ImDrawData;
class GSTextureMTL;
class GSDeviceMTL final : public GSDevice
{
public:
using DepthStencilSelector = GSHWDrawConfig::DepthStencilSelector;
using SamplerSelector = GSHWDrawConfig::SamplerSelector;
enum class LoadAction
{
DontCare,
DontCareIfFull,
Load,
};
class UsageTracker
{
struct UsageEntry
{
u64 drawno;
size_t pos;
};
std::vector<UsageEntry> m_usage;
size_t m_size = 0;
size_t m_pos = 0;
public:
size_t Size() { return m_size; }
size_t Pos() { return m_pos; }
bool PrepareForAllocation(u64 last_draw, size_t amt);
size_t Allocate(u64 current_draw, size_t amt);
void Reset(size_t new_size);
};
struct Map
{
id<MTLBuffer> gpu_buffer;
size_t gpu_offset;
void* cpu_buffer;
};
struct UploadBuffer
{
UsageTracker usage;
MRCOwned<id<MTLBuffer>> mtlbuffer;
void* buffer = nullptr;
};
struct BufferPair
{
UsageTracker usage;
MRCOwned<id<MTLBuffer>> cpubuffer;
MRCOwned<id<MTLBuffer>> gpubuffer;
void* buffer = nullptr;
size_t last_upload = 0;
};
struct ConvertShaderVertex
{
simd_float2 pos;
simd_float2 texpos;
};
struct VSSelector
{
union
{
struct
{
bool iip : 1;
bool fst : 1;
bool point_size : 1;
};
u8 key;
};
VSSelector(): key(0) {}
VSSelector(u8 key): key(key) {}
};
using PSSelector = GSHWDrawConfig::PSSelector;
// MARK: Configuration
int m_mipmap;
// MARK: Permanent resources
std::shared_ptr<std::pair<std::mutex, GSDeviceMTL*>> m_backref;
GSMTLDevice m_dev;
MRCOwned<id<MTLCommandQueue>> m_queue;
MRCOwned<id<MTLFence>> m_draw_sync_fence;
MRCOwned<MTLFunctionConstantValues*> m_fn_constants;
MRCOwned<MTLVertexDescriptor*> m_hw_vertex;
std::unique_ptr<GSTextureMTL> m_font;
// Draw IDs are used to make sure we're not clobbering things
u64 m_current_draw = 1;
std::atomic<u64> m_last_finished_draw{0};
// Functions and Pipeline States
MRCOwned<id<MTLRenderPipelineState>> m_convert_pipeline[static_cast<int>(ShaderConvert::Count)];
MRCOwned<id<MTLRenderPipelineState>> m_present_pipeline[static_cast<int>(ShaderConvert::Count)];
MRCOwned<id<MTLRenderPipelineState>> m_convert_pipeline_copy[2];
MRCOwned<id<MTLRenderPipelineState>> m_convert_pipeline_copy_mask[1 << 4];
MRCOwned<id<MTLRenderPipelineState>> m_merge_pipeline[4];
MRCOwned<id<MTLRenderPipelineState>> m_interlace_pipeline[4];
MRCOwned<id<MTLRenderPipelineState>> m_datm_pipeline[2];
MRCOwned<id<MTLRenderPipelineState>> m_stencil_clear_pipeline;
MRCOwned<id<MTLRenderPipelineState>> m_primid_init_pipeline[2][2];
MRCOwned<id<MTLRenderPipelineState>> m_hdr_init_pipeline;
MRCOwned<id<MTLRenderPipelineState>> m_hdr_resolve_pipeline;
MRCOwned<id<MTLRenderPipelineState>> m_fxaa_pipeline;
MRCOwned<id<MTLRenderPipelineState>> m_shadeboost_pipeline;
MRCOwned<id<MTLRenderPipelineState>> m_imgui_pipeline;
MRCOwned<id<MTLRenderPipelineState>> m_imgui_pipeline_a8;
MRCOwned<id<MTLFunction>> m_hw_vs[1 << 3];
std::unordered_map<PSSelector, MRCOwned<id<MTLFunction>>> m_hw_ps;
std::unordered_map<PipelineSelectorMTL, MRCOwned<id<MTLRenderPipelineState>>> m_hw_pipeline;
MRCOwned<MTLRenderPassDescriptor*> m_render_pass_desc[8];
MRCOwned<id<MTLSamplerState>> m_sampler_hw[1 << 8];
MRCOwned<id<MTLDepthStencilState>> m_dss_stencil_zero;
MRCOwned<id<MTLDepthStencilState>> m_dss_stencil_write;
MRCOwned<id<MTLDepthStencilState>> m_dss_hw[1 << 5];
MRCOwned<id<MTLBuffer>> m_texture_download_buf;
UploadBuffer m_texture_upload_buf;
BufferPair m_vertex_upload_buf;
// MARK: Ephemeral resources
MRCOwned<id<MTLCommandBuffer>> m_current_render_cmdbuf;
struct MainRenderEncoder
{
MRCOwned<id<MTLRenderCommandEncoder>> encoder;
GSTexture* color_target = nullptr;
GSTexture* depth_target = nullptr;
GSTexture* stencil_target = nullptr;
GSTexture* tex[8] = {};
void* vertex_buffer = nullptr;
void* name = nullptr;
struct Has
{
bool cb_vs : 1;
bool cb_ps : 1;
bool scissor : 1;
bool blend_color : 1;
bool pipeline_sel : 1;
bool sampler : 1;
} has;
DepthStencilSelector depth_sel = DepthStencilSelector::NoDepth();
// Clear line (Things below here are tracked by `has` and don't need to be cleared to reset)
SamplerSelector sampler_sel;
u8 blend_color;
GSVector4i scissor;
PipelineSelectorMTL pipeline_sel;
GSHWDrawConfig::VSConstantBuffer cb_vs;
GSHWDrawConfig::PSConstantBuffer cb_ps;
MainRenderEncoder(const MainRenderEncoder&) = delete;
MainRenderEncoder() = default;
} m_current_render;
MRCOwned<id<MTLCommandBuffer>> m_texture_upload_cmdbuf;
MRCOwned<id<MTLBlitCommandEncoder>> m_texture_upload_encoder;
MRCOwned<id<MTLBlitCommandEncoder>> m_late_texture_upload_encoder;
MRCOwned<id<MTLCommandBuffer>> m_vertex_upload_cmdbuf;
MRCOwned<id<MTLBlitCommandEncoder>> m_vertex_upload_encoder;
struct DebugEntry
{
enum Op { Push, Insert, Pop } op;
MRCOwned<NSString*> str;
DebugEntry(Op op, MRCOwned<NSString*> str): op(op), str(std::move(str)) {}
};
std::vector<DebugEntry> m_debug_entries;
u32 m_debug_group_level = 0;
GSDeviceMTL();
~GSDeviceMTL() override;
/// Allocate space in the given buffer
Map Allocate(UploadBuffer& buffer, size_t amt);
/// Allocate space in the given buffer for use with the given render command encoder
Map Allocate(BufferPair& buffer, size_t amt);
/// Enqueue upload of any outstanding data
void Sync(BufferPair& buffer);
/// Get the texture upload encoder, creating a new one if it doesn't exist
id<MTLBlitCommandEncoder> GetTextureUploadEncoder();
/// Get the late texture upload encoder, creating a new one if it doesn't exist
id<MTLBlitCommandEncoder> GetLateTextureUploadEncoder();
/// Get the vertex upload encoder, creating a new one if it doesn't exist
id<MTLBlitCommandEncoder> GetVertexUploadEncoder();
/// Get the render command buffer, creating a new one if it doesn't exist
id<MTLCommandBuffer> GetRenderCmdBuf();
/// Flush pending operations from all encoders to the GPU
void FlushEncoders();
/// End current render pass without flushing
void EndRenderPass();
/// Begin a new render pass (may reuse existing)
void BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil = nullptr, MTLLoadAction stencil_load = MTLLoadActionDontCare);
GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override;
void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c) override;
void DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset) override;
void DoFXAA(GSTexture* sTex, GSTexture* dTex) override;
void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) override;
void DoExternalFX(GSTexture* sTex, GSTexture* dTex) override;
MRCOwned<id<MTLFunction>> LoadShader(NSString* name);
MRCOwned<id<MTLRenderPipelineState>> MakePipeline(MTLRenderPipelineDescriptor* desc, id<MTLFunction> vertex, id<MTLFunction> fragment, NSString* name);
bool Create(HostDisplay* display) override;
void ClearRenderTarget(GSTexture* t, const GSVector4& c) override;
void ClearRenderTarget(GSTexture* t, u32 c) override;
void ClearDepth(GSTexture* t) override;
void ClearStencil(GSTexture* t, u8 c) override;
bool DownloadTexture(GSTexture* src, const GSVector4i& rect, GSTexture::GSMap& out_map) override;
void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r) override;
void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, id<MTLRenderPipelineState> pipeline, bool linear, LoadAction load_action, void* frag_uniform, size_t frag_uniform_len);
void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds);
/// Copy from a position in sTex to the same position in the currently active render encoder using the given fs pipeline and rect
void RenderCopy(GSTexture* sTex, id<MTLRenderPipelineState> pipeline, const GSVector4i& rect);
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override;
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) override;
void FlushClears(GSTexture* tex);
// MARK: Main Render Encoder operations
void MRESetHWPipelineState(GSHWDrawConfig::VSSelector vs, GSHWDrawConfig::PSSelector ps, GSHWDrawConfig::BlendState blend, GSHWDrawConfig::ColorMaskSelector cms);
void MRESetDSS(DepthStencilSelector sel);
void MRESetDSS(id<MTLDepthStencilState> dss);
void MRESetSampler(SamplerSelector sel);
void MRESetTexture(GSTexture* tex, int pos);
void MRESetVertices(id<MTLBuffer> buffer, size_t offset);
void MRESetScissor(const GSVector4i& scissor);
void MREClearScissor();
void MRESetCB(const GSHWDrawConfig::VSConstantBuffer& cb_vs);
void MRESetCB(const GSHWDrawConfig::PSConstantBuffer& cb_ps);
void MRESetBlendColor(u8 blend_color);
void MRESetPipeline(id<MTLRenderPipelineState> pipe);
void MREInitHWDraw(GSHWDrawConfig& config, const Map& verts);
// MARK: Render HW
void SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVector4i& r, bool datm);
void RenderHW(GSHWDrawConfig& config) override;
void SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder> enc, id<MTLBuffer> buffer, size_t off);
// MARK: Debug
void PushDebugGroup(const char* fmt, ...) override;
void PopDebugGroup() override;
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
void ProcessDebugEntry(id<MTLCommandEncoder> enc, const DebugEntry& entry);
void FlushDebugEntries(id<MTLCommandEncoder> enc);
void EndDebugGroup(id<MTLCommandEncoder> enc);
// MARK: ImGui
void RenderImGui(ImDrawData* data);
u32 FrameNo() const { return m_frame; }
};
#endif // __APPLE__
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,66 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef __OBJC__
#error "This header is for use with Objective-C++ only.
#endif
#ifdef __APPLE__
#include "PCSX2Base.h"
#include "common/MRCHelpers.h"
#include <Metal/Metal.h>
struct GSMTLDevice
{
enum class MetalVersion : u8
{
Metal20, ///< Metal 2.0 (macOS 10.13, iOS 11)
Metal21, ///< Metal 2.1 (macOS 10.14, iOS 12)
Metal22, ///< Metal 2.2 (macOS 10.15, iOS 13)
Metal23, ///< Metal 2.3 (macOS 11, iOS 14)
};
struct Features
{
bool unified_memory;
bool texture_swizzle;
bool framebuffer_fetch;
bool primid;
bool slow_color_compression; ///< Color compression seems to slow down rt read on AMD
MetalVersion shader_version;
int max_texsize;
};
MRCOwned<id<MTLDevice>> dev;
MRCOwned<id<MTLLibrary>> shaders;
Features features;
GSMTLDevice() = default;
explicit GSMTLDevice(MRCOwned<id<MTLDevice>> dev);
bool IsOk() const { return dev && shaders; }
void Reset()
{
dev = nullptr;
shaders = nullptr;
}
};
const char* to_string(GSMTLDevice::MetalVersion ver);
#endif // __APPLE__
+214
View File
@@ -0,0 +1,214 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "GSMTLDeviceInfo.h"
#include "GS/GS.h"
#include "common/Console.h"
#ifdef __APPLE__
static id<MTLLibrary> loadMainLibrary(id<MTLDevice> dev, NSString* name)
{
NSString* path = [[NSBundle mainBundle] pathForResource:name ofType:@"metallib"];
return path ? [dev newLibraryWithFile:path error:nullptr] : nullptr;
}
static MRCOwned<id<MTLLibrary>> loadMainLibrary(id<MTLDevice> dev)
{
if (@available(macOS 11.0, iOS 14.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal23"))
return MRCTransfer(lib);
if (@available(macOS 10.15, iOS 13.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal22"))
return MRCTransfer(lib);
if (@available(macOS 10.14, iOS 12.0, *))
if (id<MTLLibrary> lib = loadMainLibrary(dev, @"Metal21"))
return MRCTransfer(lib);
return MRCTransfer([dev newDefaultLibrary]);
}
static GSMTLDevice::MetalVersion detectLibraryVersion(id<MTLLibrary> lib)
{
// These functions are defined in tfx.metal to indicate the metal version used to make the metallib
if (MRCTransfer([lib newFunctionWithName:@"metal_version_23"]))
return GSMTLDevice::MetalVersion::Metal23;
if (MRCTransfer([lib newFunctionWithName:@"metal_version_22"]))
return GSMTLDevice::MetalVersion::Metal22;
if (MRCTransfer([lib newFunctionWithName:@"metal_version_21"]))
return GSMTLDevice::MetalVersion::Metal21;
return GSMTLDevice::MetalVersion::Metal20;
}
static bool detectPrimIDSupport(id<MTLDevice> dev, id<MTLLibrary> lib)
{
// Nvidia Metal driver is missing primid support, yay
MRCOwned<MTLRenderPipelineDescriptor*> desc = MRCTransfer([MTLRenderPipelineDescriptor new]);
[desc setVertexFunction:MRCTransfer([lib newFunctionWithName:@"fs_triangle"])];
[desc setFragmentFunction:MRCTransfer([lib newFunctionWithName:@"primid_test"])];
[[desc colorAttachments][0] setPixelFormat:MTLPixelFormatR8Uint];
NSError* err;
[[dev newRenderPipelineStateWithDescriptor:desc error:&err] release];
return !err;
}
namespace
{
enum class DetectionResult
{
HaswellOrNotIntel, ///< Everything works fine
Broadwell, ///< PrimID broken
Skylake, ///< PrimID broken, FBFetch supported
};
}
static DetectionResult detectIntelGPU(id<MTLDevice> dev, id<MTLLibrary> lib)
{
// Even though it's nowhere in the feature set tables, some Intel GPUs support fbfetch!
// Annoyingly, the Haswell compiler successfully makes a pipeline but actually miscompiles it and doesn't insert any fbfetch instructions
// The Broadwell compiler inserts the Skylake fbfetch instruction, but Broadwell doesn't support that. It seems to make the shader not do anything
// So we actually have to test the thing
// In addition, Broadwell+ has broken primid so we need to disable that.
// Conveniently we can use the same test to detect both (except on macOS < 11. All Broadwell machines support 11, so the answer to that is "upgrade")
// See https://github.com/tellowkrinkle/MetalBugReproduction/releases/tag/BrokenPrimID for details
// AMD compiler crashes and gets retried 3 times over multiple seconds trying to compile the pipeline
// We know this is only a possibility on Intel anyways
if (![[dev name] containsString:@"Intel"])
return DetectionResult::HaswellOrNotIntel;
auto pdesc = MRCTransfer([MTLRenderPipelineDescriptor new]);
[pdesc setVertexFunction:MRCTransfer([lib newFunctionWithName:@"fs_triangle"])];
[pdesc setFragmentFunction:MRCTransfer([lib newFunctionWithName:@"fbfetch_test"])];
[[pdesc colorAttachments][0] setPixelFormat:MTLPixelFormatRGBA8Unorm];
auto pipe = MRCTransfer([dev newRenderPipelineStateWithDescriptor:pdesc error:nil]);
if (!pipe)
return DetectionResult::HaswellOrNotIntel;
auto buf = MRCTransfer([dev newBufferWithLength:4 options:MTLResourceStorageModeShared]);
auto tdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:1 height:1 mipmapped:false];
[tdesc setUsage:MTLTextureUsageRenderTarget];
auto tex = MRCTransfer([dev newTextureWithDescriptor:tdesc]);
auto q = MRCTransfer([dev newCommandQueue]);
u32 px = 0x11223344;
memcpy([buf contents], &px, 4);
id<MTLCommandBuffer> cmdbuf = [q commandBuffer];
id<MTLBlitCommandEncoder> upload = [cmdbuf blitCommandEncoder];
[upload copyFromBuffer:buf sourceOffset:0 sourceBytesPerRow:4 sourceBytesPerImage:4 sourceSize:MTLSizeMake(1, 1, 1) toTexture:tex destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(0, 0, 0)];
[upload endEncoding];
auto rpdesc = MRCTransfer([MTLRenderPassDescriptor new]);
[[rpdesc colorAttachments][0] setTexture:tex];
[[rpdesc colorAttachments][0] setLoadAction:MTLLoadActionLoad];
[[rpdesc colorAttachments][0] setStoreAction:MTLStoreActionStore];
id<MTLRenderCommandEncoder> renc = [cmdbuf renderCommandEncoderWithDescriptor:rpdesc];
[renc setRenderPipelineState:pipe];
[renc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
[renc endEncoding];
id<MTLBlitCommandEncoder> download = [cmdbuf blitCommandEncoder];
[download copyFromTexture:tex sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(0, 0, 0) sourceSize:MTLSizeMake(1, 1, 1) toBuffer:buf destinationOffset:0 destinationBytesPerRow:4 destinationBytesPerImage:4];
[download endEncoding];
[cmdbuf commit];
[cmdbuf waitUntilCompleted];
u32 outpx;
memcpy(&outpx, [buf contents], 4);
// Proper fbfetch will double contents, Haswell will return black, and Broadwell will do nothing
if (outpx == 0x22446688)
return DetectionResult::Skylake;
else if (outpx == 0x11223344)
return DetectionResult::Broadwell;
else
return DetectionResult::HaswellOrNotIntel;
}
GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
{
if (!dev)
return;
shaders = loadMainLibrary(dev);
memset(&features, 0, sizeof(features));
if (char* env = getenv("MTL_UNIFIED_MEMORY"))
features.unified_memory = env[0] == '1' || env[0] == 'y' || env[0] == 'Y';
else if (@available(macOS 10.15, iOS 13.0, *))
features.unified_memory = [dev hasUnifiedMemory];
else
features.unified_memory = false;
if (@available(macOS 10.15, iOS 13.0, *))
if ([dev supportsFamily:MTLGPUFamilyMac2] || [dev supportsFamily:MTLGPUFamilyApple1])
features.texture_swizzle = true;
if (@available(macOS 11.0, iOS 13.0, *))
if ([dev supportsFamily:MTLGPUFamilyApple1])
features.framebuffer_fetch = true;
features.shader_version = detectLibraryVersion(shaders);
if (features.framebuffer_fetch && features.shader_version < MetalVersion::Metal23)
{
Console.Warning("Metal: GPU supports framebuffer fetch but shader lib does not! Get an updated shader lib for better performance!");
features.framebuffer_fetch = false;
}
features.primid = features.shader_version >= MetalVersion::Metal22;
if (features.primid && !detectPrimIDSupport(dev, shaders))
features.primid = false;
if (!features.framebuffer_fetch && features.shader_version >= MetalVersion::Metal23)
{
switch (detectIntelGPU(dev, shaders))
{
case DetectionResult::HaswellOrNotIntel:
break;
case DetectionResult::Broadwell:
features.primid = false; // Broken
break;
case DetectionResult::Skylake:
features.primid = false; // Broken
features.framebuffer_fetch = true;
break;
}
}
if (features.framebuffer_fetch && GSConfig.DisableFramebufferFetch)
{
Console.Warning("Framebuffer fetch was found but is disabled. This will reduce performance.");
features.framebuffer_fetch = false;
}
if (char* env = getenv("MTL_SLOW_COLOR_COMPRESSION"))
features.slow_color_compression = env[0] == '1' || env[0] == 'y' || env[0] == 'Y';
else
features.slow_color_compression = [[dev name] containsString:@"AMD"];
features.max_texsize = 8192;
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
features.max_texsize = 16384;
if (@available(macOS 10.15, iOS 13.0, *))
if ([dev supportsFamily:MTLGPUFamilyApple3])
features.max_texsize = 16384;
this->dev = std::move(dev);
}
const char* to_string(GSMTLDevice::MetalVersion ver)
{
switch (ver)
{
case GSMTLDevice::MetalVersion::Metal20: return "Metal 2.0";
case GSMTLDevice::MetalVersion::Metal21: return "Metal 2.1";
case GSMTLDevice::MetalVersion::Metal22: return "Metal 2.2";
case GSMTLDevice::MetalVersion::Metal23: return "Metal 2.3";
}
}
#endif // __APPLE__
@@ -0,0 +1,60 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <metal_stdlib>
#include "GSMTLSharedHeader.h"
using namespace metal;
constant uchar2 SCALING_FACTOR [[function_constant(GSMTLConstantIndex_SCALING_FACTOR)]];
struct ConvertShaderData
{
float4 p [[position]];
float2 t;
};
struct ConvertPSRes
{
texture2d<float> texture [[texture(GSMTLTextureIndexNonHW)]];
sampler s [[sampler(0)]];
float4 sample(float2 coord)
{
return texture.sample(s, coord);
}
};
struct ConvertPSDepthRes
{
depth2d<float> texture [[texture(GSMTLTextureIndexNonHW)]];
sampler s [[sampler(0)]];
float sample(float2 coord)
{
return texture.sample(s, coord);
}
};
static inline float4 convert_depth32_rgba8(float value)
{
uint val = uint(value * 0x1p32);
return float4(as_type<uchar4>(val));
}
static inline float4 convert_depth16_rgba8(float value)
{
uint val = uint(value * 0x1p32);
return float4(uint4(val << 3, val >> 2, val >> 7, val >> 8) & uint4(0xf8, 0xf8, 0xf8, 0x80));
}
@@ -0,0 +1,151 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <simd/simd.h>
enum GSMTLBufferIndices
{
GSMTLBufferIndexVertices,
GSMTLBufferIndexUniforms,
GSMTLBufferIndexHWVertices,
GSMTLBufferIndexHWUniforms,
};
enum GSMTLTextureIndex
{
GSMTLTextureIndexNonHW,
GSMTLTextureIndexTex,
GSMTLTextureIndexPalette,
GSMTLTextureIndexRenderTarget,
GSMTLTextureIndexPrimIDs,
};
struct GSMTLConvertPSUniform
{
int emoda;
int emodc;
};
struct GSMTLInterlacePSUniform
{
vector_float2 ZrH;
float hH;
};
struct GSMTLMainVSUniform
{
vector_float2 vertex_scale;
vector_float2 vertex_offset;
vector_float2 texture_scale;
vector_float2 texture_offset;
vector_float2 point_size;
uint max_depth;
};
struct GSMTLMainPSUniform
{
union
{
vector_float4 fog_color_aref;
vector_float3 fog_color;
struct
{
float pad0[3];
float aref;
};
};
vector_float4 wh; ///< xy => PS2, zw => actual (upscaled)
vector_float2 ta;
float max_depth;
float alpha_fix;
vector_uint4 uv_msk_fix;
vector_uint4 fbmask;
vector_float4 half_texel;
vector_float4 uv_min_max;
struct
{
unsigned int blue_mask;
unsigned int blue_shift;
unsigned int green_mask;
unsigned int green_shift;
} channel_shuffle;
vector_float2 tc_offset;
vector_float2 st_scale;
matrix_float4x4 dither_matrix;
};
enum GSMTLAttributes
{
GSMTLAttributeIndexST,
GSMTLAttributeIndexC,
GSMTLAttributeIndexQ,
GSMTLAttributeIndexXY,
GSMTLAttributeIndexZ,
GSMTLAttributeIndexUV,
GSMTLAttributeIndexF,
};
enum GSMTLFnConstants
{
GSMTLConstantIndex_SCALING_FACTOR,
GSMTLConstantIndex_FRAMEBUFFER_FETCH,
GSMTLConstantIndex_FST,
GSMTLConstantIndex_IIP,
GSMTLConstantIndex_VS_POINT_SIZE,
GSMTLConstantIndex_PS_AEM_FMT,
GSMTLConstantIndex_PS_PAL_FMT,
GSMTLConstantIndex_PS_DFMT,
GSMTLConstantIndex_PS_DEPTH_FMT,
GSMTLConstantIndex_PS_AEM,
GSMTLConstantIndex_PS_FBA,
GSMTLConstantIndex_PS_FOG,
GSMTLConstantIndex_PS_DATE,
GSMTLConstantIndex_PS_ATST,
GSMTLConstantIndex_PS_TFX,
GSMTLConstantIndex_PS_TCC,
GSMTLConstantIndex_PS_WMS,
GSMTLConstantIndex_PS_WMT,
GSMTLConstantIndex_PS_LTF,
GSMTLConstantIndex_PS_SHUFFLE,
GSMTLConstantIndex_PS_READ_BA,
GSMTLConstantIndex_PS_WRITE_RG,
GSMTLConstantIndex_PS_FBMASK,
GSMTLConstantIndex_PS_BLEND_A,
GSMTLConstantIndex_PS_BLEND_B,
GSMTLConstantIndex_PS_BLEND_C,
GSMTLConstantIndex_PS_BLEND_D,
GSMTLConstantIndex_PS_CLR_HW,
GSMTLConstantIndex_PS_HDR,
GSMTLConstantIndex_PS_COLCLIP,
GSMTLConstantIndex_PS_BLEND_MIX,
GSMTLConstantIndex_PS_PABE,
GSMTLConstantIndex_PS_NO_COLOR,
GSMTLConstantIndex_PS_NO_COLOR1,
GSMTLConstantIndex_PS_ONLY_ALPHA,
GSMTLConstantIndex_PS_CHANNEL,
GSMTLConstantIndex_PS_DITHER,
GSMTLConstantIndex_PS_ZCLAMP,
GSMTLConstantIndex_PS_TCOFFSETHACK,
GSMTLConstantIndex_PS_URBAN_CHAOS_HLE,
GSMTLConstantIndex_PS_TALES_OF_ABYSS_HLE,
GSMTLConstantIndex_PS_TEX_IS_FB,
GSMTLConstantIndex_PS_AUTOMATIC_LOD,
GSMTLConstantIndex_PS_MANUAL_LOD,
GSMTLConstantIndex_PS_POINT_SAMPLER,
GSMTLConstantIndex_PS_INVALID_TEX0,
GSMTLConstantIndex_PS_SCANMSK,
};
@@ -0,0 +1,28 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// Header with all metal stuff available for use with C++ (rather than Objective-C++)
#ifdef __APPLE__
#include "HostDisplay.h"
class GSDevice;
GSDevice* MakeGSDeviceMTL();
HostDisplay* MakeMetalHostDisplay();
HostDisplay::AdapterAndModeList GetMetalAdapterAndModeList();
#endif
+82
View File
@@ -0,0 +1,82 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "GS/Renderers/Common/GSTexture.h"
#ifndef __OBJC__
#error "This header is for use with Objective-C++ only.
#endif
#ifdef __APPLE__
#include "common/MRCHelpers.h"
#include <Metal/Metal.h>
class GSDeviceMTL;
class GSTextureMTL : public GSTexture
{
GSDeviceMTL* m_dev;
MRCOwned<id<MTLTexture>> m_texture;
bool m_has_mipmaps = false;
// In Metal clears happen as a part of render passes instead of as separate steps, but the GSDevice API has it as a separate step
// To deal with that, store the fact that a clear was requested here and it'll be applied on the next render pass
bool m_needs_color_clear = false;
bool m_needs_depth_clear = false;
bool m_needs_stencil_clear = false;
GSVector4 m_clear_color;
float m_clear_depth;
int m_clear_stencil;
public:
u64 m_last_read = 0; ///< Last time this texture was read by a draw
u64 m_last_write = 0; ///< Last time this texture was written by a draw
GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, Type type, Format format);
~GSTextureMTL();
/// For making fake backbuffers
void SetSize(GSVector2i size) { m_size = size; }
/// Requests the texture be cleared the next time a color render is done
void RequestColorClear(GSVector4 color);
/// Requests the texture be cleared the next time a depth render is done
void RequestDepthClear(float depth);
/// Requests the texture be cleared the next time a stencil render is done
void RequestStencilClear(int stencil);
/// Reads whether a color clear was requested, then clears the request
bool GetResetNeedsColorClear(GSVector4& colorOut);
/// Reads whether a depth clear was requested, then clears the request
bool GetResetNeedsDepthClear(float& depthOut);
/// Reads whether a stencil clear was requested, then clears the request
bool GetResetNeedsStencilClear(int& stencilOut);
/// Flushes requested clears to the texture
void FlushClears();
/// Marks pending clears as done (e.g. if the whole texture is about to be overwritten)
void InvalidateClears();
void* GetNativeHandle() const override;
bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) override;
bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override;
void* MapWithPitch(const GSVector4i& r, int pitch, int layer);
void Unmap() override;
void GenerateMipmap() override;
bool Save(const std::string& fn) override;
void Swap(GSTexture* tex) override;
id<MTLTexture> GetTexture() { return m_texture; }
};
#endif
+216
View File
@@ -0,0 +1,216 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "GSTextureMTL.h"
#include "GSDeviceMTL.h"
#include "GS/GSPerfMon.h"
#ifdef __APPLE__
GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, Type type, Format format)
: m_dev(dev)
, m_texture(std::move(texture))
{
m_type = type;
m_format = format;
m_size.x = [m_texture width];
m_size.y = [m_texture height];
m_mipmap_levels = [m_texture mipmapLevelCount];
}
GSTextureMTL::~GSTextureMTL()
{
}
void GSTextureMTL::RequestColorClear(GSVector4 color)
{
m_needs_color_clear = true;
m_clear_color = color;
}
void GSTextureMTL::RequestDepthClear(float depth)
{
m_needs_depth_clear = true;
m_clear_depth = depth;
}
void GSTextureMTL::RequestStencilClear(int stencil)
{
m_needs_stencil_clear = true;
m_clear_stencil = stencil;
}
bool GSTextureMTL::GetResetNeedsColorClear(GSVector4& colorOut)
{
if (m_needs_color_clear)
{
m_needs_color_clear = false;
colorOut = m_clear_color;
return true;
}
return false;
}
bool GSTextureMTL::GetResetNeedsDepthClear(float& depthOut)
{
if (m_needs_depth_clear)
{
m_needs_depth_clear = false;
depthOut = m_clear_depth;
return true;
}
return false;
}
bool GSTextureMTL::GetResetNeedsStencilClear(int& stencilOut)
{
if (m_needs_stencil_clear)
{
m_needs_stencil_clear = false;
stencilOut = m_clear_stencil;
return true;
}
return false;
}
void GSTextureMTL::FlushClears()
{
if (!m_needs_color_clear && !m_needs_depth_clear && !m_needs_stencil_clear)
return;
m_dev->BeginRenderPass(@"Clear",
m_needs_color_clear ? this : nullptr, MTLLoadActionLoad,
m_needs_depth_clear ? this : nullptr, MTLLoadActionLoad,
m_needs_stencil_clear ? this : nullptr, MTLLoadActionLoad);
}
void* GSTextureMTL::GetNativeHandle() const
{
return (__bridge void*)m_texture;
}
void GSTextureMTL::InvalidateClears()
{
m_needs_color_clear = false;
m_needs_depth_clear = false;
m_needs_stencil_clear = false;
}
bool GSTextureMTL::Update(const GSVector4i& r, const void* data, int pitch, int layer)
{
if (void* buffer = MapWithPitch(r, pitch, layer))
{
memcpy(buffer, data, CalcUploadSize(r.height(), pitch));
return true;
}
return false;
}
bool GSTextureMTL::Map(GSMap& m, const GSVector4i* _r, int layer)
{
GSVector4i r = _r ? *_r : GSVector4i(0, 0, m_size.x, m_size.y);
u32 block_size = GetCompressedBlockSize();
u32 blocks_wide = (r.width() + block_size - 1) / block_size;
m.pitch = blocks_wide * GetCompressedBytesPerBlock();
if (void* buffer = MapWithPitch(r, m.pitch, layer))
{
m.bits = static_cast<u8*>(buffer);
return true;
}
return false;
}
void* GSTextureMTL::MapWithPitch(const GSVector4i& r, int pitch, int layer)
{ @autoreleasepool {
if (layer >= m_mipmap_levels)
return nullptr;
m_has_mipmaps = false;
size_t size = CalcUploadSize(r.height(), pitch);
GSDeviceMTL::Map map;
bool needs_clear = false;
if (m_needs_color_clear)
{
m_needs_color_clear = false;
// Not uploading to full texture
needs_clear = r.left > 0 || r.top > 0 || r.right < m_size.x || r.bottom < m_size.y;
}
id<MTLBlitCommandEncoder> enc;
if (m_last_read == m_dev->m_current_draw || needs_clear)
{
if (needs_clear)
{
m_needs_color_clear = true;
m_dev->BeginRenderPass(@"Pre-Upload Clear", this, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare);
}
enc = m_dev->GetLateTextureUploadEncoder();
map = m_dev->Allocate(m_dev->m_vertex_upload_buf, size);
}
else
{
enc = m_dev->GetTextureUploadEncoder();
map = m_dev->Allocate(m_dev->m_texture_upload_buf, size);
}
// Copy is scheduled now, won't happen until the encoder is committed so no problems with ordering
[enc copyFromBuffer:map.gpu_buffer
sourceOffset:map.gpu_offset
sourceBytesPerRow:pitch
sourceBytesPerImage:size
sourceSize:MTLSizeMake(r.width(), r.height(), 1)
toTexture:m_texture
destinationSlice:0
destinationLevel:layer
destinationOrigin:MTLOriginMake(r.x, r.y, 0)];
g_perfmon.Put(GSPerfMon::TextureUploads, 1);
return map.cpu_buffer;
}}
void GSTextureMTL::Unmap()
{
// Nothing to do here, upload is already scheduled
}
void GSTextureMTL::GenerateMipmap()
{ @autoreleasepool {
if (m_mipmap_levels > 1 && !m_has_mipmaps)
{
id<MTLBlitCommandEncoder> enc = m_dev->GetTextureUploadEncoder();
[enc generateMipmapsForTexture:m_texture];
}
}}
bool GSTextureMTL::Save(const std::string& fn)
{
// TODO: Implement
return false;
}
void GSTextureMTL::Swap(GSTexture* other)
{
GSTexture::Swap(other);
GSTextureMTL* mtex = static_cast<GSTextureMTL*>(other);
pxAssert(m_dev == mtex->m_dev);
#define SWAP(x) std::swap(x, mtex->x)
SWAP(m_texture);
SWAP(m_has_mipmaps);
SWAP(m_needs_color_clear);
SWAP(m_needs_depth_clear);
SWAP(m_needs_stencil_clear);
SWAP(m_clear_color);
SWAP(m_clear_depth);
SWAP(m_clear_stencil);
#undef SWAP
}
#endif
+378
View File
@@ -0,0 +1,378 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "GSMTLShaderCommon.h"
using namespace metal;
struct ConvertVSIn
{
vector_float2 position [[attribute(0)]];
vector_float2 texcoord0 [[attribute(1)]];
};
struct ImGuiVSIn
{
vector_float2 position [[attribute(0)]];
vector_float2 texcoord0 [[attribute(1)]];
vector_half4 color [[attribute(2)]];
};
struct ImGuiShaderData
{
float4 p [[position]];
float2 t;
half4 c;
};
template <typename Format>
struct DirectReadTextureIn
{
texture2d<Format> tex [[texture(GSMTLTextureIndexNonHW)]];
vec<Format, 4> read(float4 pos)
{
return tex.read(uint2(pos.xy));
}
};
vertex ConvertShaderData fs_triangle(uint vid [[vertex_id]])
{
ConvertShaderData out;
out.p = float4(vid & 1 ? 3 : -1, vid & 2 ? 3 : -1, 0, 1);
out.t = float2(vid & 1 ? 2 : 0, vid & 2 ? -1 : 1);
return out;
}
vertex ConvertShaderData vs_convert(ConvertVSIn in [[stage_in]])
{
ConvertShaderData out;
out.p = float4(in.position, 0, 1);
out.t = in.texcoord0;
return out;
}
vertex ImGuiShaderData vs_imgui(ImGuiVSIn in [[stage_in]], constant float4& cb [[buffer(GSMTLBufferIndexUniforms)]])
{
ImGuiShaderData out;
out.p = float4(in.position * cb.xy + cb.zw, 0, 1);
out.t = in.texcoord0;
out.c = in.color;
return out;
}
float4 ps_crt(float4 color, int i)
{
constexpr float4 mask[4] =
{
float4(1, 0, 0, 0),
float4(0, 1, 0, 0),
float4(0, 0, 1, 0),
float4(1, 1, 1, 0),
};
return color * saturate(mask[i] + 0.5f);
}
float4 ps_scanlines(float4 color, int i)
{
constexpr float4 mask[2] =
{
float4(1, 1, 1, 0),
float4(0, 0, 0, 0)
};
return color * saturate(mask[i] + 0.5f);
}
fragment float4 ps_copy(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
return res.sample(data.t);
}
fragment ushort ps_convert_rgba8_16bits(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
float4 c = res.sample(data.t);
uint4 cu = uint4(c * 255.f + 0.5f);
return (cu.x >> 3) | ((cu.y << 2) & 0x03e0) | ((cu.z << 7) & 0x7c00) | ((cu.w << 8) & 0x8000);
}
fragment float4 ps_copy_fs(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p);
}
fragment void ps_datm1(float4 p [[position]], DirectReadTextureIn<float> tex)
{
if (tex.read(p).a < (127.5f / 255.f))
discard_fragment();
}
fragment void ps_datm0(float4 p [[position]], DirectReadTextureIn<float> tex)
{
if (tex.read(p).a > (127.5f / 255.f))
discard_fragment();
}
fragment float4 ps_primid_init_datm0(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p).a > (127.5f / 255.f) ? -1 : FLT_MAX;
}
fragment float4 ps_primid_init_datm1(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p).a < (127.5f / 255.f) ? -1 : FLT_MAX;
}
fragment float4 ps_mod256(float4 p [[position]], DirectReadTextureIn<float> tex)
{
float4 c = round(tex.read(p) * 255.f);
return (c - 256.f * floor(c / 256.f)) / 255.f;
}
fragment float4 ps_filter_scanlines(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
return ps_scanlines(res.sample(data.t), uint(data.p.y) % 2);
}
fragment float4 ps_filter_diagonal(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
uint4 p = uint4(data.p);
return ps_crt(res.sample(data.t), (p.x + (p.y % 3)) % 3);
}
fragment float4 ps_filter_transparency(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
float4 c = res.sample(data.t);
c.a = dot(c.rgb, float3(0.299f, 0.587f, 0.114f));
return c;
}
fragment float4 ps_filter_triangular(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
uint4 p = uint4(data.p);
uint val = ((p.x + ((p.y >> 1) & 1) * 3) >> 1) % 3;
return ps_crt(res.sample(data.t), val);
}
fragment float4 ps_filter_complex(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
float2 texdim = float2(res.texture.get_width(), res.texture.get_height());
if (dfdy(data.t.y) * texdim.y > 0.5)
{
return res.sample(data.t);
}
else
{
float factor = (0.9f - 0.4f * cos(2.f * M_PI_F * data.t.y * texdim.y));
float ycoord = (floor(data.t.y * texdim.y) + 0.5f) / texdim.y;
return factor * res.sample(float2(data.t.x, ycoord));
}
}
fragment uint ps_convert_float32_32bits(ConvertShaderData data [[stage_in]], ConvertPSDepthRes res)
{
return uint(0x1p32 * res.sample(data.t));
}
fragment float4 ps_convert_float32_rgba8(ConvertShaderData data [[stage_in]], ConvertPSDepthRes res)
{
return convert_depth32_rgba8(res.sample(data.t)) / 255.f;
}
fragment float4 ps_convert_float16_rgb5a1(ConvertShaderData data [[stage_in]], ConvertPSDepthRes res)
{
return convert_depth16_rgba8(res.sample(data.t)) / 255.f;
}
struct DepthOut
{
float depth [[depth(any)]];
DepthOut(float depth): depth(depth) {}
};
fragment DepthOut ps_depth_copy(ConvertShaderData data [[stage_in]], ConvertPSDepthRes res)
{
return res.sample(data.t);
}
static float pack_rgba8_depth(float4 unorm)
{
return float(as_type<uint>(uchar4(unorm * 255.f + 0.5f))) * 0x1p-32f;
}
fragment DepthOut ps_convert_rgba8_float32(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
return pack_rgba8_depth(res.sample(data.t));
}
fragment DepthOut ps_convert_rgba8_float24(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
// Same as above but without the alpha channel (24 bits Z)
return pack_rgba8_depth(float4(res.sample(data.t).rgb, 0));
}
fragment DepthOut ps_convert_rgba8_float16(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
return float(as_type<ushort>(uchar2(res.sample(data.t).rg * 255.f + 0.5f))) * 0x1p-32;
}
fragment DepthOut ps_convert_rgb5a1_float16(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
uint4 cu = uint4(res.sample(data.t) * 255.f + 0.5f);
uint out = (cu.x >> 3) | ((cu.y << 2) & 0x03e0) | ((cu.z << 7) & 0x7c00) | ((cu.w << 8) & 0x8000);
return float(out) * 0x1p-32;
}
fragment float4 ps_convert_rgba_8i(ConvertShaderData data [[stage_in]], ConvertPSRes res,
constant GSMTLConvertPSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{
// Convert a RGBA texture into a 8 bits packed texture
// Input column: 8x2 RGBA pixels
// 0: 8 RGBA
// 1: 8 RGBA
// Output column: 16x4 Index pixels
// 0: 8 R | 8 B
// 1: 8 R | 8 B
// 2: 8 G | 8 A
// 3: 8 G | 8 A
float c;
uint2 sel = uint2(data.p.xy) % uint2(16, 16);
uint2 tb = (uint2(data.p.xy) & ~uint2(15, 3)) >> 1;
uint ty = tb.y | (uint(data.p.y) & 1);
uint txN = tb.x | (uint(data.p.x) & 7);
uint txH = tb.x | ((uint(data.p.x) + 4) & 7);
txN *= SCALING_FACTOR.x;
txH *= SCALING_FACTOR.x;
ty *= SCALING_FACTOR.y;
// TODO investigate texture gather
float4 cN = res.texture.read(uint2(txN, ty));
float4 cH = res.texture.read(uint2(txH, ty));
if ((sel.y & 4) == 0)
{
// Column 0 and 2
if ((sel.y & 2) == 0)
{
if ((sel.x & 8) == 0)
c = cN.r;
else
c = cN.b;
}
else
{
if ((sel.x & 8) == 0)
c = cH.g;
else
c = cH.a;
}
}
else
{
// Column 1 and 3
if ((sel.y & 2) == 0)
{
if ((sel.x & 8) == 0)
c = cH.r;
else
c = cH.b;
}
else
{
if ((sel.x & 8) == 0)
c = cN.g;
else
c = cN.a;
}
}
return float4(c);
}
fragment float4 ps_yuv(ConvertShaderData data [[stage_in]], ConvertPSRes res,
constant GSMTLConvertPSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{
float4 i = res.sample(data.t);
float4 o;
// Value from GS manual
const float3x3 rgb2yuv =
{
{0.587, -0.311, -0.419},
{0.114, 0.500, -0.081},
{0.299, -0.169, 0.500}
};
float3 yuv = rgb2yuv * i.gbr;
float Y = 0xDB / 255.f * yuv.x + 0x10 / 255.f;
float Cr = 0xE0 / 255.f * yuv.y + 0x80 / 255.f;
float Cb = 0xE0 / 255.f * yuv.z + 0x80 / 255.f;
switch (uniform.emoda)
{
case 0: o.a = i.a; break;
case 1: o.a = Y; break;
case 2: o.a = Y/2; break;
case 3: o.a = 0; break;
}
switch (uniform.emodc)
{
case 0: o.rgb = i.rgb; break;
case 1: o.rgb = float3(Y); break;
case 2: o.rgb = float3(Y, Cb, Cr); break;
case 3: o.rgb = float3(i.a); break;
}
return o;
}
fragment half4 ps_imgui(ImGuiShaderData data [[stage_in]], texture2d<half> texture [[texture(GSMTLTextureIndexNonHW)]])
{
constexpr sampler s(coord::normalized, filter::linear, address::clamp_to_edge);
return data.c * texture.sample(s, data.t);
}
fragment half4 ps_imgui_a8(ImGuiShaderData data [[stage_in]], texture2d<half> texture [[texture(GSMTLTextureIndexNonHW)]])
{
constexpr sampler s(coord::normalized, filter::linear, address::clamp_to_edge);
return data.c * half4(1, 1, 1, texture.sample(s, data.t).a);
}
fragment float4 ps_shadeboost(float4 p [[position]], DirectReadTextureIn<float> tex, constant float3& cb [[buffer(GSMTLBufferIndexUniforms)]])
{
const float brt = cb.x;
const float con = cb.y;
const float sat = cb.z;
// Increase or decrease these values to adjust r, g and b color channels separately
const float AvgLumR = 0.5;
const float AvgLumG = 0.5;
const float AvgLumB = 0.5;
const float3 LumCoeff = float3(0.2125, 0.7154, 0.0721);
float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
float3 brtColor = tex.read(p).rgb * brt;
float dot_intensity = dot(brtColor, LumCoeff);
float3 intensity = float3(dot_intensity, dot_intensity, dot_intensity);
float3 satColor = mix(intensity, brtColor, sat);
float3 conColor = mix(AvgLumin, satColor, con);
return float4(conColor, 1);
}
+10
View File
@@ -0,0 +1,10 @@
#include "GSMTLShaderCommon.h"
#include "../../../../bin/resources/shaders/common/fxaa.fx"
fragment float4 ps_fxaa(ConvertShaderData data [[stage_in]], texture2d<float> tex [[texture(GSMTLTextureIndexNonHW)]])
{
float4 color = tex.sample(MAIN_SAMPLER, data.t);
color = PreGammaPass(color);
color = FxaaPass(color, data.t, tex);
return color;
}
+49
View File
@@ -0,0 +1,49 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "GSMTLShaderCommon.h"
using namespace metal;
fragment float4 ps_interlace0(ConvertShaderData data [[stage_in]], ConvertPSRes res,
constant GSMTLInterlacePSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{
if (fract(data.t.y * uniform.hH) - 0.5f < 0.f)
discard_fragment();
return res.sample(data.t);
}
fragment float4 ps_interlace1(ConvertShaderData data [[stage_in]], ConvertPSRes res,
constant GSMTLInterlacePSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{
if (0.5f - fract(data.t.y * uniform.hH) < 0.f)
discard_fragment();
return res.sample(data.t);
}
fragment float4 ps_interlace2(ConvertShaderData data [[stage_in]], ConvertPSRes res,
constant GSMTLInterlacePSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
{
float4 c0 = res.sample(data.t - uniform.ZrH);
float4 c1 = res.sample(data.t);
float4 c2 = res.sample(data.t + uniform.ZrH);
return (c0 + c1 * 2.f + c2) / 4.f;
}
fragment float4 ps_interlace3(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
return res.sample(data.t);
}
+34
View File
@@ -0,0 +1,34 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "GSMTLShaderCommon.h"
using namespace metal;
fragment float4 ps_merge0(ConvertShaderData data [[stage_in]], ConvertPSRes res)
{
float4 c = res.sample(data.t);
c.a *= 2.f;
return c;
}
fragment float4 ps_merge1(ConvertShaderData data [[stage_in]], ConvertPSRes res,
constant vector_float4& BGColor [[buffer(GSMTLBufferIndexUniforms)]])
{
float4 c = res.sample(data.t);
c.a = BGColor.a;
return c;
}
+939
View File
@@ -0,0 +1,939 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "GSMTLShaderCommon.h"
constant uint FMT_32 = 0;
constant uint FMT_24 = 1;
constant uint FMT_16 = 2;
constant bool HAS_FBFETCH [[function_constant(GSMTLConstantIndex_FRAMEBUFFER_FETCH)]];
constant bool FST [[function_constant(GSMTLConstantIndex_FST)]];
constant bool IIP [[function_constant(GSMTLConstantIndex_IIP)]];
constant bool VS_POINT_SIZE [[function_constant(GSMTLConstantIndex_VS_POINT_SIZE)]];
constant uint PS_AEM_FMT [[function_constant(GSMTLConstantIndex_PS_AEM_FMT)]];
constant uint PS_PAL_FMT [[function_constant(GSMTLConstantIndex_PS_PAL_FMT)]];
constant uint PS_DFMT [[function_constant(GSMTLConstantIndex_PS_DFMT)]];
constant uint PS_DEPTH_FMT [[function_constant(GSMTLConstantIndex_PS_DEPTH_FMT)]];
constant bool PS_AEM [[function_constant(GSMTLConstantIndex_PS_AEM)]];
constant bool PS_FBA [[function_constant(GSMTLConstantIndex_PS_FBA)]];
constant bool PS_FOG [[function_constant(GSMTLConstantIndex_PS_FOG)]];
constant uint PS_DATE [[function_constant(GSMTLConstantIndex_PS_DATE)]];
constant uint PS_ATST [[function_constant(GSMTLConstantIndex_PS_ATST)]];
constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TFX)]];
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
constant uint PS_WMT [[function_constant(GSMTLConstantIndex_PS_WMT)]];
constant bool PS_LTF [[function_constant(GSMTLConstantIndex_PS_LTF)]];
constant bool PS_SHUFFLE [[function_constant(GSMTLConstantIndex_PS_SHUFFLE)]];
constant bool PS_READ_BA [[function_constant(GSMTLConstantIndex_PS_READ_BA)]];
constant bool PS_WRITE_RG [[function_constant(GSMTLConstantIndex_PS_WRITE_RG)]];
constant bool PS_FBMASK [[function_constant(GSMTLConstantIndex_PS_FBMASK)]];
constant uint PS_BLEND_A [[function_constant(GSMTLConstantIndex_PS_BLEND_A)]];
constant uint PS_BLEND_B [[function_constant(GSMTLConstantIndex_PS_BLEND_B)]];
constant uint PS_BLEND_C [[function_constant(GSMTLConstantIndex_PS_BLEND_C)]];
constant uint PS_BLEND_D [[function_constant(GSMTLConstantIndex_PS_BLEND_D)]];
constant uint PS_CLR_HW [[function_constant(GSMTLConstantIndex_PS_CLR_HW)]];
constant bool PS_HDR [[function_constant(GSMTLConstantIndex_PS_HDR)]];
constant bool PS_COLCLIP [[function_constant(GSMTLConstantIndex_PS_COLCLIP)]];
constant bool PS_BLEND_MIX [[function_constant(GSMTLConstantIndex_PS_BLEND_MIX)]];
constant bool PS_PABE [[function_constant(GSMTLConstantIndex_PS_PABE)]];
constant bool PS_NO_COLOR [[function_constant(GSMTLConstantIndex_PS_NO_COLOR)]];
constant bool PS_NO_COLOR1 [[function_constant(GSMTLConstantIndex_PS_NO_COLOR1)]];
constant bool PS_ONLY_ALPHA [[function_constant(GSMTLConstantIndex_PS_ONLY_ALPHA)]];
constant uint PS_CHANNEL [[function_constant(GSMTLConstantIndex_PS_CHANNEL)]];
constant uint PS_DITHER [[function_constant(GSMTLConstantIndex_PS_DITHER)]];
constant bool PS_ZCLAMP [[function_constant(GSMTLConstantIndex_PS_ZCLAMP)]];
constant bool PS_TCOFFSETHACK [[function_constant(GSMTLConstantIndex_PS_TCOFFSETHACK)]];
constant bool PS_URBAN_CHAOS_HLE [[function_constant(GSMTLConstantIndex_PS_URBAN_CHAOS_HLE)]];
constant bool PS_TALES_OF_ABYSS_HLE [[function_constant(GSMTLConstantIndex_PS_TALES_OF_ABYSS_HLE)]];
constant bool PS_TEX_IS_FB [[function_constant(GSMTLConstantIndex_PS_TEX_IS_FB)]];
constant bool PS_AUTOMATIC_LOD [[function_constant(GSMTLConstantIndex_PS_AUTOMATIC_LOD)]];
constant bool PS_MANUAL_LOD [[function_constant(GSMTLConstantIndex_PS_MANUAL_LOD)]];
constant bool PS_POINT_SAMPLER [[function_constant(GSMTLConstantIndex_PS_POINT_SAMPLER)]];
constant bool PS_INVALID_TEX0 [[function_constant(GSMTLConstantIndex_PS_INVALID_TEX0)]];
constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SCANMSK)]];
#if defined(__METAL_MACOS__) && __METAL_VERSION__ >= 220
#define PRIMID_SUPPORT 1
#else
#define PRIMID_SUPPORT 0
#endif
#if defined(__METAL_IOS__) || __METAL_VERSION__ >= 230
#define FBFETCH_SUPPORT 1
#else
#define FBFETCH_SUPPORT 0
#endif
constant bool PS_PRIM_CHECKING_INIT = PS_DATE == 1 || PS_DATE == 2;
constant bool PS_PRIM_CHECKING_READ = PS_DATE == 3;
#if PRIMID_SUPPORT
constant bool NEEDS_PRIMID = PS_PRIM_CHECKING_INIT || PS_PRIM_CHECKING_READ;
#endif
constant bool PS_TEX_IS_DEPTH = PS_URBAN_CHAOS_HLE || PS_TALES_OF_ABYSS_HLE || PS_DEPTH_FMT == 1 || PS_DEPTH_FMT == 2;
constant bool PS_TEX_IS_COLOR = !PS_TEX_IS_DEPTH;
constant bool PS_HAS_PALETTE = PS_PAL_FMT != 0 || (PS_CHANNEL >= 1 && PS_CHANNEL <= 5);
constant bool NOT_IIP = !IIP;
constant bool SW_BLEND = (PS_BLEND_A != PS_BLEND_B) || PS_BLEND_D;
constant bool SW_AD_TO_HW = PS_BLEND_C == 1 && PS_CLR_HW > 3;
constant bool NEEDS_RT_FOR_BLEND = (((PS_BLEND_A != PS_BLEND_B) && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1)) || PS_BLEND_D == 1 || SW_AD_TO_HW);
constant bool NEEDS_RT_EARLY = PS_TEX_IS_FB || PS_DATE >= 5;
constant bool NEEDS_RT = NEEDS_RT_EARLY || (!PS_PRIM_CHECKING_INIT && (PS_FBMASK || NEEDS_RT_FOR_BLEND));
constant bool PS_COLOR0 = !PS_NO_COLOR;
constant bool PS_COLOR1 = !PS_NO_COLOR1;
struct MainVSIn
{
float2 st [[attribute(GSMTLAttributeIndexST)]];
float4 c [[attribute(GSMTLAttributeIndexC)]];
float q [[attribute(GSMTLAttributeIndexQ)]];
uint2 p [[attribute(GSMTLAttributeIndexXY)]];
uint z [[attribute(GSMTLAttributeIndexZ)]];
uint2 uv [[attribute(GSMTLAttributeIndexUV)]];
float4 f [[attribute(GSMTLAttributeIndexF)]];
};
struct MainVSOut
{
float4 p [[position]];
float4 t;
float4 ti;
float4 c [[function_constant(IIP)]];
float4 fc [[flat, function_constant(NOT_IIP)]];
float point_size [[point_size, function_constant(VS_POINT_SIZE)]];
};
struct MainPSIn
{
float4 p [[position]];
float4 t;
float4 ti;
float4 c [[function_constant(IIP)]];
float4 fc [[flat, function_constant(NOT_IIP)]];
};
struct MainPSOut
{
float4 c0 [[color(0), index(0), function_constant(PS_COLOR0)]];
float4 c1 [[color(0), index(1), function_constant(PS_COLOR1)]];
float depth [[depth(less), function_constant(PS_ZCLAMP)]];
};
// MARK: - Vertex functions
static void texture_coord(thread const MainVSIn& v, thread MainVSOut& out, constant GSMTLMainVSUniform& cb)
{
float2 uv = float2(v.uv) - cb.texture_offset;
float2 st = v.st - cb.texture_offset;
// Float coordinate
out.t.xy = st;
out.t.w = v.q;
// Integer coordinate => normalized
out.ti.xy = uv * cb.texture_scale;
if (FST)
{
// Integer coordinate => integral
out.ti.zw = uv;
}
else
{
// Some games uses float coordinate for post-processing effects
out.ti.zw = st / cb.texture_scale;
}
}
static MainVSOut vs_main_run(thread const MainVSIn& v, constant GSMTLMainVSUniform& cb)
{
constexpr float exp_min32 = 0x1p-32;
MainVSOut out;
// Clamp to max depth, gs doesn't wrap
uint z = min(v.z, cb.max_depth);
out.p.xy = float2(v.p) - float2(0.05, 0.05);
out.p.xy = out.p.xy * float2(cb.vertex_scale.x, -cb.vertex_scale.y) - float2(cb.vertex_offset.x, -cb.vertex_offset.y);
out.p.w = 1;
out.p.z = float(z) * exp_min32;
texture_coord(v, out, cb);
if (IIP)
out.c = v.c;
else
out.fc = v.c;
out.t.z = v.f.x; // pack fog with texture
if (VS_POINT_SIZE)
out.point_size = SCALING_FACTOR.x;
return out;
}
vertex MainVSOut vs_main(MainVSIn v [[stage_in]], constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
{
return vs_main_run(v, cb);
}
// MARK: - Fragment functions
constexpr sampler palette_sampler(filter::nearest, address::clamp_to_edge);
struct PSMain
{
texture2d<float> tex;
depth2d<float> tex_depth;
texture2d<float> palette;
texture2d<float> prim_id_tex;
sampler tex_sampler;
float4 current_color;
uint prim_id;
const thread MainPSIn& in;
constant GSMTLMainPSUniform& cb;
PSMain(const thread MainPSIn& in, constant GSMTLMainPSUniform& cb): in(in), cb(cb) {}
template <typename... Args>
float4 sample_tex(Args... args)
{
if (PS_TEX_IS_DEPTH)
return float4(tex_depth.sample(args...));
else
return tex.sample(args...);
}
float4 sample_c(float2 uv)
{
if (PS_TEX_IS_FB)
return current_color;
if (PS_POINT_SAMPLER)
{
// Weird issue with ATI/AMD cards,
// it looks like they add 127/128 of a texel to sampling coordinates
// occasionally causing point sampling to erroneously round up.
// I'm manually adjusting coordinates to the centre of texels here,
// though the centre is just paranoia, the top left corner works fine.
// As of 2018 this issue is still present.
uv = (trunc(uv * cb.wh.zw) + 0.5) / cb.wh.zw;
}
uv *= cb.st_scale;
if (PS_AUTOMATIC_LOD)
{
return sample_tex(tex_sampler, uv);
}
else if (PS_MANUAL_LOD)
{
float K = cb.uv_min_max.x;
float L = cb.uv_min_max.y;
float bias = cb.uv_min_max.z;
float max_lod = cb.uv_min_max.w;
float gs_lod = K - log2(abs(in.t.w)) * L;
// FIXME max useful ?
//float lod = max(min(gs_lod, max_lod) - bias, 0.f);
float lod = min(gs_lod, max_lod) - bias;
return sample_tex(tex_sampler, uv, level(lod));
}
else
{
return sample_tex(tex_sampler, uv, level(0));
}
}
float4 sample_p(float idx)
{
return palette.sample(palette_sampler, float2(idx, 0));
}
float4 clamp_wrap_uv(float4 uv)
{
float4 uv_out = uv;
float4 tex_size = PS_INVALID_TEX0 ? cb.wh.zwzw : cb.wh.xyxy;
if (PS_WMS == PS_WMT)
{
if (PS_WMS == 2)
{
uv_out = clamp(uv, cb.uv_min_max.xyxy, cb.uv_min_max.zwzw);
}
else if (PS_WMS == 3)
{
// wrap negative uv coords to avoid an off by one error that shifted
// textures. Fixes Xenosaga's hair issue.
if (!FST)
uv = fract(uv);
uv_out = float4((ushort4(uv * tex_size) & ushort4(cb.uv_msk_fix.xyxy)) | ushort4(cb.uv_msk_fix.zwzw)) / tex_size;
}
}
else
{
if (PS_WMS == 2)
{
uv_out.xz = clamp(uv.xz, cb.uv_min_max.xx, cb.uv_min_max.zz);
}
else if (PS_WMS == 3)
{
if (!FST)
uv.xz = fract(uv.xz);
uv_out.xz = float2((ushort2(uv.xz * tex_size.xx) & ushort2(cb.uv_msk_fix.xx)) | ushort2(cb.uv_msk_fix.zz)) / tex_size.xx;
}
if (PS_WMT == 2)
{
uv_out.yw = clamp(uv.yw, cb.uv_min_max.yy, cb.uv_min_max.ww);
}
else if (PS_WMT == 3)
{
if (!FST)
uv.yw = fract(uv.yw);
uv_out.yw = float2((ushort2(uv.yw * tex_size.yy) & ushort2(cb.uv_msk_fix.yy)) | ushort2(cb.uv_msk_fix.ww)) / tex_size.yy;
}
}
return uv_out;
}
float4x4 sample_4c(float4 uv)
{
return {
sample_c(uv.xy),
sample_c(uv.zy),
sample_c(uv.xw),
sample_c(uv.zw),
};
}
float4 sample_4_index(float4 uv)
{
float4 c;
// Either GS will send a texture that contains a single alpha channel
// Or we have an old RT (ie RGBA8) that contains index (4/8) in the alpha channel
// Note: texture gather can't be used because of special clamping/wrapping
// Also it doesn't support lod
c.x = sample_c(uv.xy).a;
c.y = sample_c(uv.zy).a;
c.z = sample_c(uv.xw).a;
c.w = sample_c(uv.zw).a;
uchar4 i = uchar4(c * 255.5f); // Denormalize value
if (PS_PAL_FMT == 1)
return float4(i & 0xF) / 255.f;
if (PS_PAL_FMT == 2)
return float4(i >> 4) / 255.f;
// Most textures will hit this code so keep normalized float value
return c;
}
float4x4 sample_4p(float4 u)
{
return {
sample_p(u.x),
sample_p(u.y),
sample_p(u.z),
sample_p(u.w),
};
}
uint fetch_raw_depth()
{
return tex_depth.read(ushort2(in.p.xy)) * 0x1p32f;
}
float4 fetch_raw_color()
{
if (PS_TEX_IS_FB)
return current_color;
else
return tex.read(ushort2(in.p.xy));
}
float4 fetch_c(ushort2 uv)
{
return PS_TEX_IS_DEPTH ? tex_depth.read(uv) : tex.read(uv);
}
// MARK: Depth sampling
ushort2 clamp_wrap_uv_depth(ushort2 uv)
{
ushort2 uv_out = uv;
// Keep the full precision
// It allow to multiply the ScalingFactor before the 1/16 coeff
ushort4 mask = ushort4(cb.uv_msk_fix) << 4;
if (PS_WMS == PS_WMT)
{
if (PS_WMS == 2)
uv_out = clamp(uv, mask.xy, mask.zw);
else if (PS_WMS == 3)
uv_out = (uv & mask.xy) | mask.zw;
}
else
{
if (PS_WMS == 2)
uv_out.x = clamp(uv.x, mask.x, mask.z);
else if (PS_WMS == 3)
uv_out.x = (uv.x & mask.x) | mask.z;
if (PS_WMT == 2)
uv_out.y = clamp(uv.y, mask.y, mask.w);
else if (PS_WMT == 3)
uv_out.y = (uv.y & mask.y) | mask.w;
}
return uv_out;
}
float4 sample_depth(float2 st)
{
float2 uv_f = float2(clamp_wrap_uv_depth(ushort2(st))) * (float2(SCALING_FACTOR) * float2(1.f / 16.f));
ushort2 uv = ushort2(uv_f);
float4 t = float4(0);
if (PS_TALES_OF_ABYSS_HLE)
{
// Warning: UV can't be used in channel effect
ushort depth = fetch_raw_depth();
// Convert msb based on the palette
t = palette.read(ushort2((depth >> 8) & 0xFF, 0)) * 255.f;
}
else if (PS_URBAN_CHAOS_HLE)
{
// Depth buffer is read as a RGB5A1 texture. The game try to extract the green channel.
// So it will do a first channel trick to extract lsb, value is right-shifted.
// Then a new channel trick to extract msb which will shifted to the left.
// OpenGL uses a FLOAT32 format for the depth so it requires a couple of conversion.
// To be faster both steps (msb&lsb) are done in a single pass.
// Warning: UV can't be used in channel effect
ushort depth = fetch_raw_depth();
// Convert lsb based on the palette
t = palette.read(ushort2(depth & 0xFF, 0)) * 255.f;
// Msb is easier
float green = float((depth >> 8) & 0xFF) * 36.f;
green = min(green, 255.0f);
t.g += green;
}
else if (PS_DEPTH_FMT == 1)
{
t = convert_depth32_rgba8(fetch_c(uv).r);
}
else if (PS_DEPTH_FMT == 2)
{
t = convert_depth16_rgba8(fetch_c(uv).r);
}
else if (PS_DEPTH_FMT == 3)
{
t = fetch_c(uv) * 255.f;
}
if (PS_AEM_FMT == FMT_24)
t.a = (!PS_AEM || any(bool3(t.rgb))) ? 255.f * cb.ta.x : 0.f;
else if (PS_AEM_FMT == FMT_16)
t.a = t.a >= 128.f ? 255.f * cb.ta.y : (!PS_AEM || any(bool3(t.rgb))) ? 255.f * cb.ta.x : 0.f;
return t;
}
// MARK: Fetch a Single Channel
float4 fetch_red()
{
float rt = PS_TEX_IS_DEPTH ? float(fetch_raw_depth() & 0xFF) / 255.f : fetch_raw_color().r;
return sample_p(rt) * 255.f;
}
float4 fetch_green()
{
float rt = PS_TEX_IS_DEPTH ? float((fetch_raw_depth() >> 8) & 0xFF) / 255.f : fetch_raw_color().g;
return sample_p(rt) * 255.f;
}
float4 fetch_blue()
{
float rt = PS_TEX_IS_DEPTH ? float((fetch_raw_depth() >> 16) & 0xFF) / 255.f : fetch_raw_color().b;
return sample_p(rt) * 255.f;
}
float4 fetch_alpha()
{
return sample_p(fetch_raw_color().a) * 255.f;
}
float4 fetch_rgb()
{
float4 rt = fetch_raw_color();
return float4(sample_p(rt.r).r, sample_p(rt.g).g, sample_p(rt.b).b, 1) * 255.f;
}
float4 fetch_gXbY()
{
if (PS_TEX_IS_DEPTH)
{
uint depth = fetch_raw_depth();
uint bg = (depth >> (8 + cb.channel_shuffle.green_shift)) & 0xFF;
return float4(bg);
}
else
{
uchar4 rt = uchar4(fetch_raw_color() * 255.5f);
uchar green = (rt.g >> cb.channel_shuffle.green_shift) & cb.channel_shuffle.green_mask;
uchar blue = (rt.b >> cb.channel_shuffle.blue_shift) & cb.channel_shuffle.blue_mask;
return float4(green | blue);
}
}
float4 sample_color(float2 st)
{
if (PS_TCOFFSETHACK)
st += cb.tc_offset;
float4 t;
float4x4 c;
float2 dd;
if (!PS_LTF && PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_WMS < 2 && PS_WMT < 2)
{
c[0] = sample_c(st);
}
else
{
float4 uv;
if (PS_LTF)
{
uv = st.xyxy + cb.half_texel;
dd = fract(uv.xy * cb.wh.zw);
if (!FST)
{
// Background in Shin Megami Tensei Lucifers
// I suspect that uv isn't a standard number, so fract is outside of the [0;1] range
dd = saturate(dd);
}
}
else
{
uv = st.xyxy;
}
uv = clamp_wrap_uv(uv);
if (PS_PAL_FMT != 0)
c = sample_4p(sample_4_index(uv));
else
c = sample_4c(uv);
}
for (int i = 0; i < 4; i++)
{
if (PS_AEM_FMT == FMT_24)
c[i].a = !PS_AEM || any(bool3(c[i].rgb)) ? cb.ta.x : 0.f;
else if (PS_AEM_FMT == FMT_16)
c[i].a = c[i].a >= 0.5 ? cb.ta.y : !PS_AEM || any(bool3(c[i].rgb)) ? cb.ta.x : 0.f;
}
if (PS_LTF)
t = mix(mix(c[0], c[1], dd.x), mix(c[2], c[3], dd.x), dd.y);
else
t = c[0];
// The 0.05f helps to fix the overbloom of sotc
// I think the issue is related to the rounding of texture coodinate. The linear (from fixed unit)
// interpolation could be slightly below the correct one.
return trunc(t * 255.f + 0.05f);
}
float4 tfx(float4 T, float4 C)
{
float4 C_out;
float4 FxT = trunc(trunc(C) * T / 128.f);
if (PS_TFX == 0)
C_out = FxT;
else if (PS_TFX == 1)
C_out = T;
else if (PS_TFX == 2)
C_out = float4(FxT.rgb, T.a) + C.a;
else if (PS_TFX == 3)
C_out = float4(FxT.rgb + C.a, T.a);
else
C_out = C;
if (!PS_TCC)
C_out.a = C.a;
// Clamp only when it is useful
if (PS_TFX == 0 || PS_TFX == 2 || PS_TFX == 3)
C_out = min(C_out, 255.f);
return C_out;
}
bool atst(float4 C)
{
float a = C.a;
switch (PS_ATST)
{
case 0:
break; // Nothing to do
case 1:
if (a > cb.aref)
return false;
break;
case 2:
if (a < cb.aref)
return false;
break;
case 3:
if (abs(a - cb.aref) > 0.5f)
return false;
break;
case 4:
if (abs(a - cb.aref) < 0.5f)
return false;
break;
}
return true;
}
void fog(thread float4& C, float f)
{
if (PS_FOG)
C.rgb = trunc(mix(cb.fog_color, C.rgb, f));
}
float4 ps_color()
{
float2 st, st_int;
if (!FST && PS_INVALID_TEX0)
{
st = (in.t.xy * cb.wh.xy) / (in.t.w * cb.wh.zw);
}
else if (!FST)
{
st = in.t.xy / in.t.w;
st_int = in.ti.zw / in.t.w;
}
else
{
// Note: xy are normalized coordinates
st = in.ti.xy;
st_int = in.ti.zw;
}
float4 T;
if (PS_CHANNEL == 1)
T = fetch_red();
else if (PS_CHANNEL == 2)
T = fetch_green();
else if (PS_CHANNEL == 3)
T = fetch_blue();
else if (PS_CHANNEL == 4)
T = fetch_alpha();
else if (PS_CHANNEL == 5)
T = fetch_rgb();
else if (PS_CHANNEL == 6)
T = fetch_gXbY();
else if (PS_DEPTH_FMT != 0)
T = sample_depth(st_int);
else
T = sample_color(st);
float4 C = tfx(T, IIP ? in.c : in.fc);
if (!atst(C))
discard_fragment();
fog(C, in.t.z);
return C;
}
void ps_fbmask(thread float4& C)
{
if (PS_FBMASK)
C = float4((uint4(C) & ~cb.fbmask) | (uint4(current_color * 255.5) & cb.fbmask));
}
void ps_dither(thread float4& C)
{
if (PS_DITHER == 0)
return;
ushort2 fpos;
if (PS_DITHER == 2)
fpos = ushort2(in.p.xy);
else
fpos = ushort2(in.p.xy / float2(SCALING_FACTOR));
C.rgb += cb.dither_matrix[fpos.y & 3][fpos.x & 3];
}
void ps_color_clamp_wrap(thread float4& C)
{
// When dithering the bottom 3 bits become meaningless and cause lines in the picture so we need to limit the color depth on dithered items
if (!SW_BLEND && !PS_DITHER)
return;
// Correct the Color value based on the output format
if (!PS_COLCLIP && !PS_HDR)
C.rgb = clamp(C.rgb, 0.f, 255.f); // Standard Clamp
// FIXME rouding of negative float?
// compiler uses trunc but it might need floor
// Warning: normally blending equation is mult(A, B) = A * B >> 7. GPU have the full accuracy
// GS: Color = 1, Alpha = 255 => output 1
// GPU: Color = 1/255, Alpha = 255/255 * 255/128 => output 1.9921875
if (PS_DFMT == FMT_16 && (PS_HDR || !PS_BLEND_MIX))
// In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania
C.rgb = float3(short3(C.rgb) & 0xF8);
else if (PS_COLCLIP && !PS_HDR)
C.rgb = float3(short3(C.rgb) & 0xFF);
}
template <typename T>
static T pick(uint selector, T zero, T one, T two)
{
return selector == 0 ? zero : selector == 1 ? one : two;
}
void ps_blend(thread float4& Color, float As)
{
if (SW_BLEND)
{
float Ad = PS_DFMT == FMT_24 ? 1.f : trunc(current_color.a * 255.5f) / 128.f;
float3 Cd = trunc(current_color.rgb * 255.5f);
float3 Cs = Color.rgb;
float3 A = pick(PS_BLEND_A, Cs, Cd, float3(0.f));
float3 B = pick(PS_BLEND_B, Cs, Cd, float3(0.f));
float C = pick(PS_BLEND_C, As, Ad, cb.alpha_fix);
float3 D = pick(PS_BLEND_D, Cs, Cd, float3(0.f));
if (PS_BLEND_MIX)
C = min(C, 1.f);
if (PS_BLEND_A == PS_BLEND_B)
Color.rgb = D;
else
Color.rgb = trunc((A - B) * C + D);
if (PS_PABE)
Color.rgb = (As >= 1.f) ? Color.rgb : Cs;
}
else
{
// Needed for Cd * (As/Ad/F + 1) blending mdoes
if (PS_CLR_HW == 1 || PS_CLR_HW == 5)
{
Color.rgb = 255.f;
}
else if (PS_CLR_HW == 2 || PS_CLR_HW == 4)
{
float Alpha = PS_BLEND_C == 2 ? cb.alpha_fix : As;
Color.rgb = saturate(Alpha - 1.f) * 255.f;
}
else if (PS_CLR_HW == 3)
{
// Needed for Cs*Ad, Cs*Ad + Cd, Cd - Cs*Ad
// Multiply Color.rgb by (255/128) to compensate for wrong Ad/255 value
Color.rgb *= (255.f / 128.f);
}
}
}
MainPSOut ps_main()
{
MainPSOut out = {};
if (PS_SCANMSK & 2)
{
if ((uint(in.p.y) & 1) == (PS_SCANMSK & 1))
discard_fragment();
}
if (PS_DATE >= 5)
{
// 1 => DATM == 0, 2 => DATM == 1
float rt_a = PS_WRITE_RG ? current_color.g : current_color.a;
bool bad = (PS_DATE & 3) == 1 ? (rt_a > 0.5) : (rt_a < 0.5);
if (bad)
discard_fragment();
}
if (PS_DATE == 3)
{
float stencil_ceil = prim_id_tex.read(uint2(in.p.xy)).r;
// Note prim_id == stencil_ceil will be the primitive that will update
// the bad alpha value so we must keep it.
if (float(prim_id) > stencil_ceil)
discard_fragment();
}
float4 C = ps_color();
if (PS_SHUFFLE)
{
uchar4 denorm_c = uchar4(C);
uchar2 denorm_TA = uchar2(cb.ta * 255.5f);
C.rb = PS_READ_BA ? C.bb : C.rr;
if (PS_READ_BA)
C.ga = (denorm_c.a & 0x7F) | (denorm_c.a & 0x80 ? denorm_TA.y & 0x80 : denorm_TA.x & 0x80);
else
C.ga = (denorm_c.g & 0x7F) | (denorm_c.g & 0x80 ? denorm_TA.y & 0x80 : denorm_TA.x & 0x80);
}
// Must be done before alpha correction
float alpha_blend = SW_AD_TO_HW ? (PS_DFMT == FMT_24 ? 1.f : trunc(current_color.a * 255.5f) / 128.f) : (C.a / 128.f);
if (PS_DFMT == FMT_16)
{
float A_one = 128.f;
C.a = (PS_FBA) ? A_one : step(128.f, C.a) * A_one;
}
else if (PS_DFMT == FMT_32 && PS_FBA)
{
if (C.a < 128.f)
C.a += 128.f;
}
// Get first primitive that will write a failing alpha value
if (PS_DATE == 1)
{
// DATM == 0, Pixel with alpha equal to 1 will failed (128-255)
out.c0 = C.a > 127.5f ? float(prim_id) : FLT_MAX;
return out;
}
else if (PS_DATE == 2)
{
// DATM == 1, Pixel with alpha equal to 0 will failed (0-127)
out.c0 = C.a < 127.5f ? float(prim_id) : FLT_MAX;
return out;
}
ps_blend(C, alpha_blend);
ps_dither(C);
// Color clamp/wrap needs to be done after sw blending and dithering
ps_color_clamp_wrap(C);
ps_fbmask(C);
if (PS_COLOR0)
out.c0 = C / 255.f;
if (PS_COLOR0 && PS_ONLY_ALPHA)
out.c0.rgb = 0;
if (PS_COLOR1)
out.c1 = alpha_blend;
if (PS_ZCLAMP)
out.depth = min(in.p.z, cb.max_depth);
return out;
}
};
#if FBFETCH_SUPPORT
fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]])
{
return in * 2;
}
constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH;
constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH;
#else
constant bool NEEDS_RT_TEX = NEEDS_RT;
#endif
fragment MainPSOut ps_main(
MainPSIn in [[stage_in]],
constant GSMTLMainPSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
sampler s [[sampler(0)]],
#if PRIMID_SUPPORT
uint primid [[primitive_id, function_constant(NEEDS_PRIMID)]],
#endif
#if FBFETCH_SUPPORT
float4 rt_fbf [[color(0), raster_order_group(0), function_constant(NEEDS_RT_FBF)]],
#endif
texture2d<float> tex [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_COLOR)]],
depth2d<float> depth [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_DEPTH)]],
texture2d<float> palette [[texture(GSMTLTextureIndexPalette), function_constant(PS_HAS_PALETTE)]],
texture2d<float> rt [[texture(GSMTLTextureIndexRenderTarget), function_constant(NEEDS_RT_TEX)]],
texture2d<float> primidtex [[texture(GSMTLTextureIndexPrimIDs), function_constant(PS_PRIM_CHECKING_READ)]])
{
PSMain main(in, cb);
main.tex_sampler = s;
if (PS_TEX_IS_COLOR)
main.tex = tex;
else
main.tex_depth = depth;
if (PS_HAS_PALETTE)
main.palette = palette;
if (PS_PRIM_CHECKING_READ)
main.prim_id_tex = primidtex;
#if PRIMID_SUPPORT
if (NEEDS_PRIMID)
main.prim_id = primid;
#endif
if (NEEDS_RT)
{
#if FBFETCH_SUPPORT
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(uint2(in.p.xy));
#else
main.current_color = rt.read(uint2(in.p.xy));
#endif
}
else
{
main.current_color = 0;
}
return main.ps_main();
}
#if PRIMID_SUPPORT
fragment uint primid_test(uint id [[primitive_id]])
{
return id;
}
#endif
// MARK: Markers for detecting the Metal version a metallib was compiled against
#if __METAL_VERSION__ >= 210
kernel void metal_version_21() {}
#endif
#if __METAL_VERSION__ >= 220
kernel void metal_version_22() {}
#endif
#if __METAL_VERSION__ >= 230
kernel void metal_version_23() {}
#endif
+1 -1
View File
@@ -931,7 +931,7 @@ void GSDeviceOGL::InitPrimDateTexture(GSTexture* rt, const GSVector4i& area)
// Create a texture to avoid the useless clean@0
if (m_date.t == NULL)
m_date.t = CreateTexture(rtsize.x, rtsize.y, false, GSTexture::Format::Int32);
m_date.t = CreateTexture(rtsize.x, rtsize.y, false, GSTexture::Format::PrimID);
// Clean with the max signed value
const int max_int = 0x7FFFFFFF;
+3 -3
View File
@@ -189,7 +189,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
switch (m_format)
{
// 1 Channel integer
case Format::Int32:
case Format::PrimID:
gl_fmt = GL_R32I;
m_int_format = GL_RED_INTEGER;
m_int_type = GL_INT;
@@ -311,7 +311,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
case Format::Color:
case Format::UInt32:
case Format::Int32:
case Format::PrimID:
m_sparse &= GLLoader::found_compatible_GL_ARB_sparse_texture2;
SetGpuPageSize(GSVector2i(127, 127));
break;
@@ -645,7 +645,7 @@ bool GSTextureOGL::Save(const std::string& fn)
fmt = GSPng::RGB_A_PNG;
}
else if (m_format == Format::Int32)
else if (m_format == Format::PrimID)
{
// Note: 4.5 function used for accurate DATE
// barely used outside of dev and not sparse anyway
+6 -7
View File
@@ -283,12 +283,12 @@ bool GSDeviceVK::CheckFeatures()
m_features.line_expand ? "hardware" : "geometry shaders");
// Check texture format support before we try to create them.
for (u32 fmt = static_cast<u32>(GSTexture::Format::Color); fmt < static_cast<u32>(GSTexture::Format::Int32); fmt++)
for (u32 fmt = static_cast<u32>(GSTexture::Format::Color); fmt < static_cast<u32>(GSTexture::Format::PrimID); fmt++)
{
const VkFormat vkfmt = LookupNativeFormat(static_cast<GSTexture::Format>(fmt));
const VkFormatFeatureFlags bits = (static_cast<GSTexture::Format>(fmt) == GSTexture::Format::DepthStencil) ?
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) :
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT);
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) :
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT);
VkFormatProperties props = {};
vkGetPhysicalDeviceFormatProperties(g_vulkan_context->GetPhysicalDevice(), vkfmt, &props);
@@ -1492,11 +1492,10 @@ bool GSDeviceVK::CompileConvertPipelines()
for (u32 clear = 0; clear < 2; clear++)
{
m_date_image_setup_render_passes[ds][clear] =
g_vulkan_context->GetRenderPass(LookupNativeFormat(GSTexture::Format::Int32),
g_vulkan_context->GetRenderPass(LookupNativeFormat(GSTexture::Format::PrimID),
ds ? LookupNativeFormat(GSTexture::Format::DepthStencil) : VK_FORMAT_UNDEFINED,
VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE,
ds ? (clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD) :
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
ds ? (clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD) : VK_ATTACHMENT_LOAD_OP_DONT_CARE,
ds ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE);
}
}
@@ -2732,7 +2731,7 @@ GSTextureVK* GSDeviceVK::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, Pipe
const GSVector2i rtsize(config.rt->GetSize());
GSTextureVK* image =
static_cast<GSTextureVK*>(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::Int32, false));
static_cast<GSTextureVK*>(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::PrimID, false));
if (!image)
return nullptr;
+7 -1
View File
@@ -22,6 +22,7 @@
#ifdef _WIN32
#include "Frontend/D3D11HostDisplay.h"
#endif
#include "GS/Renderers/Metal/GSMetalCPPAccessible.h"
#ifdef ENABLE_VULKAN
#include "Frontend/VulkanHostDisplay.h"
@@ -694,6 +695,11 @@ void Dialog::RendererChange()
case GSRendererType::VK:
list = VulkanHostDisplay::StaticGetAdapterAndModeList(nullptr);
break;
#endif
#ifdef __APPLE__
case GSRendererType::Metal:
list = GetMetalAdapterAndModeList();
break;
#endif
default:
break;
@@ -774,7 +780,7 @@ void Dialog::Update()
else
{
// cross-tab dependencies yay
const bool is_hw = renderer == GSRendererType::OGL || renderer == GSRendererType::DX11 || renderer == GSRendererType::VK;
const bool is_hw = renderer == GSRendererType::OGL || renderer == GSRendererType::DX11 || renderer == GSRendererType::VK || renderer == GSRendererType::Metal;
const bool is_upscale = m_renderer_panel->m_internal_resolution->GetSelection() != 0;
m_hacks_panel->m_is_native_res = !is_hw || !is_upscale;
m_hacks_panel->m_is_hardware = is_hw;
+12
View File
@@ -285,6 +285,7 @@ static const char* s_gs_hw_fix_names[] = {
"halfPixelOffset",
"roundSprite",
"texturePreloading",
"interlace",
};
static_assert(std::size(s_gs_hw_fix_names) == static_cast<u32>(GameDatabaseSchema::GSHWFixId::Count), "HW fix name lookup is correct size");
@@ -308,6 +309,7 @@ bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id)
{
switch (id)
{
case GSHWFixId::Interlace:
case GSHWFixId::Mipmap:
case GSHWFixId::TexturePreloading:
case GSHWFixId::ConservativeFramebuffer:
@@ -437,6 +439,16 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
}
break;
case GSHWFixId::Interlace:
if (value >= 0 && value <= static_cast<int>(GSInterlaceMode::Automatic))
{
if (config.InterlaceMode == GSInterlaceMode::Automatic)
config.InterlaceMode = static_cast<GSInterlaceMode>(value);
else
Console.Warning("[GameDB] Game requires different interlace mode but it has been overridden by user setting.");
}
break;
default:
break;
}
+1
View File
@@ -81,6 +81,7 @@ namespace GameDatabaseSchema
HalfPixelOffset,
RoundSprite,
TexturePreloading,
Interlace,
Count
};
+18 -2
View File
@@ -32,8 +32,19 @@ HostDisplay::~HostDisplay() = default;
const char* HostDisplay::RenderAPIToString(RenderAPI api)
{
static const char* names[] = {"None", "D3D11", "Vulkan", "OpenGL", "OpenGLES"};
return (static_cast<u32>(api) >= std::size(names)) ? names[0] : names[static_cast<u32>(api)];
switch (api)
{
#define CASE(x) case RenderAPI::x: return #x
CASE(None);
CASE(D3D11);
CASE(Metal);
CASE(Vulkan);
CASE(OpenGL);
CASE(OpenGLES);
#undef CASE
default:
return "Unknown";
}
}
bool HostDisplay::UsesLowerLeftOrigin() const
@@ -122,6 +133,7 @@ std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float re
#ifdef _WIN32
#include "Frontend/D3D11HostDisplay.h"
#endif
#include "GS/Renderers/Metal/GSMetalCPPAccessible.h"
std::unique_ptr<HostDisplay> HostDisplay::CreateDisplayForAPI(RenderAPI api)
{
@@ -131,6 +143,10 @@ std::unique_ptr<HostDisplay> HostDisplay::CreateDisplayForAPI(RenderAPI api)
case RenderAPI::D3D11:
return std::make_unique<D3D11HostDisplay>();
#endif
#ifdef __APPLE__
case HostDisplay::RenderAPI::Metal:
return std::unique_ptr<HostDisplay>(MakeMetalHostDisplay());
#endif
case RenderAPI::OpenGL:
case RenderAPI::OpenGLES:
+1
View File
@@ -46,6 +46,7 @@ public:
{
None,
D3D11,
Metal,
Vulkan,
OpenGL,
OpenGLES
+1 -1
View File
@@ -441,7 +441,7 @@ void SysMtgsThread::ExecuteTaskInThread()
((GSRegSIGBLID&)RingBuffer.Regs[0x1080]) = (GSRegSIGBLID&)remainder[2];
// CSR & 0x2000; is the pageflip id.
GSvsync(((u32&)RingBuffer.Regs[0x1000]) & 0x2000, remainder[4] != 0);
GSvsync((((u32&)RingBuffer.Regs[0x1000]) & 0x2000) ? 0 : 1, remainder[4] != 0);
gsFrameSkip();
m_QueuedFrameCount.fetch_sub(1);
+71 -115
View File
@@ -90,7 +90,7 @@ void Pcsx2Config::SpeedhackOptions::Set(SpeedhackId id, bool enabled)
case Speedhack_MTVU:
vuThread = enabled;
break;
jNO_DEFAULT;
jNO_DEFAULT;
}
}
@@ -224,6 +224,14 @@ void Pcsx2Config::RecompilerOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBool(PreBlockCheckIOP);
}
bool Pcsx2Config::CpuOptions::CpusChanged(const CpuOptions& right) const
{
return (Recompiler.EnableEE != right.Recompiler.EnableEE ||
Recompiler.EnableIOP != right.Recompiler.EnableIOP ||
Recompiler.EnableVU0 != right.Recompiler.EnableVU0 ||
Recompiler.EnableVU1 != right.Recompiler.EnableVU1);
}
Pcsx2Config::CpuOptions::CpuOptions()
{
sseMXCSR.bitmask = DEFAULT_sseMXCSR;
@@ -269,20 +277,14 @@ const char* Pcsx2Config::GSOptions::GetRendererName(GSRendererType type)
{
switch (type)
{
case GSRendererType::Auto:
return "Auto";
case GSRendererType::DX11:
return "Direct3D 11";
case GSRendererType::OGL:
return "OpenGL";
case GSRendererType::VK:
return "Vulkan";
case GSRendererType::SW:
return "Software";
case GSRendererType::Null:
return "Null";
default:
return "";
case GSRendererType::Auto: return "Auto";
case GSRendererType::DX11: return "Direct3D 11";
case GSRendererType::Metal: return "Metal";
case GSRendererType::OGL: return "OpenGL";
case GSRendererType::VK: return "Vulkan";
case GSRendererType::SW: return "Software";
case GSRendererType::Null: return "Null";
default: return "";
}
}
@@ -556,6 +558,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
GSSettingIntEnumEx(Renderer, "Renderer");
GSSettingIntEx(UpscaleMultiplier, "upscale_multiplier");
UpscaleMultiplier = std::clamp(UpscaleMultiplier, 1u, 8u);
GSSettingIntEnumEx(HWMipmap, "mipmap_hw");
GSSettingIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit");
@@ -644,7 +647,7 @@ void Pcsx2Config::GSOptions::MaskUpscalingHacks()
bool Pcsx2Config::GSOptions::UseHardwareRenderer() const
{
return (Renderer == GSRendererType::DX11 || Renderer == GSRendererType::OGL || Renderer == GSRendererType::VK);
return (Renderer == GSRendererType::DX11 || Renderer == GSRendererType::OGL || Renderer == GSRendererType::VK || Renderer == GSRendererType::Metal);
}
VsyncMode Pcsx2Config::GetEffectiveVsyncMode() const
@@ -817,23 +820,24 @@ std::string Pcsx2Config::DEV9Options::SaveIPHelper(u8* field)
}
static const char* const tbl_GamefixNames[] =
{
"FpuMul",
"FpuNegDiv",
"GoemonTlb",
"SoftwareRendererFMV",
"SkipMPEG",
"OPHFlag",
"EETiming",
"DMABusy",
"GIFFIFO",
"VIFFIFO",
"VIF1Stall",
"VuAddSub",
"Ibit",
"VUSync",
"VUOverflow",
"XGKick"};
{
"FpuMul",
"FpuNegDiv",
"GoemonTlb",
"SoftwareRendererFMV",
"SkipMPEG",
"OPHFlag",
"EETiming",
"DMABusy",
"GIFFIFO",
"VIFFIFO",
"VIF1Stall",
"VuAddSub",
"Ibit",
"VUSync",
"VUOverflow",
"XGKick"
};
const char* EnumToString(GamefixId id)
{
@@ -881,55 +885,23 @@ void Pcsx2Config::GamefixOptions::Set(GamefixId id, bool enabled)
EnumAssert(id);
switch (id)
{
case Fix_VuAddSub:
VuAddSubHack = enabled;
break;
case Fix_FpuMultiply:
FpuMulHack = enabled;
break;
case Fix_FpuNegDiv:
FpuNegDivHack = enabled;
break;
case Fix_XGKick:
XgKickHack = enabled;
break;
case Fix_EETiming:
EETimingHack = enabled;
break;
case Fix_SoftwareRendererFMV:
SoftwareRendererFMVHack = enabled;
break;
case Fix_SkipMpeg:
SkipMPEGHack = enabled;
break;
case Fix_OPHFlag:
OPHFlagHack = enabled;
break;
case Fix_DMABusy:
DMABusyHack = enabled;
break;
case Fix_VIFFIFO:
VIFFIFOHack = enabled;
break;
case Fix_VIF1Stall:
VIF1StallHack = enabled;
break;
case Fix_GIFFIFO:
GIFFIFOHack = enabled;
break;
case Fix_GoemonTlbMiss:
GoemonTlbHack = enabled;
break;
case Fix_Ibit:
IbitHack = enabled;
break;
case Fix_VUSync:
VUSyncHack = enabled;
break;
case Fix_VUOverflow:
VUOverflowHack = enabled;
break;
jNO_DEFAULT;
case Fix_VuAddSub: VuAddSubHack = enabled; break;
case Fix_FpuMultiply: FpuMulHack = enabled; break;
case Fix_FpuNegDiv: FpuNegDivHack = enabled; break;
case Fix_XGKick: XgKickHack = enabled; break;
case Fix_EETiming: EETimingHack = enabled; break;
case Fix_SoftwareRendererFMV: SoftwareRendererFMVHack = enabled; break;
case Fix_SkipMpeg: SkipMPEGHack = enabled; break;
case Fix_OPHFlag: OPHFlagHack = enabled; break;
case Fix_DMABusy: DMABusyHack = enabled; break;
case Fix_VIFFIFO: VIFFIFOHack = enabled; break;
case Fix_VIF1Stall: VIF1StallHack = enabled; break;
case Fix_GIFFIFO: GIFFIFOHack = enabled; break;
case Fix_GoemonTlbMiss: GoemonTlbHack = enabled; break;
case Fix_Ibit: IbitHack = enabled; break;
case Fix_VUSync: VUSyncHack = enabled; break;
case Fix_VUOverflow: VUOverflowHack = enabled; break;
jNO_DEFAULT;
}
}
@@ -938,39 +910,23 @@ bool Pcsx2Config::GamefixOptions::Get(GamefixId id) const
EnumAssert(id);
switch (id)
{
case Fix_VuAddSub:
return VuAddSubHack;
case Fix_FpuMultiply:
return FpuMulHack;
case Fix_FpuNegDiv:
return FpuNegDivHack;
case Fix_XGKick:
return XgKickHack;
case Fix_EETiming:
return EETimingHack;
case Fix_SoftwareRendererFMV:
return SoftwareRendererFMVHack;
case Fix_SkipMpeg:
return SkipMPEGHack;
case Fix_OPHFlag:
return OPHFlagHack;
case Fix_DMABusy:
return DMABusyHack;
case Fix_VIFFIFO:
return VIFFIFOHack;
case Fix_VIF1Stall:
return VIF1StallHack;
case Fix_GIFFIFO:
return GIFFIFOHack;
case Fix_GoemonTlbMiss:
return GoemonTlbHack;
case Fix_Ibit:
return IbitHack;
case Fix_VUSync:
return VUSyncHack;
case Fix_VUOverflow:
return VUOverflowHack;
jNO_DEFAULT;
case Fix_VuAddSub: return VuAddSubHack;
case Fix_FpuMultiply: return FpuMulHack;
case Fix_FpuNegDiv: return FpuNegDivHack;
case Fix_XGKick: return XgKickHack;
case Fix_EETiming: return EETimingHack;
case Fix_SoftwareRendererFMV: return SoftwareRendererFMVHack;
case Fix_SkipMpeg: return SkipMPEGHack;
case Fix_OPHFlag: return OPHFlagHack;
case Fix_DMABusy: return DMABusyHack;
case Fix_VIFFIFO: return VIFFIFOHack;
case Fix_VIF1Stall: return VIF1StallHack;
case Fix_GIFFIFO: return GIFFIFOHack;
case Fix_GoemonTlbMiss: return GoemonTlbHack;
case Fix_Ibit: return IbitHack;
case Fix_VUSync: return VUSyncHack;
case Fix_VUOverflow: return VUOverflowHack;
jNO_DEFAULT;
}
return false; // unreachable, but we still need to suppress warnings >_<
}
+3
View File
@@ -592,7 +592,10 @@ void SysCpuProviderPack::ApplyConfig() const
// Use this method to reset the recs when important global pointers like the MTGS are re-assigned.
void SysClearExecutionCache()
{
// Done by VMManager in Qt.
#ifndef PCSX2_CORE
GetCpuProviders().ApplyConfig();
#endif
Cpu->Reset();
psxCpu->Reset();
-19
View File
@@ -170,25 +170,6 @@ extern wxString SysGetDiscID();
extern SysMainMemory& GetVmMemory();
// --------------------------------------------------------------------------------------
// PCSX2_SEH - Defines existence of "built in" Structured Exception Handling support.
// --------------------------------------------------------------------------------------
// This should be available on Windows, via Microsoft or Intel compilers (I'm pretty sure Intel
// supports native SEH model). GNUC in Windows, or any compiler in a non-windows platform, will
// need to use setjmp/longjmp instead to exit recompiled code.
// In addition, we don't currently set up SEH properly on Windows x64 so disable it there too
//
//#define PCSX2_SEH 0 // use this to force disable SEH on win32, to test setjmp functionality.
#ifndef PCSX2_SEH
# if defined(_WIN32) && !defined(__GNUC__) && !defined(_WIN64)
# define PCSX2_SEH 1
# else
# define PCSX2_SEH 0
# endif
#endif
// special macro which disables inlining on functions that require their own function stackframe.
// This is due to how Win32 handles structured exception handling. Linux uses signals instead
// of SEH, and so these functions can be inlined.
+22 -1
View File
@@ -96,6 +96,7 @@ static std::unique_ptr<INISettingsInterface> s_game_settings_interface;
static u64 s_emu_thread_affinity;
static std::atomic<VMState> s_state{VMState::Shutdown};
static std::atomic_bool s_cpu_implementation_changed{false};
static std::mutex s_info_mutex;
static std::string s_disc_path;
@@ -757,6 +758,8 @@ bool VMManager::Initialize(const VMBootParameters& boot_params)
s_mxcsr_saved = static_cast<u32>(a64_getfpcr());
#endif
s_cpu_implementation_changed.store(false);
s_cpu_provider_pack->ApplyConfig();
SetCPUState(EmuConfig.Cpu.sseMXCSR, EmuConfig.Cpu.sseVUMXCSR);
SysClearExecutionCache();
memBindConditionalHandlers();
@@ -1039,6 +1042,15 @@ bool VMManager::IsGSDumpFileName(const std::string& path)
void VMManager::Execute()
{
// Check for interpreter<->recompiler switches.
if (s_cpu_implementation_changed.exchange(false))
{
// We need to switch the cpus out, and reset the new ones if so.
s_cpu_provider_pack->ApplyConfig();
SysClearExecutionCache();
}
// Execute until we're asked to stop.
Cpu->Execute();
}
@@ -1058,7 +1070,7 @@ const std::string& VMManager::Internal::GetElfOverride()
bool VMManager::Internal::IsExecutionInterrupted()
{
return s_state.load() != VMState::Running;
return s_state.load() != VMState::Running || s_cpu_implementation_changed.load();
}
void VMManager::Internal::GameStartingOnCPUThread()
@@ -1098,6 +1110,15 @@ void VMManager::CheckForCPUConfigChanges(const Pcsx2Config& old_config)
SetCPUState(EmuConfig.Cpu.sseMXCSR, EmuConfig.Cpu.sseVUMXCSR);
SysClearExecutionCache();
memBindConditionalHandlers();
// did we toggle recompilers?
if (EmuConfig.Cpu.CpusChanged(old_config.Cpu))
{
// This has to be done asynchronously, since we're still executing the
// cpu when this function is called. Break the execution as soon as
// possible and reset next time we're called.
s_cpu_implementation_changed.store(true);
}
}
void VMManager::CheckForGSConfigChanges(const Pcsx2Config& old_config)
+11 -6
View File
@@ -129,6 +129,8 @@ Dialogs::GSDumpDialog::GSDumpDialog(wxWindow* parent)
#endif
#if defined(_WIN32)
rdoverrides.Add(Pcsx2Config::GSOptions::GetRendererName(GSRendererType::DX11));
#elif defined(__APPLE__)
rdoverrides.Add(Pcsx2Config::GSOptions::GetRendererName(GSRendererType::Metal));
#endif
m_renderer_overrides->Create(this, wxID_ANY, "Renderer overrides", wxDefaultPosition, wxDefaultSize, rdoverrides, 1);
@@ -766,7 +768,7 @@ void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSDumpFile::GSData& event, u8
}
case GSType::VSync:
{
GSvsync((*((int*)(regs + 4096)) & 0x2000) > 0 ? (u8)1 : (u8)0, false);
GSvsync((*((int*)(regs + 4096)) & 0x2000) > 0 ? (u8)0 : (u8)1, false);
g_FrameCount++;
break;
}
@@ -829,18 +831,21 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
case 2:
renderer = GSRendererType::OGL;
break;
#ifdef ENABLE_VULKAN
// Vulkan
case 3:
#ifdef ENABLE_VULKAN
// Vulkan
renderer = GSRendererType::VK;
break;
case 4:
#endif
#ifdef _WIN32
// D3D11
case 4: // WIN32 implies WITH_VULKAN so this is okay
// D3D11
renderer = GSRendererType::DX11;
break;
#elif defined(__APPLE__)
// Metal
renderer = GSRendererType::Metal;
#endif
break;
default:
break;
}
+6 -6
View File
@@ -16,6 +16,7 @@
#include "PrecompiledHeader.h"
#include <cstdio>
#include <cstring>
#include "common/FileSystem.h"
#include "common/StringUtil.h"
@@ -58,15 +59,13 @@ BiosDebugInformation CurrentBiosInformation;
static bool LoadBiosVersion(std::FILE* fp, u32& version, std::string& description, u32& region, std::string& zone)
{
uint i;
romdir rd;
for (i = 0; i < 512 * 1024; i++)
for (u32 i = 0; i < 512 * 1024; i++)
{
if (std::fread(&rd, sizeof(rd), 1, fp) != 1)
return false;
if (std::strncmp(rd.fileName, "RESET", 5) == 0)
if (std::strncmp(rd.fileName, "RESET", sizeof(rd.fileName)) == 0)
break; /* found romdir */
}
@@ -74,9 +73,10 @@ static bool LoadBiosVersion(std::FILE* fp, u32& version, std::string& descriptio
s64 fileSize = FileSystem::FSize64(fp);
bool foundRomVer = false;
while (strlen(rd.fileName) > 0)
// ensure it's a null-terminated and not zero-length string
while (rd.fileName[0] != '\0' && strnlen(rd.fileName, sizeof(rd.fileName)) != sizeof(rd.fileName))
{
if (strcmp(rd.fileName, "ROMVER") == 0)
if (std::strncmp(rd.fileName, "ROMVER", sizeof(rd.fileName)) == 0)
{
char romver[14 + 1] = {}; // ascii version loaded from disk.
+15 -49
View File
@@ -40,11 +40,7 @@
#include "DebugTools/Breakpoints.h"
#include "Patch.h"
#if !PCSX2_SEH
#include "common/FastJmp.h"
#endif
#include "common/FastJmp.h"
#include "common/MemsetFast.inl"
#include "common/Perf.h"
@@ -638,9 +634,9 @@ static void recResetRaw()
recAlloc();
eeRecNeedsReset = false;
if (eeRecIsReset.exchange(true))
return;
eeRecNeedsReset = false;
Console.WriteLn(Color_StrongBlack, "EE/iR5900-32 Recompiler Reset");
@@ -700,36 +696,26 @@ void recStep()
{
}
#if !PCSX2_SEH
#define SETJMP_CODE(x) x
static fastjmp_buf m_SetJmp_StateCheck;
static std::unique_ptr<BaseR5900Exception> m_cpuException;
static ScopedExcept m_Exception;
#else
#define SETJMP_CODE(x)
#endif
static fastjmp_buf m_SetJmp_StateCheck;
static std::unique_ptr<BaseR5900Exception> m_cpuException;
static ScopedExcept m_Exception;
static void recExitExecution()
{
#if PCSX2_SEH
throw Exception::ExitCpuExecute();
#else
// Without SEH we'll need to hop to a safehouse point outside the scope of recompiled
// code. C++ exceptions can't cross the mighty chasm in the stackframe that the recompiler
// creates. However, the longjump is slow so we only want to do one when absolutely
// necessary:
fastjmp_jmp(&m_SetJmp_StateCheck, 1);
#endif
}
static void recCheckExecutionState()
{
#ifndef PCSX2_CORE
if (SETJMP_CODE(m_cpuException || m_Exception ||) eeRecIsReset || GetCoreThread().HasPendingStateChangeRequest())
if (m_cpuException || m_Exception || eeRecNeedsReset || GetCoreThread().HasPendingStateChangeRequest())
#else
if (SETJMP_CODE(m_cpuException || m_Exception ||) eeRecIsReset || VMManager::Internal::IsExecutionInterrupted())
if (m_cpuException || m_Exception || eeRecNeedsReset || VMManager::Internal::IsExecutionInterrupted())
#endif
{
recExitExecution();
@@ -738,33 +724,22 @@ static void recCheckExecutionState()
static void recExecute()
{
// Implementation Notes:
// [TODO] fix this comment to explain various code entry/exit points, when I'm not so tired!
#if PCSX2_SEH
eeRecIsReset = false;
ScopedBool executing(eeCpuExecuting);
try
{
EnterRecompiledCode();
}
catch (Exception::ExitCpuExecute&)
{
}
#else
// Reset before we try to execute any code, if there's one pending.
// We need to do this here, because if we reset while we're executing, it sets the "needs reset"
// flag, which triggers a JIT exit (the fastjmp_set below), and eventually loops back here.
eeRecIsReset.store(false);
if (eeRecNeedsReset.load())
recResetRaw();
int oldstate;
m_cpuException = NULL;
m_Exception = NULL;
m_cpuException = nullptr;
m_Exception = nullptr;
// setjmp will save the register context and will return 0
// A call to longjmp will restore the context (included the eip/rip)
// but will return the longjmp 2nd parameter (here 1)
if (!fastjmp_set(&m_SetJmp_StateCheck))
{
eeRecIsReset = false;
eeCpuExecuting = true;
// Important! Most of the console logging and such has cancel points in it. This is great
@@ -791,7 +766,6 @@ static void recExecute()
// FIXME Warning thread unsafe
Perf::dump();
#endif
EE::Profiler.Print();
}
@@ -2386,26 +2360,18 @@ StartRecomp:
// SEH unwind (MSW) or setjmp/longjmp (GCC).
static void recThrowException(const BaseR5900Exception& ex)
{
#if PCSX2_SEH
ex.Rethrow();
#else
if (!eeCpuExecuting)
ex.Rethrow();
m_cpuException = std::unique_ptr<BaseR5900Exception>(ex.Clone());
recExitExecution();
#endif
}
static void recThrowException(const BaseException& ex)
{
#if PCSX2_SEH
ex.Rethrow();
#else
if (!eeCpuExecuting)
ex.Rethrow();
m_Exception = ScopedExcept(ex.Clone());
recExitExecution();
#endif
}
static void recSetCacheReserve(uint reserveInMegs)