mirror of https://github.com/PCSX2/pcsx2.git
GS: Add Metal renderer
This commit is contained in:
parent
24b2277206
commit
5ecaa9459d
|
@ -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
|
#ifndef FXAA_GLSL_130
|
||||||
#define FXAA_GLSL_130 0
|
#define FXAA_GLSL_130 0
|
||||||
|
@ -47,6 +47,8 @@ struct PS_OUTPUT
|
||||||
float4 c : SV_Target0;
|
float4 c : SV_Target0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#elif defined(__METAL_VERSION__)
|
||||||
|
static constexpr sampler MAIN_SAMPLER(coord::normalized, address::clamp_to_edge, filter::linear);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*------------------------------------------------------------------------------
|
/*------------------------------------------------------------------------------
|
||||||
|
@ -63,6 +65,9 @@ struct PS_OUTPUT
|
||||||
|
|
||||||
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
|
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
|
||||||
#define FXAA_GATHER4_ALPHA 1
|
#define FXAA_GATHER4_ALPHA 1
|
||||||
|
|
||||||
|
#elif defined(__METAL_VERSION__)
|
||||||
|
#define FXAA_GATHER4_ALPHA 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if (FXAA_HLSL_5 == 1)
|
#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)
|
#define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3)
|
||||||
#endif
|
#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
|
#endif
|
||||||
|
|
||||||
#define FxaaEdgeThreshold 0.063
|
#define FxaaEdgeThreshold 0.063
|
||||||
|
@ -151,14 +164,8 @@ float3 LinearToRGBGamma(float3 color, float gamma)
|
||||||
return color;
|
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;
|
const float GammaConst = 2.233;
|
||||||
color.rgb = RGBGammaToLinear(color.rgb, GammaConst);
|
color.rgb = RGBGammaToLinear(color.rgb, GammaConst);
|
||||||
color.rgb = LinearToRGBGamma(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)
|
float4 FxaaPass(float4 FxaaColor, float2 uv0)
|
||||||
#elif (SHADER_MODEL >= 0x400)
|
#elif (SHADER_MODEL >= 0x400)
|
||||||
float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
|
float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
|
||||||
|
#elif defined(__METAL_VERSION__)
|
||||||
|
float4 FxaaPass(float4 FxaaColor, float2 uv0, texture2d<float> tex)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -498,6 +507,9 @@ float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
|
||||||
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
|
#elif (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
|
||||||
vec2 PixelSize = textureSize(TextureSampler, 0);
|
vec2 PixelSize = textureSize(TextureSampler, 0);
|
||||||
FxaaColor = FxaaPixelShader(uv0, TextureSampler, 1.0/PixelSize.xy, FxaaSubpixMax, FxaaEdgeThreshold, FxaaEdgeThresholdMin);
|
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
|
#endif
|
||||||
|
|
||||||
return FxaaColor;
|
return FxaaColor;
|
||||||
|
@ -511,7 +523,7 @@ float4 FxaaPass(float4 FxaaColor : COLOR0, float2 uv0 : TEXCOORD0)
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 color = texture(TextureSampler, PSin_t);
|
vec4 color = texture(TextureSampler, PSin_t);
|
||||||
color = PreGammaPass(color, PSin_t);
|
color = PreGammaPass(color);
|
||||||
color = FxaaPass(color, PSin_t);
|
color = FxaaPass(color, PSin_t);
|
||||||
|
|
||||||
SV_Target0 = color;
|
SV_Target0 = color;
|
||||||
|
@ -524,7 +536,7 @@ PS_OUTPUT ps_main(VS_OUTPUT input)
|
||||||
|
|
||||||
float4 color = Texture.Sample(TextureSampler, input.t);
|
float4 color = Texture.Sample(TextureSampler, input.t);
|
||||||
|
|
||||||
color = PreGammaPass(color, input.t);
|
color = PreGammaPass(color);
|
||||||
color = FxaaPass(color, input.t);
|
color = FxaaPass(color, input.t);
|
||||||
|
|
||||||
output.c = color;
|
output.c = color;
|
||||||
|
@ -532,6 +544,7 @@ PS_OUTPUT ps_main(VS_OUTPUT input)
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metal main function in in fxaa.metal
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -305,9 +305,14 @@ endif()
|
||||||
# MacOS-specific things
|
# 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)
|
# Older versions of the macOS stdlib don't have operator new(size_t, align_val_t)
|
||||||
# Disable use of them with this flag
|
# Disable use of them with this flag
|
||||||
# Not great, but also no worse that what we were getting before we turned on C++17
|
# Not great, but also no worse that what we were getting before we turned on C++17
|
||||||
|
|
|
@ -86,6 +86,7 @@ target_sources(common PRIVATE
|
||||||
MemcpyFast.h
|
MemcpyFast.h
|
||||||
MemsetFast.inl
|
MemsetFast.inl
|
||||||
MD5Digest.h
|
MD5Digest.h
|
||||||
|
MRCHelpers.h
|
||||||
Path.h
|
Path.h
|
||||||
PageFaultSource.h
|
PageFaultSource.h
|
||||||
PrecompiledHeader.h
|
PrecompiledHeader.h
|
||||||
|
@ -185,8 +186,8 @@ elseif(APPLE)
|
||||||
GL/ContextAGL.h
|
GL/ContextAGL.h
|
||||||
)
|
)
|
||||||
set_source_files_properties(GL/ContextAGL.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
|
set_source_files_properties(GL/ContextAGL.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
|
||||||
target_compile_options(common PUBLIC -fobjc-arc)
|
target_compile_options(common PRIVATE -fobjc-arc)
|
||||||
target_link_options(common PUBLIC -fobjc-link-runtime)
|
target_link_options(common PRIVATE -fobjc-link-runtime)
|
||||||
else()
|
else()
|
||||||
if(X11_API OR WAYLAND_API)
|
if(X11_API OR WAYLAND_API)
|
||||||
target_sources(common PRIVATE
|
target_sources(common PRIVATE
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -789,6 +789,14 @@ if(USE_VULKAN)
|
||||||
)
|
)
|
||||||
endif()
|
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)
|
if(NOT PCSX2_CORE)
|
||||||
list(APPEND pcsx2GSSources
|
list(APPEND pcsx2GSSources
|
||||||
GS/Window/GSwxDialog.cpp
|
GS/Window/GSwxDialog.cpp
|
||||||
|
@ -1004,6 +1012,26 @@ if(WIN32)
|
||||||
list(APPEND pcsx2FrontendHeaders
|
list(APPEND pcsx2FrontendHeaders
|
||||||
Frontend/D3D11HostDisplay.h
|
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()
|
endif()
|
||||||
|
|
||||||
if(PCSX2_CORE)
|
if(PCSX2_CORE)
|
||||||
|
@ -1710,6 +1738,49 @@ if(GETTEXT_FOUND AND NOT NO_TRANSLATION AND NOT PCSX2_CORE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
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)
|
# 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
|
# 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
|
# Obviously not being able to make those arrays executable would be a problem
|
||||||
|
@ -1766,6 +1837,7 @@ source_group(System/Ps2/DEV9 REGULAR_EXPRESSION DEV9/*)
|
||||||
source_group(System/Ps2/PAD FILES ${pcsx2PADSources} ${pcsx2PADHeaders})
|
source_group(System/Ps2/PAD FILES ${pcsx2PADSources} ${pcsx2PADHeaders})
|
||||||
source_group(System/Ps2/SPU2 REGULAR_EXPRESSION SPU2/*)
|
source_group(System/Ps2/SPU2 REGULAR_EXPRESSION SPU2/*)
|
||||||
source_group(System/Ps2/USB REGULAR_EXPRESSION USB/*)
|
source_group(System/Ps2/USB REGULAR_EXPRESSION USB/*)
|
||||||
|
source_group(System/Ps2/GS/Renderers/Metal REGULAR_EXPRESSION GS/Renderers/Metal/*)
|
||||||
|
|
||||||
# Generated resource files
|
# Generated resource files
|
||||||
source_group(Resources/GUI FILES ${pcsx2GuiResources})
|
source_group(Resources/GUI FILES ${pcsx2GuiResources})
|
||||||
|
|
|
@ -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
|
|
@ -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__
|
|
@ -18,7 +18,8 @@
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
// MacOS headers define PAGE_SIZE to the size of an x86 page
|
// 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
|
#undef PAGE_SIZE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -797,4 +797,7 @@ struct GSAdapter
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<GSHWDrawConfig::PSSelector> : public GSHWDrawConfig::PSSelectorHash {};
|
||||||
|
|
||||||
extern std::unique_ptr<GSDevice> g_gs_device;
|
extern std::unique_ptr<GSDevice> g_gs_device;
|
||||||
|
|
|
@ -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__
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -32,8 +32,19 @@ HostDisplay::~HostDisplay() = default;
|
||||||
|
|
||||||
const char* HostDisplay::RenderAPIToString(RenderAPI api)
|
const char* HostDisplay::RenderAPIToString(RenderAPI api)
|
||||||
{
|
{
|
||||||
static const char* names[] = {"None", "D3D11", "Vulkan", "OpenGL", "OpenGLES"};
|
switch (api)
|
||||||
return (static_cast<u32>(api) >= std::size(names)) ? names[0] : names[static_cast<u32>(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
|
bool HostDisplay::UsesLowerLeftOrigin() const
|
||||||
|
@ -122,6 +133,7 @@ std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float re
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "Frontend/D3D11HostDisplay.h"
|
#include "Frontend/D3D11HostDisplay.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "GS/Renderers/Metal/GSMetalCPPAccessible.h"
|
||||||
|
|
||||||
std::unique_ptr<HostDisplay> HostDisplay::CreateDisplayForAPI(RenderAPI api)
|
std::unique_ptr<HostDisplay> HostDisplay::CreateDisplayForAPI(RenderAPI api)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,6 +46,7 @@ public:
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
D3D11,
|
D3D11,
|
||||||
|
Metal,
|
||||||
Vulkan,
|
Vulkan,
|
||||||
OpenGL,
|
OpenGL,
|
||||||
OpenGLES
|
OpenGLES
|
||||||
|
|
Loading…
Reference in New Issue