Merge branch 'master' of https://github.com/xenia-project/xenia into canary_experimental

This commit is contained in:
Gliniak 2022-10-14 20:13:03 +02:00
commit d262214c1b
24 changed files with 6168 additions and 1531 deletions

View File

@ -3643,15 +3643,14 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl(
// flow.
reg::RB_COLOR_INFO color_infos[4];
float rt_clamp[4][4];
// Two UINT32_MAX if no components actually existing in the RT are written.
uint32_t rt_keep_masks[4][2];
for (uint32_t i = 0; i < 4; ++i) {
auto color_info = regs.Get<reg::RB_COLOR_INFO>(
reg::RB_COLOR_INFO::rt_register_indices[i]);
color_infos[i] = color_info;
if (edram_rov_used) {
// Get the mask for keeping previous color's components unmodified,
// or two UINT32_MAX if no colors actually existing in the RT are written.
DxbcShaderTranslator::ROV_GetColorFormatSystemConstants(
RenderTargetCache::GetPSIColorFormatInfo(
color_info.color_format, (normalized_color_mask >> (i * 4)) & 0b1111,
rt_clamp[i][0], rt_clamp[i][1], rt_clamp[i][2], rt_clamp[i][3],
rt_keep_masks[i][0], rt_keep_masks[i][1]);
@ -4021,11 +4020,10 @@ XE_NOINLINE void D3D12CommandProcessor::UpdateSystemConstantValues_Impl(
rt_base_dwords_scaled);
system_constants_.edram_rt_base_dwords_scaled[i] =
rt_base_dwords_scaled;
uint32_t format_flags = DxbcShaderTranslator::ROV_AddColorFormatFlags(
color_info.color_format);
uint32_t format_flags =
RenderTargetCache::AddPSIColorFormatFlags(color_info.color_format);
update_dirty_uint32_cmp(system_constants_.edram_rt_format_flags[i],
format_flags);
system_constants_.edram_rt_format_flags[i] = format_flags;
// Can't do float comparisons here because NaNs would result in always
// setting the dirty flag.

View File

@ -267,19 +267,6 @@ class DxbcShaderTranslator : public ShaderTranslator {
};
static_assert(kSysFlag_Count <= 32, "Too many flags in the system constants");
// Appended to the format in the format constant.
enum : uint32_t {
// Starting from bit 4 because the format itself needs 4 bits.
kRTFormatFlag_64bpp_Shift = 4,
// Requires clamping of blending sources and factors.
kRTFormatFlag_FixedPointColor_Shift,
kRTFormatFlag_FixedPointAlpha_Shift,
kRTFormatFlag_64bpp = 1u << kRTFormatFlag_64bpp_Shift,
kRTFormatFlag_FixedPointColor = 1u << kRTFormatFlag_FixedPointColor_Shift,
kRTFormatFlag_FixedPointAlpha = 1u << kRTFormatFlag_FixedPointAlpha_Shift,
};
// IF SYSTEM CONSTANTS ARE CHANGED OR ADDED, THE FOLLOWING MUST BE UPDATED:
// - SystemConstants::Index enum.
// - system_constant_rdef_.
@ -383,7 +370,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
uint32_t edram_rt_base_dwords_scaled[4];
// RT format combined with kRTFormatFlags.
// RT format combined with RenderTargetCache::kPSIColorFormatFlag values
// (pass via RenderTargetCache::AddPSIColorFormatFlags).
uint32_t edram_rt_format_flags[4];
// Format info - values to clamp the color to before blending or storing.
@ -524,40 +512,6 @@ class DxbcShaderTranslator : public ShaderTranslator {
kEdram,
};
// Returns the format with internal flags for passing via the
// edram_rt_format_flags system constant.
static constexpr uint32_t ROV_AddColorFormatFlags(
xenos::ColorRenderTargetFormat format) {
uint32_t format_flags = uint32_t(format);
if (format == xenos::ColorRenderTargetFormat::k_16_16_16_16 ||
format == xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT ||
format == xenos::ColorRenderTargetFormat::k_32_32_FLOAT) {
format_flags |= kRTFormatFlag_64bpp;
}
if (format == xenos::ColorRenderTargetFormat::k_8_8_8_8 ||
format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA ||
format == xenos::ColorRenderTargetFormat::k_2_10_10_10 ||
format == xenos::ColorRenderTargetFormat::k_16_16 ||
format == xenos::ColorRenderTargetFormat::k_16_16_16_16 ||
format == xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10) {
format_flags |=
kRTFormatFlag_FixedPointColor | kRTFormatFlag_FixedPointAlpha;
} else if (format == xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT ||
format == xenos::ColorRenderTargetFormat::
k_2_10_10_10_FLOAT_AS_16_16_16_16) {
format_flags |= kRTFormatFlag_FixedPointAlpha;
}
return format_flags;
}
// Returns the bits that need to be added to the RT flags constant - needs to
// be done externally, not in SetColorFormatConstants, because the flags
// contain other state.
static void ROV_GetColorFormatSystemConstants(
xenos::ColorRenderTargetFormat format, uint32_t write_mask,
float& clamp_rgb_low, float& clamp_alpha_low, float& clamp_rgb_high,
float& clamp_alpha_high, uint32_t& keep_mask_low,
uint32_t& keep_mask_high);
uint64_t GetDefaultVertexShaderModification(
uint32_t dynamic_addressable_register_count,
Shader::HostVertexShaderType host_vertex_shader_type =
@ -772,6 +726,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
// Whether it's possible and worth skipping running the translated shader for
// 2x2 quads.
bool ROV_IsDepthStencilEarly() const {
assert_true(edram_rov_used_);
return !is_depth_only_pixel_shader_ && !current_shader().writes_depth() &&
!current_shader().is_valid_memexport_used();
}

View File

@ -14,139 +14,13 @@
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/gpu/draw_util.h"
#include "xenia/gpu/render_target_cache.h"
#include "xenia/gpu/texture_cache.h"
namespace xe {
namespace gpu {
using namespace ucode;
void DxbcShaderTranslator::ROV_GetColorFormatSystemConstants(
xenos::ColorRenderTargetFormat format, uint32_t write_mask,
float& clamp_rgb_low, float& clamp_alpha_low, float& clamp_rgb_high,
float& clamp_alpha_high, uint32_t& keep_mask_low,
uint32_t& keep_mask_high) {
keep_mask_low = keep_mask_high = 0;
switch (format) {
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
clamp_rgb_low = clamp_alpha_low = 0.0f;
clamp_rgb_high = clamp_alpha_high = 1.0f;
for (uint32_t i = 0; i < 4; ++i) {
if (!(write_mask & (1 << i))) {
keep_mask_low |= uint32_t(0xFF) << (i * 8);
}
}
} break;
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: {
clamp_rgb_low = clamp_alpha_low = 0.0f;
clamp_rgb_high = clamp_alpha_high = 1.0f;
for (uint32_t i = 0; i < 3; ++i) {
if (!(write_mask & (1 << i))) {
keep_mask_low |= uint32_t(0x3FF) << (i * 10);
}
}
if (!(write_mask & 0b1000)) {
keep_mask_low |= uint32_t(3) << 30;
}
} break;
case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT:
case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: {
clamp_rgb_low = clamp_alpha_low = 0.0f;
clamp_rgb_high = 31.875f;
clamp_alpha_high = 1.0f;
for (uint32_t i = 0; i < 3; ++i) {
if (!(write_mask & (1 << i))) {
keep_mask_low |= uint32_t(0x3FF) << (i * 10);
}
}
if (!(write_mask & 0b1000)) {
keep_mask_low |= uint32_t(3) << 30;
}
} break;
case xenos::ColorRenderTargetFormat::k_16_16:
case xenos::ColorRenderTargetFormat::k_16_16_16_16:
// Alpha clamping affects blending source, so it's non-zero for alpha for
// k_16_16 (the render target is fixed-point). There's one deviation from
// how Direct3D 11.3 functional specification defines SNorm conversion
// (NaN should be 0, not the lowest negative number), but NaN handling in
// output shouldn't be very important.
clamp_rgb_low = clamp_alpha_low = -32.0f;
clamp_rgb_high = clamp_alpha_high = 32.0f;
if (!(write_mask & 0b0001)) {
keep_mask_low |= 0xFFFFu;
}
if (!(write_mask & 0b0010)) {
keep_mask_low |= 0xFFFF0000u;
}
if (format == xenos::ColorRenderTargetFormat::k_16_16_16_16) {
if (!(write_mask & 0b0100)) {
keep_mask_high |= 0xFFFFu;
}
if (!(write_mask & 0b1000)) {
keep_mask_high |= 0xFFFF0000u;
}
} else {
write_mask &= 0b0011;
}
break;
case xenos::ColorRenderTargetFormat::k_16_16_FLOAT:
case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
// No NaNs on the Xbox 360 GPU, though can't use the extended range with
// f32tof16.
clamp_rgb_low = clamp_alpha_low = -65504.0f;
clamp_rgb_high = clamp_alpha_high = 65504.0f;
if (!(write_mask & 0b0001)) {
keep_mask_low |= 0xFFFFu;
}
if (!(write_mask & 0b0010)) {
keep_mask_low |= 0xFFFF0000u;
}
if (format == xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT) {
if (!(write_mask & 0b0100)) {
keep_mask_high |= 0xFFFFu;
}
if (!(write_mask & 0b1000)) {
keep_mask_high |= 0xFFFF0000u;
}
} else {
write_mask &= 0b0011;
}
break;
case xenos::ColorRenderTargetFormat::k_32_FLOAT:
// No clamping - let min/max always pick the original value.
clamp_rgb_low = clamp_alpha_low = clamp_rgb_high = clamp_alpha_high =
std::nanf("");
write_mask &= 0b0001;
if (!(write_mask & 0b0001)) {
keep_mask_low = ~uint32_t(0);
}
break;
case xenos::ColorRenderTargetFormat::k_32_32_FLOAT:
// No clamping - let min/max always pick the original value.
clamp_rgb_low = clamp_alpha_low = clamp_rgb_high = clamp_alpha_high =
std::nanf("");
write_mask &= 0b0011;
if (!(write_mask & 0b0001)) {
keep_mask_low = ~uint32_t(0);
}
if (!(write_mask & 0b0010)) {
keep_mask_high = ~uint32_t(0);
}
break;
default:
assert_unhandled_case(format);
// Disable invalid render targets.
write_mask = 0;
break;
}
// Special case handled in the shaders for empty write mask to completely skip
// a disabled render target: all keep bits are set.
if (!write_mask) {
keep_mask_low = keep_mask_high = ~uint32_t(0);
}
}
void DxbcShaderTranslator::StartPixelShader_LoadROVParameters() {
bool any_color_targets_written = current_shader().writes_color_targets() != 0;
@ -484,8 +358,8 @@ void DxbcShaderTranslator::StartPixelShader_LoadROVParameters() {
{
// Copy the 4x AA coverage to system_temp_rov_params_.x, making top-right
// the sample [2] and bottom-left the sample [1] (the opposite of Direct3D
// 12), because on the Xbox 360, 2x MSAA doubles the storage width, 4x MSAA
// doubles the storage height.
// 12), because on the Xbox 360, 2x MSAA doubles the storage height, 4x MSAA
// doubles the storage width.
// Flip samples in bits 0:1 to bits 29:30.
a_.OpBFRev(dxbc::Dest::R(system_temp_rov_params_, 0b0001),
dxbc::Src::VCoverage());
@ -1304,7 +1178,7 @@ void DxbcShaderTranslator::ROV_UnpackColor(
// k_8_8_8_8_GAMMA
// ***************************************************************************
for (uint32_t i = 0; i < 2; ++i) {
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
i ? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA
: xenos::ColorRenderTargetFormat::k_8_8_8_8)));
// Unpack the components.
@ -1328,9 +1202,9 @@ void DxbcShaderTranslator::ROV_UnpackColor(
// k_2_10_10_10
// k_2_10_10_10_AS_10_10_10_10
// ***************************************************************************
a_.OpCase(dxbc::Src::LU(
ROV_AddColorFormatFlags(xenos::ColorRenderTargetFormat::k_2_10_10_10)));
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10)));
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10)));
{
// Unpack the components.
@ -1350,9 +1224,9 @@ void DxbcShaderTranslator::ROV_UnpackColor(
// k_2_10_10_10_FLOAT_AS_16_16_16_16
// https://github.com/Microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexConvert.cpp
// ***************************************************************************
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT)));
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16)));
{
// Unpack the alpha.
@ -1381,7 +1255,7 @@ void DxbcShaderTranslator::ROV_UnpackColor(
// k_16_16_16_16 (64bpp)
// ***************************************************************************
for (uint32_t i = 0; i < 2; ++i) {
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
i ? xenos::ColorRenderTargetFormat::k_16_16_16_16
: xenos::ColorRenderTargetFormat::k_16_16)));
dxbc::Dest color_components_dest(
@ -1404,7 +1278,7 @@ void DxbcShaderTranslator::ROV_UnpackColor(
// k_16_16_16_16_FLOAT (64bpp)
// ***************************************************************************
for (uint32_t i = 0; i < 2; ++i) {
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
i ? xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT
: xenos::ColorRenderTargetFormat::k_16_16_FLOAT)));
dxbc::Dest color_components_dest(
@ -1465,7 +1339,7 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor(
// k_8_8_8_8_GAMMA
// ***************************************************************************
for (uint32_t i = 0; i < 2; ++i) {
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
i ? xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA
: xenos::ColorRenderTargetFormat::k_8_8_8_8)));
for (uint32_t j = 0; j < 4; ++j) {
@ -1496,9 +1370,9 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor(
// k_2_10_10_10
// k_2_10_10_10_AS_10_10_10_10
// ***************************************************************************
a_.OpCase(dxbc::Src::LU(
ROV_AddColorFormatFlags(xenos::ColorRenderTargetFormat::k_2_10_10_10)));
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10)));
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10)));
for (uint32_t i = 0; i < 4; ++i) {
// Denormalize and convert to fixed-point.
@ -1518,9 +1392,9 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor(
// k_2_10_10_10_FLOAT_AS_16_16_16_16
// https://github.com/Microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexConvert.cpp
// ***************************************************************************
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT)));
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16)));
{
// Convert red directly to the destination, which may be the same as the
@ -1550,7 +1424,7 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor(
// k_16_16_16_16 (64bpp)
// ***************************************************************************
for (uint32_t i = 0; i < 2; ++i) {
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
i ? xenos::ColorRenderTargetFormat::k_16_16_16_16
: xenos::ColorRenderTargetFormat::k_16_16)));
for (uint32_t j = 0; j < (uint32_t(2) << i); ++j) {
@ -1582,7 +1456,7 @@ void DxbcShaderTranslator::ROV_PackPreClampedColor(
// k_16_16_16_16_FLOAT (64bpp)
// ***************************************************************************
for (uint32_t i = 0; i < 2; ++i) {
a_.OpCase(dxbc::Src::LU(ROV_AddColorFormatFlags(
a_.OpCase(dxbc::Src::LU(RenderTargetCache::AddPSIColorFormatFlags(
i ? xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT
: xenos::ColorRenderTargetFormat::k_16_16_FLOAT)));
for (uint32_t j = 0; j < (uint32_t(2) << i); ++j) {
@ -2230,7 +2104,8 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Load whether the render target is 64bpp to system_temp_rov_params_.y to
// get the needed relative sample address.
a_.OpAnd(dxbc::Dest::R(system_temp_rov_params_, 0b0010),
rt_format_flags_src, dxbc::Src::LU(kRTFormatFlag_64bpp));
rt_format_flags_src,
dxbc::Src::LU(RenderTargetCache::kPSIColorFormatFlag_64bpp));
// Choose the relative sample address for the render target to
// system_temp_rov_params_.y.
a_.OpMovC(dxbc::Dest::R(system_temp_rov_params_, 0b0010),
@ -2287,7 +2162,8 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the blending source color is fixed-point for clamping if it is.
// temp.x = whether color is fixed-point.
a_.OpAnd(temp_x_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_FixedPointColor));
dxbc::Src::LU(
RenderTargetCache::kPSIColorFormatFlag_FixedPointColor));
// Check if the blending source color is fixed-point and needs clamping.
// temp.x = free.
a_.OpIf(true, temp_x_src);
@ -2306,7 +2182,8 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the blending source alpha is fixed-point for clamping if it is.
// temp.x = whether alpha is fixed-point.
a_.OpAnd(temp_x_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_FixedPointAlpha));
dxbc::Src::LU(
RenderTargetCache::kPSIColorFormatFlag_FixedPointAlpha));
// Check if the blending source alpha is fixed-point and needs clamping.
// temp.x = free.
a_.OpIf(true, temp_x_src);
@ -2387,7 +2264,7 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the format is 64bpp to temp.w.
// temp.w = whether the render target is 64bpp.
a_.OpAnd(temp_w_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_64bpp));
dxbc::Src::LU(RenderTargetCache::kPSIColorFormatFlag_64bpp));
// Check if the format is 64bpp.
// temp.w = free.
a_.OpIf(true, temp_w_src);
@ -2478,8 +2355,10 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the render target color is fixed-point and the source
// color factor needs clamping to temp.x.
// temp.x = whether color is fixed-point.
a_.OpAnd(temp_x_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_FixedPointColor));
a_.OpAnd(
temp_x_dest, rt_format_flags_src,
dxbc::Src::LU(
RenderTargetCache::kPSIColorFormatFlag_FixedPointColor));
// Check if the source color factor needs clamping.
a_.OpIf(true, temp_x_src);
{
@ -2558,8 +2437,10 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the render target color is fixed-point and the
// destination color factor needs clamping to temp.x.
// temp.x = whether color is fixed-point.
a_.OpAnd(temp_x_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_FixedPointColor));
a_.OpAnd(
temp_x_dest, rt_format_flags_src,
dxbc::Src::LU(
RenderTargetCache::kPSIColorFormatFlag_FixedPointColor));
// Check if the destination color factor needs clamping.
a_.OpIf(true, temp_x_src);
{
@ -2701,8 +2582,10 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the render target alpha is fixed-point and the source
// alpha factor needs clamping to temp.y.
// temp.y = whether alpha is fixed-point.
a_.OpAnd(temp_y_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_FixedPointAlpha));
a_.OpAnd(
temp_y_dest, rt_format_flags_src,
dxbc::Src::LU(
RenderTargetCache::kPSIColorFormatFlag_FixedPointAlpha));
// Check if the source alpha factor needs clamping.
a_.OpIf(true, temp_y_src);
{
@ -2769,9 +2652,11 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// destination alpha factor needs clamping.
// alpha_is_fixed_temp.x = whether alpha is fixed-point.
uint32_t alpha_is_fixed_temp = PushSystemTemp();
a_.OpAnd(dxbc::Dest::R(alpha_is_fixed_temp, 0b0001),
a_.OpAnd(
dxbc::Dest::R(alpha_is_fixed_temp, 0b0001),
rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_FixedPointAlpha));
dxbc::Src::LU(
RenderTargetCache::kPSIColorFormatFlag_FixedPointAlpha));
// Check if the destination alpha factor needs clamping.
a_.OpIf(true,
dxbc::Src::R(alpha_is_fixed_temp, dxbc::Src::kXXXX));
@ -2925,7 +2810,7 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Get if the format is 64bpp to temp.z.
// temp.z = whether the render target is 64bpp.
a_.OpAnd(temp_z_dest, rt_format_flags_src,
dxbc::Src::LU(kRTFormatFlag_64bpp));
dxbc::Src::LU(RenderTargetCache::kPSIColorFormatFlag_64bpp));
// Check if the format is 64bpp.
// temp.z = free.
a_.OpIf(true, temp_z_src);
@ -2954,16 +2839,29 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() {
// Close the sample covered check.
a_.OpEndIf();
// Go to the next sample (samples are at +0, +(80*scale_x), +1,
// +(80*scale_x+1), so need to do +(80*scale_x), -(80*scale_x-1),
// +(80*scale_x) and -(80*scale_x+1) after each sample).
// Go to the next sample (samples are at +0, +(80*scale_x), +dwpp,
// +(80*scale_x+dwpp), so need to do +(80*scale_x), -(80*scale_x-dwpp),
// +(80*scale_x) and -(80*scale_x+dwpp) after each sample).
// Though no need to do this for the last sample as for the next render
// target, the address will be recalculated.
if (j < 3) {
if (j & 1) {
// temp.z = whether the render target is 64bpp.
a_.OpAnd(temp_z_dest, rt_format_flags_src,
dxbc::Src::LU(RenderTargetCache::kPSIColorFormatFlag_64bpp));
// temp.z = offset from the current sample to the next.
a_.OpMovC(temp_z_dest, temp_z_src,
dxbc::Src::LI(-int32_t(tile_width) + 2 * (2 - int32_t(j))),
dxbc::Src::LI(-int32_t(tile_width) + (2 - int32_t(j))));
// temp.z = free.
a_.OpIAdd(dxbc::Dest::R(system_temp_rov_params_, 0b0010),
dxbc::Src::R(system_temp_rov_params_, dxbc::Src::kYYYY),
dxbc::Src::LI((j & 1) ? -int32_t(tile_width) + 2 - j
: int32_t(tile_width)));
temp_z_src);
} else {
a_.OpIAdd(dxbc::Dest::R(system_temp_rov_params_, 0b0010),
dxbc::Src::R(system_temp_rov_params_, dxbc::Src::kYYYY),
dxbc::Src::LU(tile_width));
}
}
}
@ -2987,6 +2885,17 @@ void DxbcShaderTranslator::CompletePixelShader() {
if (current_shader().writes_color_target(0) &&
!IsForceEarlyDepthStencilGlobalFlagEnabled()) {
if (edram_rov_used_) {
// Check if the render target 0 was written to on the execution path.
uint32_t rt_0_written_temp = PushSystemTemp();
a_.OpAnd(dxbc::Dest::R(rt_0_written_temp, 0b0001),
dxbc::Src::R(system_temp_rov_params_, dxbc::Src::kXXXX),
dxbc::Src::LU(1 << 8));
a_.OpIf(true, dxbc::Src::R(rt_0_written_temp, dxbc::Src::kXXXX));
// Release rt_0_written_temp.
PopSystemTemp();
}
// Alpha test.
// X - mask, then masked result (SGPR for loading, VGPR for masking).
// Y - operation result (SGPR for mask operations, VGPR for alpha
@ -3057,11 +2966,16 @@ void DxbcShaderTranslator::CompletePixelShader() {
a_.OpEndIf();
// Release alpha_test_temp.
PopSystemTemp();
}
// Discard samples with alpha to coverage.
CompletePixelShader_AlphaToMask();
if (edram_rov_used_) {
// Close the render target 0 written check.
a_.OpEndIf();
}
}
// Write the values to the render targets. Not applying the exponent bias yet
// because the original 0 to 1 alpha value is needed for alpha to coverage,
// which is done differently for ROV and RTV/DSV.

View File

@ -207,6 +207,134 @@ DEFINE_bool(
namespace xe {
namespace gpu {
void RenderTargetCache::GetPSIColorFormatInfo(
xenos::ColorRenderTargetFormat format, uint32_t write_mask,
float& clamp_rgb_low, float& clamp_alpha_low, float& clamp_rgb_high,
float& clamp_alpha_high, uint32_t& keep_mask_low,
uint32_t& keep_mask_high) {
keep_mask_low = keep_mask_high = 0;
switch (format) {
case xenos::ColorRenderTargetFormat::k_8_8_8_8:
case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: {
clamp_rgb_low = clamp_alpha_low = 0.0f;
clamp_rgb_high = clamp_alpha_high = 1.0f;
for (uint32_t i = 0; i < 4; ++i) {
if (!(write_mask & (1 << i))) {
keep_mask_low |= uint32_t(0xFF) << (i * 8);
}
}
} break;
case xenos::ColorRenderTargetFormat::k_2_10_10_10:
case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: {
clamp_rgb_low = clamp_alpha_low = 0.0f;
clamp_rgb_high = clamp_alpha_high = 1.0f;
for (uint32_t i = 0; i < 3; ++i) {
if (!(write_mask & (1 << i))) {
keep_mask_low |= uint32_t(0x3FF) << (i * 10);
}
}
if (!(write_mask & 0b1000)) {
keep_mask_low |= uint32_t(3) << 30;
}
} break;
case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT:
case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: {
clamp_rgb_low = clamp_alpha_low = 0.0f;
clamp_rgb_high = 31.875f;
clamp_alpha_high = 1.0f;
for (uint32_t i = 0; i < 3; ++i) {
if (!(write_mask & (1 << i))) {
keep_mask_low |= uint32_t(0x3FF) << (i * 10);
}
}
if (!(write_mask & 0b1000)) {
keep_mask_low |= uint32_t(3) << 30;
}
} break;
case xenos::ColorRenderTargetFormat::k_16_16:
case xenos::ColorRenderTargetFormat::k_16_16_16_16:
// Alpha clamping affects blending source, so it's non-zero for alpha for
// k_16_16 (the render target is fixed-point). There's one deviation from
// how Direct3D 11.3 functional specification defines SNorm conversion
// (NaN should be 0, not the lowest negative number), and that needs to be
// handled separately.
clamp_rgb_low = clamp_alpha_low = -32.0f;
clamp_rgb_high = clamp_alpha_high = 32.0f;
if (!(write_mask & 0b0001)) {
keep_mask_low |= 0xFFFFu;
}
if (!(write_mask & 0b0010)) {
keep_mask_low |= 0xFFFF0000u;
}
if (format == xenos::ColorRenderTargetFormat::k_16_16_16_16) {
if (!(write_mask & 0b0100)) {
keep_mask_high |= 0xFFFFu;
}
if (!(write_mask & 0b1000)) {
keep_mask_high |= 0xFFFF0000u;
}
} else {
write_mask &= 0b0011;
}
break;
case xenos::ColorRenderTargetFormat::k_16_16_FLOAT:
case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
// No NaNs on the Xbox 360 GPU, though can't use the extended range with
// Direct3D and Vulkan conversions.
// TODO(Triang3l): Use the extended-range encoding in all implementations.
clamp_rgb_low = clamp_alpha_low = -65504.0f;
clamp_rgb_high = clamp_alpha_high = 65504.0f;
if (!(write_mask & 0b0001)) {
keep_mask_low |= 0xFFFFu;
}
if (!(write_mask & 0b0010)) {
keep_mask_low |= 0xFFFF0000u;
}
if (format == xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT) {
if (!(write_mask & 0b0100)) {
keep_mask_high |= 0xFFFFu;
}
if (!(write_mask & 0b1000)) {
keep_mask_high |= 0xFFFF0000u;
}
} else {
write_mask &= 0b0011;
}
break;
case xenos::ColorRenderTargetFormat::k_32_FLOAT:
// No clamping - let min/max always pick the original value.
clamp_rgb_low = clamp_alpha_low = clamp_rgb_high = clamp_alpha_high =
std::nanf("");
write_mask &= 0b0001;
if (!(write_mask & 0b0001)) {
keep_mask_low = ~uint32_t(0);
}
break;
case xenos::ColorRenderTargetFormat::k_32_32_FLOAT:
// No clamping - let min/max always pick the original value.
clamp_rgb_low = clamp_alpha_low = clamp_rgb_high = clamp_alpha_high =
std::nanf("");
write_mask &= 0b0011;
if (!(write_mask & 0b0001)) {
keep_mask_low = ~uint32_t(0);
}
if (!(write_mask & 0b0010)) {
keep_mask_high = ~uint32_t(0);
}
break;
default:
assert_unhandled_case(format);
// Disable invalid render targets.
write_mask = 0;
break;
}
// Special case handled in the shaders for empty write mask to completely skip
// a disabled render target: all keep bits are set.
if (!write_mask) {
keep_mask_low = keep_mask_high = ~uint32_t(0);
}
}
uint32_t RenderTargetCache::Transfer::GetRangeRectangles(
uint32_t start_tiles, uint32_t end_tiles, uint32_t base_tiles,
uint32_t pitch_tiles, xenos::MsaaSamples msaa_samples, bool is_64bpp,

View File

@ -113,6 +113,54 @@ class RenderTargetCache {
kSrgbToLinearExponent);
}
// Pixel shader interlock implementation helpers.
// Appended to the format in the format constant via bitwise OR.
enum : uint32_t {
kPSIColorFormatFlag_64bpp_Shift = xenos::kColorRenderTargetFormatBits,
// Requires clamping of blending sources and factors.
kPSIColorFormatFlag_FixedPointColor_Shift,
kPSIColorFormatFlag_FixedPointAlpha_Shift,
kPSIColorFormatFlag_64bpp = uint32_t(1) << kPSIColorFormatFlag_64bpp_Shift,
kPSIColorFormatFlag_FixedPointColor =
uint32_t(1) << kPSIColorFormatFlag_FixedPointColor_Shift,
kPSIColorFormatFlag_FixedPointAlpha =
uint32_t(1) << kPSIColorFormatFlag_FixedPointAlpha_Shift,
};
static constexpr uint32_t AddPSIColorFormatFlags(
xenos::ColorRenderTargetFormat format) {
uint32_t format_flags = uint32_t(format);
if (format == xenos::ColorRenderTargetFormat::k_16_16_16_16 ||
format == xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT ||
format == xenos::ColorRenderTargetFormat::k_32_32_FLOAT) {
format_flags |= kPSIColorFormatFlag_64bpp;
}
if (format == xenos::ColorRenderTargetFormat::k_8_8_8_8 ||
format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA ||
format == xenos::ColorRenderTargetFormat::k_2_10_10_10 ||
format == xenos::ColorRenderTargetFormat::k_16_16 ||
format == xenos::ColorRenderTargetFormat::k_16_16_16_16 ||
format == xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10) {
format_flags |= kPSIColorFormatFlag_FixedPointColor |
kPSIColorFormatFlag_FixedPointAlpha;
} else if (format == xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT ||
format == xenos::ColorRenderTargetFormat::
k_2_10_10_10_FLOAT_AS_16_16_16_16) {
format_flags |= kPSIColorFormatFlag_FixedPointAlpha;
}
return format_flags;
}
static void GetPSIColorFormatInfo(xenos::ColorRenderTargetFormat format,
uint32_t write_mask, float& clamp_rgb_low,
float& clamp_alpha_low,
float& clamp_rgb_high,
float& clamp_alpha_high,
uint32_t& keep_mask_low,
uint32_t& keep_mask_high);
virtual ~RenderTargetCache();
virtual Path GetPath() const = 0;

View File

@ -54,8 +54,10 @@ DEFINE_string(
"GPU");
DEFINE_bool(shader_output_bindless_resources, false,
"Output host shader with bindless resources used.", "GPU");
DEFINE_bool(shader_output_dxbc_rov, false,
"Output ROV-based output-merger code in DXBC pixel shaders.",
DEFINE_bool(
shader_output_pixel_shader_interlock, false,
"Output host shader with a render backend implementation based on pixel "
"shader interlock.",
"GPU");
namespace xe {
@ -124,12 +126,15 @@ int shader_compiler_main(const std::vector<std::string>& args) {
SpirvShaderTranslator::Features spirv_features(true);
if (cvars::shader_output_type == "spirv" ||
cvars::shader_output_type == "spirvtext") {
translator = std::make_unique<SpirvShaderTranslator>(spirv_features);
translator = std::make_unique<SpirvShaderTranslator>(
spirv_features, true, true,
cvars::shader_output_pixel_shader_interlock);
} else if (cvars::shader_output_type == "dxbc" ||
cvars::shader_output_type == "dxbctext") {
translator = std::make_unique<DxbcShaderTranslator>(
ui::GraphicsProvider::GpuVendorID(0),
cvars::shader_output_bindless_resources, cvars::shader_output_dxbc_rov);
cvars::shader_output_bindless_resources,
cvars::shader_output_pixel_shader_interlock);
} else {
// Just output microcode disassembly generated during microcode information
// gathering.

View File

@ -21,6 +21,7 @@
#include "third_party/glslang/SPIRV/GLSL.std.450.h"
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/base/string_buffer.h"
#include "xenia/gpu/spirv_shader.h"
namespace xe {
@ -31,6 +32,8 @@ SpirvShaderTranslator::Features::Features(bool all)
max_storage_buffer_range(all ? UINT32_MAX : (128 * 1024 * 1024)),
clip_distance(all),
cull_distance(all),
demote_to_helper_invocation(all),
fragment_shader_sample_interlock(all),
full_draw_index_uint32(all),
image_view_format_swizzle(all),
signed_zero_inf_nan_preserve_float32(all),
@ -42,6 +45,14 @@ SpirvShaderTranslator::Features::Features(
provider.device_properties().limits.maxStorageBufferRange),
clip_distance(provider.device_features().shaderClipDistance),
cull_distance(provider.device_features().shaderCullDistance),
demote_to_helper_invocation(
provider.device_extensions().ext_shader_demote_to_helper_invocation &&
provider.device_shader_demote_to_helper_invocation_features()
.shaderDemoteToHelperInvocation),
fragment_shader_sample_interlock(
provider.device_extensions().ext_fragment_shader_interlock &&
provider.device_fragment_shader_interlock_features()
.fragmentShaderSampleInterlock),
full_draw_index_uint32(provider.device_features().fullDrawIndexUint32) {
uint32_t device_version = provider.device_properties().apiVersion;
const ui::vulkan::VulkanProvider::DeviceExtensions& device_extensions =
@ -78,9 +89,6 @@ SpirvShaderTranslator::Features::Features(
}
}
SpirvShaderTranslator::SpirvShaderTranslator(const Features& features)
: features_(features) {}
uint64_t SpirvShaderTranslator::GetDefaultVertexShaderModification(
uint32_t dynamic_addressable_register_count,
Shader::HostVertexShaderType host_vertex_shader_type) const {
@ -99,6 +107,19 @@ uint64_t SpirvShaderTranslator::GetDefaultPixelShaderModification(
return shader_modification.value;
}
std::vector<uint8_t> SpirvShaderTranslator::CreateDepthOnlyFragmentShader() {
is_depth_only_fragment_shader_ = true;
// TODO(Triang3l): Handle in a nicer way (is_depth_only_fragment_shader_ is a
// leftover from when a Shader object wasn't used during translation).
Shader shader(xenos::ShaderType::kPixel, 0, nullptr, 0);
StringBuffer instruction_disassembly_buffer;
shader.AnalyzeUcode(instruction_disassembly_buffer);
Shader::Translation& translation = *shader.GetOrCreateTranslation(0);
TranslateAnalyzedShader(translation);
is_depth_only_fragment_shader_ = false;
return translation.translated_binary();
}
void SpirvShaderTranslator::Reset() {
ShaderTranslator::Reset();
@ -109,6 +130,7 @@ void SpirvShaderTranslator::Reset() {
input_point_coordinates_ = spv::NoResult;
input_fragment_coordinates_ = spv::NoResult;
input_front_facing_ = spv::NoResult;
input_sample_mask_ = spv::NoResult;
std::fill(input_output_interpolators_.begin(),
input_output_interpolators_.end(), spv::NoResult);
output_point_coordinates_ = spv::NoResult;
@ -120,6 +142,8 @@ void SpirvShaderTranslator::Reset() {
main_interface_.clear();
var_main_registers_ = spv::NoResult;
var_main_point_size_edge_flag_kill_vertex_ = spv::NoResult;
var_main_kill_pixel_ = spv::NoResult;
var_main_fsi_color_written_ = spv::NoResult;
main_switch_op_.reset();
main_switch_next_pc_phi_operands_.clear();
@ -217,6 +241,10 @@ void SpirvShaderTranslator::StartTranslation() {
size_t offset;
spv::Id type;
};
spv::Id type_float4_array_4 = builder_->makeArrayType(
type_float4_, builder_->makeUintConstant(4), sizeof(float) * 4);
builder_->addDecoration(type_float4_array_4, spv::DecorationArrayStride,
sizeof(float) * 4);
spv::Id type_uint4_array_2 = builder_->makeArrayType(
type_uint4_, builder_->makeUintConstant(2), sizeof(uint32_t) * 4);
builder_->addDecoration(type_uint4_array_2, spv::DecorationArrayStride,
@ -250,8 +278,37 @@ void SpirvShaderTranslator::StartTranslation() {
type_uint4_array_4},
{"alpha_test_reference", offsetof(SystemConstants, alpha_test_reference),
type_float_},
{"edram_32bpp_tile_pitch_dwords_scaled",
offsetof(SystemConstants, edram_32bpp_tile_pitch_dwords_scaled),
type_uint_},
{"edram_depth_base_dwords_scaled",
offsetof(SystemConstants, edram_depth_base_dwords_scaled), type_uint_},
{"color_exp_bias", offsetof(SystemConstants, color_exp_bias),
type_float4_},
{"edram_poly_offset_front_scale",
offsetof(SystemConstants, edram_poly_offset_front_scale), type_float_},
{"edram_poly_offset_back_scale",
offsetof(SystemConstants, edram_poly_offset_back_scale), type_float_},
{"edram_poly_offset_front_offset",
offsetof(SystemConstants, edram_poly_offset_front_offset), type_float_},
{"edram_poly_offset_back_offset",
offsetof(SystemConstants, edram_poly_offset_back_offset), type_float_},
{"edram_stencil_front", offsetof(SystemConstants, edram_stencil_front),
type_uint2_},
{"edram_stencil_back", offsetof(SystemConstants, edram_stencil_back),
type_uint2_},
{"edram_rt_base_dwords_scaled",
offsetof(SystemConstants, edram_rt_base_dwords_scaled), type_uint4_},
{"edram_rt_format_flags",
offsetof(SystemConstants, edram_rt_format_flags), type_uint4_},
{"edram_rt_blend_factors_ops",
offsetof(SystemConstants, edram_rt_blend_factors_ops), type_uint4_},
{"edram_rt_keep_mask", offsetof(SystemConstants, edram_rt_keep_mask),
type_uint4_array_2},
{"edram_rt_clamp", offsetof(SystemConstants, edram_rt_clamp),
type_float4_array_4},
{"edram_blend_constant", offsetof(SystemConstants, edram_blend_constant),
type_float4_},
};
id_vector_temp_.clear();
id_vector_temp_.reserve(xe::countof(system_constants));
@ -281,6 +338,7 @@ void SpirvShaderTranslator::StartTranslation() {
main_interface_.push_back(uniform_system_constants_);
}
if (!is_depth_only_fragment_shader_) {
// Common uniform buffer - float constants.
uint32_t float_constant_count =
current_shader().constant_register_map().float_count;
@ -289,11 +347,11 @@ void SpirvShaderTranslator::StartTranslation() {
id_vector_temp_.push_back(builder_->makeArrayType(
type_float4_, builder_->makeUintConstant(float_constant_count),
sizeof(float) * 4));
// Currently (as of October 24, 2020) makeArrayType only uses the stride to
// check if deduplication can be done - the array stride decoration needs to
// be applied explicitly.
builder_->addDecoration(id_vector_temp_.back(), spv::DecorationArrayStride,
sizeof(float) * 4);
// Currently (as of October 24, 2020) makeArrayType only uses the stride
// to check if deduplication can be done - the array stride decoration
// needs to be applied explicitly.
builder_->addDecoration(id_vector_temp_.back(),
spv::DecorationArrayStride, sizeof(float) * 4);
spv::Id type_float_constants =
builder_->makeStructType(id_vector_temp_, "XeFloatConstants");
builder_->addMemberName(type_float_constants, 0, "float_constants");
@ -346,7 +404,8 @@ void SpirvShaderTranslator::StartTranslation() {
builder_->addDecoration(uniform_bool_loop_constants_,
spv::DecorationDescriptorSet,
int(kDescriptorSetConstants));
builder_->addDecoration(uniform_bool_loop_constants_, spv::DecorationBinding,
builder_->addDecoration(uniform_bool_loop_constants_,
spv::DecorationBinding,
int(kConstantBufferBoolLoop));
if (features_.spirv_version >= spv::Spv_1_4) {
main_interface_.push_back(uniform_bool_loop_constants_);
@ -363,8 +422,8 @@ void SpirvShaderTranslator::StartTranslation() {
spv::Id type_fetch_constants =
builder_->makeStructType(id_vector_temp_, "XeFetchConstants");
builder_->addMemberName(type_fetch_constants, 0, "fetch_constants");
builder_->addMemberDecoration(type_fetch_constants, 0, spv::DecorationOffset,
0);
builder_->addMemberDecoration(type_fetch_constants, 0,
spv::DecorationOffset, 0);
builder_->addDecoration(type_fetch_constants, spv::DecorationBlock);
uniform_fetch_constants_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassUniform, type_fetch_constants,
@ -388,6 +447,8 @@ void SpirvShaderTranslator::StartTranslation() {
spv::Id type_shared_memory =
builder_->makeStructType(id_vector_temp_, "XeSharedMemory");
builder_->addMemberName(type_shared_memory, 0, "shared_memory");
builder_->addMemberDecoration(type_shared_memory, 0,
spv::DecorationRestrict);
// TODO(Triang3l): Make writable when memexport is implemented.
builder_->addMemberDecoration(type_shared_memory, 0,
spv::DecorationNonWritable);
@ -409,12 +470,14 @@ void SpirvShaderTranslator::StartTranslation() {
features_.spirv_version >= spv::Spv_1_3 ? spv::StorageClassStorageBuffer
: spv::StorageClassUniform,
type_shared_memory, "xe_shared_memory");
builder_->addDecoration(buffers_shared_memory_, spv::DecorationDescriptorSet,
builder_->addDecoration(buffers_shared_memory_,
spv::DecorationDescriptorSet,
int(kDescriptorSetSharedMemoryAndEdram));
builder_->addDecoration(buffers_shared_memory_, spv::DecorationBinding, 0);
if (features_.spirv_version >= spv::Spv_1_4) {
main_interface_.push_back(buffers_shared_memory_);
}
}
if (is_vertex_shader()) {
StartVertexOrTessEvalShaderBeforeMain();
@ -438,6 +501,7 @@ void SpirvShaderTranslator::StartTranslation() {
uniform_system_constants_, id_vector_temp_),
spv::NoPrecision);
if (!is_depth_only_fragment_shader_) {
// Begin ucode translation. Initialize everything, even without defined
// defaults, for safety.
var_main_predicate_ = builder_->createVariable(
@ -474,6 +538,7 @@ void SpirvShaderTranslator::StartTranslation() {
builder_->createVariable(spv::NoPrecision, spv::StorageClassFunction,
type_register_array, "xe_var_registers");
}
}
// Write the execution model-specific prologue with access to variables in the
// main function.
@ -483,6 +548,10 @@ void SpirvShaderTranslator::StartTranslation() {
StartFragmentShaderInMain();
}
if (is_depth_only_fragment_shader_) {
return;
}
// Open the main loop.
spv::Block& main_loop_pre_header = *builder_->getBuildPoint();
main_loop_header_ = &builder_->makeNewBlock();
@ -551,6 +620,7 @@ void SpirvShaderTranslator::StartTranslation() {
}
std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
if (!is_depth_only_fragment_shader_) {
// Close flow control within the last switch case.
CloseExecConditionals();
bool has_main_switch = !current_shader().label_addresses().empty();
@ -566,23 +636,24 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
builder_->setBuildPoint(main_switch_header_);
builder_->getBuildPoint()->addInstruction(std::move(main_switch_op_));
// Build the main switch merge, breaking out of the loop after falling
// through the end or breaking from exece (only continuing if a jump - from
// a guest loop or from jmp/call - was made).
// through the end or breaking from exece (only continuing if a jump -
// from a guest loop or from jmp/call - was made).
function_main_->addBlock(main_switch_merge_);
builder_->setBuildPoint(main_switch_merge_);
builder_->createBranch(main_loop_merge_);
}
// Main loop continuation - choose the program counter based on the path
// taken (-1 if not from a jump as a safe fallback, which would result in not
// hitting any switch case and reaching the final break in the body).
// taken (-1 if not from a jump as a safe fallback, which would result in
// not hitting any switch case and reaching the final break in the body).
function_main_->addBlock(main_loop_continue_);
builder_->setBuildPoint(main_loop_continue_);
if (has_main_switch) {
// OpPhi, if added, must be the first in the block.
// If labels were added, but not jumps (for example, due to the call
// instruction not being implemented as of October 18, 2020), send an
// impossible program counter value (-1) to the OpPhi at the next iteration.
// impossible program counter value (-1) to the OpPhi at the next
// iteration.
if (main_switch_next_pc_phi_operands_.empty()) {
main_switch_next_pc_phi_operands_.push_back(
builder_->makeIntConstant(-1));
@ -590,18 +661,21 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
std::unique_ptr<spv::Instruction> main_loop_pc_next_op =
std::make_unique<spv::Instruction>(
main_loop_pc_next_, type_int_,
main_switch_next_pc_phi_operands_.size() >= 2 ? spv::OpPhi
main_switch_next_pc_phi_operands_.size() >= 2
? spv::OpPhi
: spv::OpCopyObject);
for (spv::Id operand : main_switch_next_pc_phi_operands_) {
main_loop_pc_next_op->addIdOperand(operand);
}
builder_->getBuildPoint()->addInstruction(std::move(main_loop_pc_next_op));
builder_->getBuildPoint()->addInstruction(
std::move(main_loop_pc_next_op));
}
builder_->createBranch(main_loop_header_);
// Add the main loop merge block and go back to the function.
function_main_->addBlock(main_loop_merge_);
builder_->setBuildPoint(main_loop_merge_);
}
if (is_vertex_shader()) {
CompleteVertexOrTessEvalShaderInMain();
@ -622,6 +696,20 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
builder_->addExecutionMode(function_main_,
spv::ExecutionModeEarlyFragmentTests);
}
if (edram_fragment_shader_interlock_) {
// Accessing per-sample values, so interlocking just when there's common
// coverage is enough if the device exposes that.
if (features_.fragment_shader_sample_interlock) {
builder_->addCapability(
spv::CapabilityFragmentShaderSampleInterlockEXT);
builder_->addExecutionMode(function_main_,
spv::ExecutionModeSampleInterlockOrderedEXT);
} else {
builder_->addCapability(spv::CapabilityFragmentShaderPixelInterlockEXT);
builder_->addExecutionMode(function_main_,
spv::ExecutionModePixelInterlockOrderedEXT);
}
}
} else {
assert_true(is_vertex_shader());
execution_model = IsSpirvTessEvalShader()
@ -649,8 +737,10 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
entry_point->addIdOperand(interface_id);
}
if (!is_depth_only_fragment_shader_) {
// Specify the binding indices for samplers when the number of textures is
// known, as samplers are located after images in the texture descriptor set.
// known, as samplers are located after images in the texture descriptor
// set.
size_t texture_binding_count = texture_bindings_.size();
size_t sampler_binding_count = sampler_bindings_.size();
for (size_t i = 0; i < sampler_binding_count; ++i) {
@ -658,6 +748,7 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
spv::DecorationBinding,
int(texture_binding_count + i));
}
}
// TODO(Triang3l): Avoid copy?
std::vector<unsigned int> module_uints;
@ -1682,13 +1773,48 @@ void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderInMain() {
void SpirvShaderTranslator::StartFragmentShaderBeforeMain() {
Modification shader_modification = GetSpirvShaderModification();
if (edram_fragment_shader_interlock_) {
builder_->addExtension("SPV_EXT_fragment_shader_interlock");
// EDRAM buffer uint[].
id_vector_temp_.clear();
id_vector_temp_.push_back(builder_->makeRuntimeArray(type_uint_));
// Storage buffers have std430 packing, no padding to 4-component vectors.
builder_->addDecoration(id_vector_temp_.back(), spv::DecorationArrayStride,
sizeof(uint32_t));
spv::Id type_edram = builder_->makeStructType(id_vector_temp_, "XeEdram");
builder_->addMemberName(type_edram, 0, "edram");
builder_->addMemberDecoration(type_edram, 0, spv::DecorationCoherent);
builder_->addMemberDecoration(type_edram, 0, spv::DecorationRestrict);
builder_->addMemberDecoration(type_edram, 0, spv::DecorationOffset, 0);
builder_->addDecoration(type_edram, features_.spirv_version >= spv::Spv_1_3
? spv::DecorationBlock
: spv::DecorationBufferBlock);
buffer_edram_ = builder_->createVariable(
spv::NoPrecision,
features_.spirv_version >= spv::Spv_1_3 ? spv::StorageClassStorageBuffer
: spv::StorageClassUniform,
type_edram, "xe_edram");
builder_->addDecoration(buffer_edram_, spv::DecorationDescriptorSet,
int(kDescriptorSetSharedMemoryAndEdram));
builder_->addDecoration(buffer_edram_, spv::DecorationBinding, 1);
if (features_.spirv_version >= spv::Spv_1_4) {
main_interface_.push_back(buffer_edram_);
}
}
bool param_gen_needed = !is_depth_only_fragment_shader_ &&
GetPsParamGenInterpolator() != UINT32_MAX;
if (!is_depth_only_fragment_shader_) {
uint32_t input_location = 0;
// Interpolator inputs.
{
uint32_t interpolators_remaining = GetModificationInterpolatorMask();
uint32_t interpolator_index;
while (xe::bit_scan_forward(interpolators_remaining, &interpolator_index)) {
while (
xe::bit_scan_forward(interpolators_remaining, &interpolator_index)) {
interpolators_remaining &= ~(UINT32_C(1) << interpolator_index);
spv::Id interpolator = builder_->createVariable(
spv::NoPrecision, spv::StorageClassInput, type_float4_,
@ -1705,26 +1831,25 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() {
}
}
bool param_gen_needed = GetPsParamGenInterpolator() != UINT32_MAX;
// Point coordinate input.
if (shader_modification.pixel.param_gen_point) {
if (param_gen_needed) {
input_point_coordinates_ =
builder_->createVariable(spv::NoPrecision, spv::StorageClassInput,
type_float2_, "xe_in_point_coordinates");
builder_->addDecoration(input_point_coordinates_, spv::DecorationLocation,
int(input_location));
builder_->addDecoration(input_point_coordinates_,
spv::DecorationLocation, int(input_location));
main_interface_.push_back(input_point_coordinates_);
}
++input_location;
}
}
// Fragment coordinates.
// TODO(Triang3l): More conditions - fragment shader interlock render backend,
// alpha to coverage (if RT 0 is written, and there's no early depth /
// stencil), depth writing in the fragment shader (per-sample if supported).
if (param_gen_needed) {
// TODO(Triang3l): More conditions - alpha to coverage (if RT 0 is written,
// and there's no early depth / stencil), depth writing in the fragment shader
// (per-sample if supported).
if (edram_fragment_shader_interlock_ || param_gen_needed) {
input_fragment_coordinates_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassInput, type_float4_, "gl_FragCoord");
builder_->addDecoration(input_fragment_coordinates_, spv::DecorationBuiltIn,
@ -1733,9 +1858,9 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() {
}
// Is front facing.
// TODO(Triang3l): Needed for stencil in the fragment shader interlock render
// backend.
if (param_gen_needed && !GetSpirvShaderModification().pixel.param_gen_point) {
if (edram_fragment_shader_interlock_ ||
(param_gen_needed &&
!GetSpirvShaderModification().pixel.param_gen_point)) {
input_front_facing_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassInput, type_bool_, "gl_FrontFacing");
builder_->addDecoration(input_front_facing_, spv::DecorationBuiltIn,
@ -1743,33 +1868,165 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() {
main_interface_.push_back(input_front_facing_);
}
// Framebuffer attachment outputs.
std::fill(output_fragment_data_.begin(), output_fragment_data_.end(),
spv::NoResult);
static const char* const kFragmentDataNames[] = {
// Sample mask input.
if (edram_fragment_shader_interlock_) {
// SampleMask depends on SampleRateShading in some SPIR-V revisions.
builder_->addCapability(spv::CapabilitySampleRateShading);
input_sample_mask_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassInput,
builder_->makeArrayType(type_int_, builder_->makeUintConstant(1), 0),
"gl_SampleMaskIn");
builder_->addDecoration(input_sample_mask_, spv::DecorationFlat);
builder_->addDecoration(input_sample_mask_, spv::DecorationBuiltIn,
spv::BuiltInSampleMask);
main_interface_.push_back(input_sample_mask_);
}
if (!is_depth_only_fragment_shader_) {
// Framebuffer color attachment outputs.
if (!edram_fragment_shader_interlock_) {
std::fill(output_or_var_fragment_data_.begin(),
output_or_var_fragment_data_.end(), spv::NoResult);
static const char* const kFragmentDataOutputNames[] = {
"xe_out_fragment_data_0",
"xe_out_fragment_data_1",
"xe_out_fragment_data_2",
"xe_out_fragment_data_3",
};
uint32_t color_targets_remaining = current_shader().writes_color_targets();
uint32_t color_targets_remaining =
current_shader().writes_color_targets();
uint32_t color_target_index;
while (xe::bit_scan_forward(color_targets_remaining, &color_target_index)) {
while (
xe::bit_scan_forward(color_targets_remaining, &color_target_index)) {
color_targets_remaining &= ~(UINT32_C(1) << color_target_index);
spv::Id output_fragment_data_rt = builder_->createVariable(
spv::NoPrecision, spv::StorageClassOutput, type_float4_,
kFragmentDataNames[color_target_index]);
output_fragment_data_[color_target_index] = output_fragment_data_rt;
builder_->addDecoration(output_fragment_data_rt, spv::DecorationLocation,
kFragmentDataOutputNames[color_target_index]);
output_or_var_fragment_data_[color_target_index] =
output_fragment_data_rt;
builder_->addDecoration(output_fragment_data_rt,
spv::DecorationLocation,
int(color_target_index));
// Make invariant as pixel shaders may be used for various precise
// computations.
builder_->addDecoration(output_fragment_data_rt, spv::DecorationInvariant);
builder_->addDecoration(output_fragment_data_rt,
spv::DecorationInvariant);
main_interface_.push_back(output_fragment_data_rt);
}
}
}
}
void SpirvShaderTranslator::StartFragmentShaderInMain() {
// Set up pixel killing from within the translated shader without affecting
// the control flow (unlike with OpKill), similarly to how pixel killing works
// on the Xenos, and also keeping a single critical section exit and return
// for safety across different Vulkan implementations with fragment shader
// interlock.
if (current_shader().kills_pixels()) {
if (features_.demote_to_helper_invocation) {
// TODO(Triang3l): Promoted to SPIR-V 1.6 - don't add the extension there.
builder_->addExtension("SPV_EXT_demote_to_helper_invocation");
builder_->addCapability(spv::CapabilityDemoteToHelperInvocationEXT);
} else {
var_main_kill_pixel_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassFunction, type_bool_,
"xe_var_kill_pixel", builder_->makeBoolConstant(false));
}
// For killing with fragment shader interlock when demotion is supported,
// using OpIsHelperInvocationEXT to avoid allocating a variable in addition
// to the execution mask GPUs naturally have.
}
if (edram_fragment_shader_interlock_) {
// Initialize color output variables with fragment shader interlock.
std::fill(output_or_var_fragment_data_.begin(),
output_or_var_fragment_data_.end(), spv::NoResult);
var_main_fsi_color_written_ = spv::NoResult;
uint32_t color_targets_written = current_shader().writes_color_targets();
if (color_targets_written) {
static const char* const kFragmentDataVariableNames[] = {
"xe_var_fragment_data_0",
"xe_var_fragment_data_1",
"xe_var_fragment_data_2",
"xe_var_fragment_data_3",
};
uint32_t color_targets_remaining = color_targets_written;
uint32_t color_target_index;
while (
xe::bit_scan_forward(color_targets_remaining, &color_target_index)) {
color_targets_remaining &= ~(UINT32_C(1) << color_target_index);
output_or_var_fragment_data_[color_target_index] =
builder_->createVariable(
spv::NoPrecision, spv::StorageClassFunction, type_float4_,
kFragmentDataVariableNames[color_target_index],
const_float4_0_);
}
var_main_fsi_color_written_ = builder_->createVariable(
spv::NoPrecision, spv::StorageClassFunction, type_uint_,
"xe_var_fsi_color_written", const_uint_0_);
}
}
if (edram_fragment_shader_interlock_ && FSI_IsDepthStencilEarly()) {
spv::Id msaa_samples = LoadMsaaSamplesFromFlags();
FSI_LoadSampleMask(msaa_samples);
FSI_LoadEdramOffsets(msaa_samples);
builder_->createNoResultOp(spv::OpBeginInvocationInterlockEXT);
FSI_DepthStencilTest(msaa_samples, false);
if (!is_depth_only_fragment_shader_) {
// Skip the rest of the shader if the whole quad (due to derivatives) has
// failed the depth / stencil test, and there are no depth and stencil
// values to conditionally write after running the shader to check if
// samples don't additionally need to be discarded.
spv::Id quad_needs_execution = builder_->createBinOp(
spv::OpINotEqual, type_bool_, main_fsi_sample_mask_, const_uint_0_);
// TODO(Triang3l): Use GroupNonUniformQuad operations where supported.
// If none of the pixels in the quad passed the depth / stencil test, the
// value of (any samples covered ? 1.0f : 0.0f) for the current pixel will
// be 0.0f, and since it will be 0.0f in other pixels too, the derivatives
// will be zero as well.
builder_->addCapability(spv::CapabilityDerivativeControl);
// Query the horizontally adjacent pixel.
quad_needs_execution = builder_->createBinOp(
spv::OpLogicalOr, type_bool_, quad_needs_execution,
builder_->createBinOp(
spv::OpFOrdNotEqual, type_bool_,
builder_->createUnaryOp(
spv::OpDPdxFine, type_float_,
builder_->createTriOp(spv::OpSelect, type_float_,
quad_needs_execution, const_float_1_,
const_float_0_)),
const_float_0_));
// Query the vertically adjacent pair of pixels.
quad_needs_execution = builder_->createBinOp(
spv::OpLogicalOr, type_bool_, quad_needs_execution,
builder_->createBinOp(
spv::OpFOrdNotEqual, type_bool_,
builder_->createUnaryOp(
spv::OpDPdyCoarse, type_float_,
builder_->createTriOp(spv::OpSelect, type_float_,
quad_needs_execution, const_float_1_,
const_float_0_)),
const_float_0_));
spv::Block& main_fsi_early_depth_stencil_execute_quad =
builder_->makeNewBlock();
main_fsi_early_depth_stencil_execute_quad_merge_ =
&builder_->makeNewBlock();
SpirvCreateSelectionMerge(
main_fsi_early_depth_stencil_execute_quad_merge_->getId(),
spv::SelectionControlDontFlattenMask);
builder_->createConditionalBranch(
quad_needs_execution, &main_fsi_early_depth_stencil_execute_quad,
main_fsi_early_depth_stencil_execute_quad_merge_);
builder_->setBuildPoint(&main_fsi_early_depth_stencil_execute_quad);
}
}
if (is_depth_only_fragment_shader_) {
return;
}
uint32_t param_gen_interpolator = GetPsParamGenInterpolator();
// Zero general-purpose registers to prevent crashes when the game
@ -1928,13 +2185,15 @@ void SpirvShaderTranslator::StartFragmentShaderInMain() {
var_main_registers_, id_vector_temp_));
}
if (!edram_fragment_shader_interlock_) {
// Initialize the colors for safety.
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
spv::Id output_fragment_data_rt = output_fragment_data_[i];
spv::Id output_fragment_data_rt = output_or_var_fragment_data_[i];
if (output_fragment_data_rt != spv::NoResult) {
builder_->createStore(const_float4_0_, output_fragment_data_rt);
}
}
}
}
void SpirvShaderTranslator::UpdateExecConditionals(
@ -2299,11 +2558,18 @@ void SpirvShaderTranslator::StoreResult(const InstructionResult& result,
assert_true(is_pixel_shader());
assert_not_zero(used_write_mask);
assert_true(current_shader().writes_color_target(result.storage_index));
target_pointer = output_fragment_data_[result.storage_index];
// May be spv::NoResult if the color output is explicitly removed due to
// an empty write mask without independent blending.
// TODO(Triang3l): Store the alpha of the first output in this case for
// alpha test and alpha to coverage.
target_pointer = output_or_var_fragment_data_[result.storage_index];
if (edram_fragment_shader_interlock_) {
assert_true(var_main_fsi_color_written_ != spv::NoResult);
builder_->createStore(
builder_->createBinOp(
spv::OpBitwiseOr, type_uint_,
builder_->createLoad(var_main_fsi_color_written_,
spv::NoPrecision),
builder_->makeUintConstant(uint32_t(1)
<< result.storage_index)),
var_main_fsi_color_written_);
}
} break;
default:
// TODO(Triang3l): All storage targets.

View File

@ -96,6 +96,9 @@ class SpirvShaderTranslator : public ShaderTranslator {
kSysFlag_WNotReciprocal_Shift,
kSysFlag_PrimitivePolygonal_Shift,
kSysFlag_PrimitiveLine_Shift,
kSysFlag_MsaaSamples_Shift,
kSysFlag_DepthFloat24_Shift =
kSysFlag_MsaaSamples_Shift + xenos::kMsaaSamplesBits,
kSysFlag_AlphaPassIfLess_Shift,
kSysFlag_AlphaPassIfEqual_Shift,
kSysFlag_AlphaPassIfGreater_Shift,
@ -104,6 +107,26 @@ class SpirvShaderTranslator : public ShaderTranslator {
kSysFlag_ConvertColor2ToGamma_Shift,
kSysFlag_ConvertColor3ToGamma_Shift,
kSysFlag_FSIDepthStencil_Shift,
kSysFlag_FSIDepthPassIfLess_Shift,
kSysFlag_FSIDepthPassIfEqual_Shift,
kSysFlag_FSIDepthPassIfGreater_Shift,
// 1 to write new depth to the depth buffer, 0 to keep the old one if the
// depth test passes.
kSysFlag_FSIDepthWrite_Shift,
kSysFlag_FSIStencilTest_Shift,
// If the depth / stencil test has failed, but resulted in a stencil value
// that is different than the one currently in the depth buffer, write it
// anyway and don't run the rest of the shader (to check if the sample may
// be discarded some way) - use when alpha test and alpha to coverage are
// disabled. Ignored by the shader if not applicable to it (like if it has
// kill instructions or writes the depth output).
// TODO(Triang3l): Investigate replacement with an alpha-to-mask flag,
// checking `(flags & (alpha test | alpha to mask)) == (always | disabled)`,
// taking into account the potential relation with occlusion queries (but
// should be safe at least temporarily).
kSysFlag_FSIDepthStencilEarlyWrite_Shift,
kSysFlag_Count,
// For HostVertexShaderType kVertex, if fullDrawIndexUint32 is not
@ -127,6 +150,7 @@ class SpirvShaderTranslator : public ShaderTranslator {
kSysFlag_WNotReciprocal = 1u << kSysFlag_WNotReciprocal_Shift,
kSysFlag_PrimitivePolygonal = 1u << kSysFlag_PrimitivePolygonal_Shift,
kSysFlag_PrimitiveLine = 1u << kSysFlag_PrimitiveLine_Shift,
kSysFlag_DepthFloat24 = 1u << kSysFlag_DepthFloat24_Shift,
kSysFlag_AlphaPassIfLess = 1u << kSysFlag_AlphaPassIfLess_Shift,
kSysFlag_AlphaPassIfEqual = 1u << kSysFlag_AlphaPassIfEqual_Shift,
kSysFlag_AlphaPassIfGreater = 1u << kSysFlag_AlphaPassIfGreater_Shift,
@ -134,6 +158,14 @@ class SpirvShaderTranslator : public ShaderTranslator {
kSysFlag_ConvertColor1ToGamma = 1u << kSysFlag_ConvertColor1ToGamma_Shift,
kSysFlag_ConvertColor2ToGamma = 1u << kSysFlag_ConvertColor2ToGamma_Shift,
kSysFlag_ConvertColor3ToGamma = 1u << kSysFlag_ConvertColor3ToGamma_Shift,
kSysFlag_FSIDepthStencil = 1u << kSysFlag_FSIDepthStencil_Shift,
kSysFlag_FSIDepthPassIfLess = 1u << kSysFlag_FSIDepthPassIfLess_Shift,
kSysFlag_FSIDepthPassIfEqual = 1u << kSysFlag_FSIDepthPassIfEqual_Shift,
kSysFlag_FSIDepthPassIfGreater = 1u << kSysFlag_FSIDepthPassIfGreater_Shift,
kSysFlag_FSIDepthWrite = 1u << kSysFlag_FSIDepthWrite_Shift,
kSysFlag_FSIStencilTest = 1u << kSysFlag_FSIStencilTest_Shift,
kSysFlag_FSIDepthStencilEarlyWrite =
1u << kSysFlag_FSIDepthStencilEarlyWrite_Shift,
};
static_assert(kSysFlag_Count <= 32, "Too many flags in the system constants");
@ -171,9 +203,55 @@ class SpirvShaderTranslator : public ShaderTranslator {
uint32_t texture_swizzles[16];
float alpha_test_reference;
float padding_alpha_test_reference[3];
uint32_t edram_32bpp_tile_pitch_dwords_scaled;
uint32_t edram_depth_base_dwords_scaled;
float padding_edram_depth_base_dwords_scaled;
float color_exp_bias[4];
float edram_poly_offset_front_scale;
float edram_poly_offset_back_scale;
float edram_poly_offset_front_offset;
float edram_poly_offset_back_offset;
union {
struct {
uint32_t edram_stencil_front_reference_masks;
uint32_t edram_stencil_front_func_ops;
uint32_t edram_stencil_back_reference_masks;
uint32_t edram_stencil_back_func_ops;
};
struct {
uint32_t edram_stencil_front[2];
uint32_t edram_stencil_back[2];
};
};
uint32_t edram_rt_base_dwords_scaled[4];
// RT format combined with RenderTargetCache::kPSIColorFormatFlag values
// (pass via RenderTargetCache::AddPSIColorFormatFlags).
uint32_t edram_rt_format_flags[4];
// Render target blending options - RB_BLENDCONTROL, with only the relevant
// options (factors and operations - AND 0x1FFF1FFF). If 0x00010001
// (1 * src + 0 * dst), blending is disabled for the render target.
uint32_t edram_rt_blend_factors_ops[4];
// Format info - mask to apply to the old packed RT data, and to apply as
// inverted to the new packed data, before storing (more or less the inverse
// of the write mask packed like render target channels). This can be used
// to bypass unpacking if blending is not used. If 0 and not blending,
// reading the old data from the EDRAM buffer is not required.
uint32_t edram_rt_keep_mask[4][2];
// Format info - values to clamp the color to before blending or storing.
// Low color, low alpha, high color, high alpha.
float edram_rt_clamp[4][4];
// The constant blend factor for the respective modes.
float edram_blend_constant[4];
};
enum ConstantBuffer : uint32_t {
@ -248,12 +326,22 @@ class SpirvShaderTranslator : public ShaderTranslator {
uint32_t max_storage_buffer_range;
bool clip_distance;
bool cull_distance;
bool demote_to_helper_invocation;
bool fragment_shader_sample_interlock;
bool full_draw_index_uint32;
bool image_view_format_swizzle;
bool signed_zero_inf_nan_preserve_float32;
bool denorm_flush_to_zero_float32;
};
SpirvShaderTranslator(const Features& features);
SpirvShaderTranslator(const Features& features,
bool native_2x_msaa_with_attachments,
bool native_2x_msaa_no_attachments,
bool edram_fragment_shader_interlock)
: features_(features),
native_2x_msaa_with_attachments_(native_2x_msaa_with_attachments),
native_2x_msaa_no_attachments_(native_2x_msaa_no_attachments),
edram_fragment_shader_interlock_(edram_fragment_shader_interlock) {}
uint64_t GetDefaultVertexShaderModification(
uint32_t dynamic_addressable_register_count,
@ -277,6 +365,10 @@ class SpirvShaderTranslator : public ShaderTranslator {
features_.max_storage_buffer_range);
}
// Creates a special fragment shader without color outputs - this resets the
// state of the translator.
std::vector<uint8_t> CreateDepthOnlyFragmentShader();
// Common functions useful not only for the translator, but also for EDRAM
// emulation via conventional render targets.
@ -385,10 +477,10 @@ class SpirvShaderTranslator : public ShaderTranslator {
}
bool IsExecutionModeEarlyFragmentTests() const {
// TODO(Triang3l): Not applicable to fragment shader interlock.
return is_pixel_shader() &&
GetSpirvShaderModification().pixel.depth_stencil_mode ==
Modification::DepthStencilMode::kEarlyHint &&
!edram_fragment_shader_interlock_ &&
current_shader().implicit_early_z_write_allowed();
}
@ -528,7 +620,72 @@ class SpirvShaderTranslator : public ShaderTranslator {
spv::Id image_unsigned, spv::Id image_signed,
spv::Id sampler, spv::Id is_all_signed);
spv::Id LoadMsaaSamplesFromFlags();
// Whether it's possible and worth skipping running the translated shader for
// 2x2 quads.
bool FSI_IsDepthStencilEarly() const {
assert_true(edram_fragment_shader_interlock_);
return !is_depth_only_fragment_shader_ &&
!current_shader().writes_depth() &&
!current_shader().is_valid_memexport_used();
}
void FSI_LoadSampleMask(spv::Id msaa_samples);
void FSI_LoadEdramOffsets(spv::Id msaa_samples);
// The address must be a signed int. Whether the render target is 64bpp, if
// present at all, must be a bool (if it's NoResult, 32bpp will be assumed).
spv::Id FSI_AddSampleOffset(spv::Id sample_0_address, uint32_t sample_index,
spv::Id is_64bpp = spv::NoResult);
// Updates main_fsi_sample_mask_. Must be called outside non-uniform control
// flow because of taking derivatives of the fragment depth.
void FSI_DepthStencilTest(spv::Id msaa_samples,
bool sample_mask_potentially_narrowed_previouly);
// Returns the first and the second 32 bits as two uints.
std::array<spv::Id, 2> FSI_ClampAndPackColor(spv::Id color_float4,
spv::Id format_with_flags);
std::array<spv::Id, 4> FSI_UnpackColor(std::array<spv::Id, 2> color_packed,
spv::Id format_with_flags);
// The bounds must have the same number of components as the color or alpha.
spv::Id FSI_FlushNaNClampAndInBlending(spv::Id color_or_alpha,
spv::Id is_fixed_point,
spv::Id min_value, spv::Id max_value);
spv::Id FSI_ApplyColorBlendFactor(spv::Id value, spv::Id is_fixed_point,
spv::Id clamp_min_value,
spv::Id clamp_max_value, spv::Id factor,
spv::Id source_color, spv::Id source_alpha,
spv::Id dest_color, spv::Id dest_alpha,
spv::Id constant_color,
spv::Id constant_alpha);
spv::Id FSI_ApplyAlphaBlendFactor(spv::Id value, spv::Id is_fixed_point,
spv::Id clamp_min_value,
spv::Id clamp_max_value, spv::Id factor,
spv::Id source_alpha, spv::Id dest_alpha,
spv::Id constant_alpha);
// If source_color_clamped, dest_color, constant_color_clamped are
// spv::NoResult, will blend the alpha. Otherwise, will blend the color.
// The result will be unclamped (color packing is supposed to clamp it).
spv::Id FSI_BlendColorOrAlphaWithUnclampedResult(
spv::Id is_fixed_point, spv::Id clamp_min_value, spv::Id clamp_max_value,
spv::Id source_color_clamped, spv::Id source_alpha_clamped,
spv::Id dest_color, spv::Id dest_alpha, spv::Id constant_color_clamped,
spv::Id constant_alpha_clamped, spv::Id equation, spv::Id source_factor,
spv::Id dest_factor);
Features features_;
bool native_2x_msaa_with_attachments_;
bool native_2x_msaa_no_attachments_;
// For safety with different drivers (even though fragment shader interlock in
// SPIR-V only has one control flow requirement - that both begin and end must
// be dynamically executed exactly once in this order), adhering to the more
// strict control flow limitations of OpenGL (GLSL) fragment shader interlock,
// that begin and end are called only on the outermost level of the control
// flow of the main function, and that there are no returns before either
// (there's a single return from the shader).
bool edram_fragment_shader_interlock_;
// Is currently writing the empty depth-only pixel shader, such as for depth
// and stencil testing with fragment shader interlock.
bool is_depth_only_fragment_shader_ = false;
std::unique_ptr<spv::Builder> builder_;
@ -621,7 +778,23 @@ class SpirvShaderTranslator : public ShaderTranslator {
kSystemConstantTextureSwizzledSigns,
kSystemConstantTextureSwizzles,
kSystemConstantAlphaTestReference,
kSystemConstantEdram32bppTilePitchDwordsScaled,
kSystemConstantEdramDepthBaseDwordsScaled,
kSystemConstantColorExpBias,
kSystemConstantEdramPolyOffsetFrontScale,
kSystemConstantEdramPolyOffsetBackScale,
kSystemConstantEdramPolyOffsetFrontOffset,
kSystemConstantEdramPolyOffsetBackOffset,
kSystemConstantEdramStencilFront,
kSystemConstantEdramStencilBack,
kSystemConstantEdramRTBaseDwordsScaled,
kSystemConstantEdramRTFormatFlags,
kSystemConstantEdramRTBlendFactorsOps,
// Accessed as float4[2], not float2[4], due to std140 array stride
// alignment.
kSystemConstantEdramRTKeepMask,
kSystemConstantEdramRTClamp,
kSystemConstantEdramBlendConstant,
};
spv::Id uniform_system_constants_;
spv::Id uniform_float_constants_;
@ -629,6 +802,7 @@ class SpirvShaderTranslator : public ShaderTranslator {
spv::Id uniform_fetch_constants_;
spv::Id buffers_shared_memory_;
spv::Id buffer_edram_;
// Not using combined images and samplers because
// maxPerStageDescriptorSamplers is often lower than
@ -647,6 +821,8 @@ class SpirvShaderTranslator : public ShaderTranslator {
spv::Id input_fragment_coordinates_;
// PS, only when needed - bool.
spv::Id input_front_facing_;
// PS, only when needed - int[1].
spv::Id input_sample_mask_;
// VS output or PS input, only the ones that are needed (spv::NoResult for the
// unneeded interpolators), indexed by the guest interpolator index - float4.
@ -671,7 +847,10 @@ class SpirvShaderTranslator : public ShaderTranslator {
};
spv::Id output_per_vertex_;
std::array<spv::Id, xenos::kMaxColorRenderTargets> output_fragment_data_;
// With fragment shader interlock, variables in the main function.
// Otherwise, framebuffer color attachment outputs.
std::array<spv::Id, xenos::kMaxColorRenderTargets>
output_or_var_fragment_data_;
std::vector<spv::Id> main_interface_;
spv::Function* function_main_;
@ -698,6 +877,40 @@ class SpirvShaderTranslator : public ShaderTranslator {
spv::Id var_main_registers_;
// VS only - float3 (special exports).
spv::Id var_main_point_size_edge_flag_kill_vertex_;
// PS, only when needed - bool.
spv::Id var_main_kill_pixel_;
// PS, only when writing to color render targets with fragment shader
// interlock - uint.
// Whether color buffers have been written to, if not written on the taken
// execution path, don't export according to Direct3D 9 register documentation
// (some games rely on this behavior).
spv::Id var_main_fsi_color_written_;
// Loaded by FSI_LoadSampleMask.
// Can be modified on the outermost control flow level in the main function.
// 0:3 - Per-sample coverage at the current stage of the shader's execution.
// Affected by things like gl_SampleMaskIn, early or late depth /
// stencil (always resets bits for failing, no matter if need to defer
// writing), alpha to coverage.
// 4:7 - Depth write deferred mask - when early depth / stencil resulted in a
// different value for the sample (like different stencil if the test
// failed), but can't write it before running the shader because it's
// not known if the sample will be discarded by the shader, alphatest or
// AtoC.
// Early depth / stencil rejection of the pixel is possible when both 0:3 and
// 4:7 are zero.
spv::Id main_fsi_sample_mask_;
// Loaded by FSI_LoadEdramOffsets.
// Including the depth render target base.
spv::Id main_fsi_address_depth_;
// Not including the render target base.
spv::Id main_fsi_offset_32bpp_;
spv::Id main_fsi_offset_64bpp_;
// Loaded by FSI_DepthStencilTest for early depth / stencil, the depth /
// stencil values to write at the end of the shader if the specified in
// main_fsi_sample_mask_ and if the samples were not discarded later after the
// early test.
std::array<spv::Id, 4> main_fsi_late_write_depth_stencil_;
spv::Block* main_fsi_early_depth_stencil_execute_quad_merge_;
spv::Block* main_loop_header_;
spv::Block* main_loop_continue_;
spv::Block* main_loop_merge_;

View File

@ -123,7 +123,7 @@ spv::Id SpirvShaderTranslator::ProcessVectorAluOperation(
: spv::NoType;
// In case the paired scalar instruction (if processed first) terminates the
// block (like via OpKill).
// block.
EnsureBuildPointAvailable();
// Lookup table for variants of instructions with similar structure.
@ -838,9 +838,15 @@ spv::Id SpirvShaderTranslator::ProcessVectorAluOperation(
SpirvCreateSelectionMerge(merge_block.getId());
builder_->createConditionalBranch(condition, &kill_block, &merge_block);
builder_->setBuildPoint(&kill_block);
// TODO(Triang3l): Demote to helper invocation to keep derivatives if
// needed (and return 1 if killed in this case).
builder_->createNoResultOp(spv::OpKill);
// Kill without influencing the control flow in the translated shader.
if (var_main_kill_pixel_ != spv::NoResult) {
builder_->createStore(builder_->makeBoolConstant(true),
var_main_kill_pixel_);
}
if (features_.demote_to_helper_invocation) {
builder_->createNoResultOp(spv::OpDemoteToHelperInvocationEXT);
}
builder_->createBranch(&merge_block);
builder_->setBuildPoint(&merge_block);
return const_float_0_;
}
@ -938,7 +944,7 @@ spv::Id SpirvShaderTranslator::ProcessScalarAluOperation(
}
// In case the paired vector instruction (if processed first) terminates the
// block (like via OpKill).
// block.
EnsureBuildPointAvailable();
// Lookup table for variants of instructions with similar structure.
@ -1393,9 +1399,15 @@ spv::Id SpirvShaderTranslator::ProcessScalarAluOperation(
SpirvCreateSelectionMerge(merge_block.getId());
builder_->createConditionalBranch(condition, &kill_block, &merge_block);
builder_->setBuildPoint(&kill_block);
// TODO(Triang3l): Demote to helper invocation to keep derivatives if
// needed (and return 1 if killed in this case).
builder_->createNoResultOp(spv::OpKill);
// Kill without influencing the control flow in the translated shader.
if (var_main_kill_pixel_ != spv::NoResult) {
builder_->createStore(builder_->makeBoolConstant(true),
var_main_kill_pixel_);
}
if (features_.demote_to_helper_invocation) {
builder_->createNoResultOp(spv::OpDemoteToHelperInvocationEXT);
}
builder_->createBranch(&merge_block);
builder_->setBuildPoint(&merge_block);
return const_float_0_;
}

View File

@ -1898,30 +1898,14 @@ void SpirvShaderTranslator::ProcessTextureFetchInstruction(
builder_->setBuildPoint(&block_dimension_stacked_start);
if (use_computed_lod) {
// Extract 2D gradients for stacked textures which are 2D arrays.
{
std::unique_ptr<spv::Instruction> shuffle_op =
std::make_unique<spv::Instruction>(builder_->getUniqueId(),
type_float2_,
spv::OpVectorShuffle);
shuffle_op->addIdOperand(gradients_h);
shuffle_op->addIdOperand(gradients_h);
shuffle_op->addImmediateOperand(0);
shuffle_op->addImmediateOperand(1);
texture_parameters.gradX = shuffle_op->getResultId();
builder_->getBuildPoint()->addInstruction(std::move(shuffle_op));
}
{
std::unique_ptr<spv::Instruction> shuffle_op =
std::make_unique<spv::Instruction>(builder_->getUniqueId(),
type_float2_,
spv::OpVectorShuffle);
shuffle_op->addIdOperand(gradients_v);
shuffle_op->addIdOperand(gradients_v);
shuffle_op->addImmediateOperand(0);
shuffle_op->addImmediateOperand(1);
texture_parameters.gradY = shuffle_op->getResultId();
builder_->getBuildPoint()->addInstruction(std::move(shuffle_op));
}
uint_vector_temp_.clear();
uint_vector_temp_.reserve(2);
uint_vector_temp_.push_back(0);
uint_vector_temp_.push_back(1);
texture_parameters.gradX = builder_->createRvalueSwizzle(
spv::NoPrecision, type_float2_, gradients_h, uint_vector_temp_);
texture_parameters.gradY = builder_->createRvalueSwizzle(
spv::NoPrecision, type_float2_, gradients_v, uint_vector_temp_);
}
// Check if linear filtering is needed.
bool vol_mag_filter_is_fetch_const =

File diff suppressed because it is too large Load Diff

View File

@ -68,9 +68,6 @@ const VkDescriptorPoolSize
{VK_DESCRIPTOR_TYPE_SAMPLER, kLinkedTypeDescriptorPoolSetCount},
};
// No specific reason for 32768 descriptors, just the "too much" amount from
// Direct3D 12 PIX warnings. 2x descriptors for textures because of unsigned and
// signed bindings.
VulkanCommandProcessor::VulkanCommandProcessor(
VulkanGraphicsSystem* graphics_system, kernel::KernelState* kernel_state)
: CommandProcessor(graphics_system, kernel_state),
@ -107,6 +104,32 @@ void VulkanCommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr,
void VulkanCommandProcessor::RestoreEdramSnapshot(const void* snapshot) {}
std::string VulkanCommandProcessor::GetWindowTitleText() const {
std::ostringstream title;
title << "Vulkan";
if (render_target_cache_) {
switch (render_target_cache_->GetPath()) {
case RenderTargetCache::Path::kHostRenderTargets:
title << " - FBO";
break;
case RenderTargetCache::Path::kPixelShaderInterlock:
title << " - FSI";
break;
default:
break;
}
uint32_t draw_resolution_scale_x =
texture_cache_ ? texture_cache_->draw_resolution_scale_x() : 1;
uint32_t draw_resolution_scale_y =
texture_cache_ ? texture_cache_->draw_resolution_scale_y() : 1;
if (draw_resolution_scale_x > 1 || draw_resolution_scale_y > 1) {
title << ' ' << draw_resolution_scale_x << 'x' << draw_resolution_scale_y;
}
}
title << " - HEAVILY INCOMPLETE, early development";
return title.str();
}
bool VulkanCommandProcessor::SetupContext() {
if (!CommandProcessor::SetupContext()) {
XELOGE("Failed to initialize base command processor context");
@ -147,7 +170,7 @@ bool VulkanCommandProcessor::SetupContext() {
size_t(16384)),
size_t(uniform_buffer_alignment)));
// Descriptor set layouts.
// Descriptor set layouts that don't depend on the setup of other subsystems.
VkShaderStageFlags guest_shader_stages =
guest_shader_vertex_stages_ | VK_SHADER_STAGE_FRAGMENT_BIT;
// Empty.
@ -164,37 +187,6 @@ bool VulkanCommandProcessor::SetupContext() {
XELOGE("Failed to create an empty Vulkan descriptor set layout");
return false;
}
// Shared memory and EDRAM.
uint32_t shared_memory_binding_count_log2 =
SpirvShaderTranslator::GetSharedMemoryStorageBufferCountLog2(
provider.device_properties().limits.maxStorageBufferRange);
uint32_t shared_memory_binding_count = UINT32_C(1)
<< shared_memory_binding_count_log2;
VkDescriptorSetLayoutBinding
descriptor_set_layout_bindings_shared_memory_and_edram[1];
descriptor_set_layout_bindings_shared_memory_and_edram[0].binding = 0;
descriptor_set_layout_bindings_shared_memory_and_edram[0].descriptorType =
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_set_layout_bindings_shared_memory_and_edram[0].descriptorCount =
shared_memory_binding_count;
descriptor_set_layout_bindings_shared_memory_and_edram[0].stageFlags =
guest_shader_stages;
descriptor_set_layout_bindings_shared_memory_and_edram[0].pImmutableSamplers =
nullptr;
// TODO(Triang3l): EDRAM storage image binding for the fragment shader
// interlocks case.
descriptor_set_layout_create_info.bindingCount = uint32_t(
xe::countof(descriptor_set_layout_bindings_shared_memory_and_edram));
descriptor_set_layout_create_info.pBindings =
descriptor_set_layout_bindings_shared_memory_and_edram;
if (dfn.vkCreateDescriptorSetLayout(
device, &descriptor_set_layout_create_info, nullptr,
&descriptor_set_layout_shared_memory_and_edram_) != VK_SUCCESS) {
XELOGE(
"Failed to create a Vulkan descriptor set layout for the shared memory "
"and the EDRAM");
return false;
}
// Guest draw constants.
VkDescriptorSetLayoutBinding descriptor_set_layout_bindings_constants
[SpirvShaderTranslator::kConstantBufferCount] = {};
@ -290,16 +282,70 @@ bool VulkanCommandProcessor::SetupContext() {
return false;
}
uint32_t shared_memory_binding_count_log2 =
SpirvShaderTranslator::GetSharedMemoryStorageBufferCountLog2(
provider.device_properties().limits.maxStorageBufferRange);
uint32_t shared_memory_binding_count = UINT32_C(1)
<< shared_memory_binding_count_log2;
// Requires the transient descriptor set layouts.
// TODO(Triang3l): Get the actual draw resolution scale when the texture cache
// supports resolution scaling.
render_target_cache_ = std::make_unique<VulkanRenderTargetCache>(
*register_file_, *memory_, trace_writer_, 1, 1, *this);
if (!render_target_cache_->Initialize()) {
if (!render_target_cache_->Initialize(shared_memory_binding_count)) {
XELOGE("Failed to initialize the render target cache");
return false;
}
// Shared memory and EDRAM descriptor set layout.
bool edram_fragment_shader_interlock =
render_target_cache_->GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
VkDescriptorSetLayoutBinding
shared_memory_and_edram_descriptor_set_layout_bindings[2];
shared_memory_and_edram_descriptor_set_layout_bindings[0].binding = 0;
shared_memory_and_edram_descriptor_set_layout_bindings[0].descriptorType =
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
shared_memory_and_edram_descriptor_set_layout_bindings[0].descriptorCount =
shared_memory_binding_count;
shared_memory_and_edram_descriptor_set_layout_bindings[0].stageFlags =
guest_shader_stages;
shared_memory_and_edram_descriptor_set_layout_bindings[0].pImmutableSamplers =
nullptr;
VkDescriptorSetLayoutCreateInfo
shared_memory_and_edram_descriptor_set_layout_create_info;
shared_memory_and_edram_descriptor_set_layout_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
shared_memory_and_edram_descriptor_set_layout_create_info.pNext = nullptr;
shared_memory_and_edram_descriptor_set_layout_create_info.flags = 0;
shared_memory_and_edram_descriptor_set_layout_create_info.pBindings =
shared_memory_and_edram_descriptor_set_layout_bindings;
if (edram_fragment_shader_interlock) {
// EDRAM.
shared_memory_and_edram_descriptor_set_layout_bindings[1].binding = 1;
shared_memory_and_edram_descriptor_set_layout_bindings[1].descriptorType =
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
shared_memory_and_edram_descriptor_set_layout_bindings[1].descriptorCount =
1;
shared_memory_and_edram_descriptor_set_layout_bindings[1].stageFlags =
VK_SHADER_STAGE_FRAGMENT_BIT;
shared_memory_and_edram_descriptor_set_layout_bindings[1]
.pImmutableSamplers = nullptr;
shared_memory_and_edram_descriptor_set_layout_create_info.bindingCount = 2;
} else {
shared_memory_and_edram_descriptor_set_layout_create_info.bindingCount = 1;
}
if (dfn.vkCreateDescriptorSetLayout(
device, &shared_memory_and_edram_descriptor_set_layout_create_info,
nullptr,
&descriptor_set_layout_shared_memory_and_edram_) != VK_SUCCESS) {
XELOGE(
"Failed to create a Vulkan descriptor set layout for the shared memory "
"and the EDRAM");
return false;
}
pipeline_cache_ = std::make_unique<VulkanPipelineCache>(
*this, *register_file_, *render_target_cache_,
guest_shader_vertex_stages_);
@ -321,9 +367,8 @@ bool VulkanCommandProcessor::SetupContext() {
// Shared memory and EDRAM common bindings.
VkDescriptorPoolSize descriptor_pool_sizes[1];
descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_pool_sizes[0].descriptorCount = shared_memory_binding_count;
// TODO(Triang3l): EDRAM storage image binding for the fragment shader
// interlocks case.
descriptor_pool_sizes[0].descriptorCount =
shared_memory_binding_count + uint32_t(edram_fragment_shader_interlock);
VkDescriptorPoolCreateInfo descriptor_pool_create_info;
descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
@ -370,20 +415,45 @@ bool VulkanCommandProcessor::SetupContext() {
shared_memory_binding_range * i;
shared_memory_descriptor_buffer_info.range = shared_memory_binding_range;
}
VkWriteDescriptorSet write_descriptor_sets[1];
write_descriptor_sets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_sets[0].pNext = nullptr;
write_descriptor_sets[0].dstSet = shared_memory_and_edram_descriptor_set_;
write_descriptor_sets[0].dstBinding = 0;
write_descriptor_sets[0].dstArrayElement = 0;
write_descriptor_sets[0].descriptorCount = shared_memory_binding_count;
write_descriptor_sets[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write_descriptor_sets[0].pImageInfo = nullptr;
write_descriptor_sets[0].pBufferInfo = shared_memory_descriptor_buffers_info;
write_descriptor_sets[0].pTexelBufferView = nullptr;
// TODO(Triang3l): EDRAM storage image binding for the fragment shader
// interlocks case.
dfn.vkUpdateDescriptorSets(device, 1, write_descriptor_sets, 0, nullptr);
VkWriteDescriptorSet write_descriptor_sets[2];
VkWriteDescriptorSet& write_descriptor_set_shared_memory =
write_descriptor_sets[0];
write_descriptor_set_shared_memory.sType =
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_set_shared_memory.pNext = nullptr;
write_descriptor_set_shared_memory.dstSet =
shared_memory_and_edram_descriptor_set_;
write_descriptor_set_shared_memory.dstBinding = 0;
write_descriptor_set_shared_memory.dstArrayElement = 0;
write_descriptor_set_shared_memory.descriptorCount =
shared_memory_binding_count;
write_descriptor_set_shared_memory.descriptorType =
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write_descriptor_set_shared_memory.pImageInfo = nullptr;
write_descriptor_set_shared_memory.pBufferInfo =
shared_memory_descriptor_buffers_info;
write_descriptor_set_shared_memory.pTexelBufferView = nullptr;
VkDescriptorBufferInfo edram_descriptor_buffer_info;
if (edram_fragment_shader_interlock) {
edram_descriptor_buffer_info.buffer = render_target_cache_->edram_buffer();
edram_descriptor_buffer_info.offset = 0;
edram_descriptor_buffer_info.range = VK_WHOLE_SIZE;
VkWriteDescriptorSet& write_descriptor_set_edram = write_descriptor_sets[1];
write_descriptor_set_edram.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_set_edram.pNext = nullptr;
write_descriptor_set_edram.dstSet = shared_memory_and_edram_descriptor_set_;
write_descriptor_set_edram.dstBinding = 1;
write_descriptor_set_edram.dstArrayElement = 0;
write_descriptor_set_edram.descriptorCount = 1;
write_descriptor_set_edram.descriptorType =
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write_descriptor_set_edram.pImageInfo = nullptr;
write_descriptor_set_edram.pBufferInfo = &edram_descriptor_buffer_info;
write_descriptor_set_edram.pTexelBufferView = nullptr;
}
dfn.vkUpdateDescriptorSets(device,
1 + uint32_t(edram_fragment_shader_interlock),
write_descriptor_sets, 0, nullptr);
// Swap objects.
@ -1042,6 +1112,9 @@ void VulkanCommandProcessor::ShutdownContext() {
}
descriptor_set_layouts_textures_.clear();
ui::vulkan::util::DestroyAndNullHandle(
dfn.vkDestroyDescriptorSetLayout, device,
descriptor_set_layout_shared_memory_and_edram_);
for (VkDescriptorSetLayout& descriptor_set_layout_single_transient :
descriptor_set_layouts_single_transient_) {
ui::vulkan::util::DestroyAndNullHandle(
@ -1051,9 +1124,6 @@ void VulkanCommandProcessor::ShutdownContext() {
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout,
device,
descriptor_set_layout_constants_);
ui::vulkan::util::DestroyAndNullHandle(
dfn.vkDestroyDescriptorSetLayout, device,
descriptor_set_layout_shared_memory_and_edram_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout,
device, descriptor_set_layout_empty_);
@ -2415,7 +2485,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
// Update system constants before uploading them.
UpdateSystemConstantValues(primitive_polygonal, primitive_processing_result,
shader_32bit_index_dma, viewport_info,
used_texture_mask);
used_texture_mask, normalized_depth_control,
normalized_color_mask);
// Update uniform buffers and descriptor sets after binding the pipeline with
// the new layout.
@ -2475,6 +2546,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
// After all commands that may dispatch, copy or insert barriers, submit the
// barriers (may end the render pass), and (re)enter the render pass before
// drawing.
// TODO(Triang3l): Handle disabled variableMultisampleRate by restarting the
// render pass with no attachments if the sample count becomes different.
SubmitBarriersAndEnterRenderTargetCacheRenderPass(
render_target_cache_->last_update_render_pass(),
render_target_cache_->last_update_framebuffer());
@ -3194,9 +3267,9 @@ void VulkanCommandProcessor::UpdateDynamicState(
scissor_rect.extent.height = scissor.extent[1];
SetScissor(scissor_rect);
if (render_target_cache_->GetPath() ==
RenderTargetCache::Path::kHostRenderTargets) {
// Depth bias.
// TODO(Triang3l): Disable the depth bias for the fragment shader interlock RB
// implementation.
float depth_bias_constant_factor, depth_bias_slope_factor;
draw_util::GetPreferredFacePolygonOffset(regs, primitive_polygonal,
depth_bias_slope_factor,
@ -3207,9 +3280,9 @@ void VulkanCommandProcessor::UpdateDynamicState(
? draw_util::kD3D10PolygonOffsetFactorUnorm24
: draw_util::kD3D10PolygonOffsetFactorFloat24;
// With non-square resolution scaling, make sure the worst-case impact is
// reverted (slope only along the scaled axis), thus max. More bias is better
// than less bias, because less bias means Z fighting with the background is
// more likely.
// reverted (slope only along the scaled axis), thus max. More bias is
// better than less bias, because less bias means Z fighting with the
// background is more likely.
depth_bias_slope_factor *=
xenos::kPolygonOffsetScaleSubpixelUnit *
float(std::max(render_target_cache_->draw_resolution_scale_x(),
@ -3249,12 +3322,12 @@ void VulkanCommandProcessor::UpdateDynamicState(
// Stencil masks and references.
// Due to pretty complex conditions involving registers not directly related
// to stencil (primitive type, culling), changing the values only when stencil
// is actually needed. However, due to the way dynamic state needs to be set
// in Vulkan, which doesn't take into account whether the state actually has
// effect on drawing, and because the masks and the references are always
// dynamic in Xenia guest pipelines, they must be set in the command buffer
// before any draw.
// to stencil (primitive type, culling), changing the values only when
// stencil is actually needed. However, due to the way dynamic state needs
// to be set in Vulkan, which doesn't take into account whether the state
// actually has effect on drawing, and because the masks and the references
// are always dynamic in Xenia guest pipelines, they must be set in the
// command buffer before any draw.
if (normalized_depth_control.stencil_enable) {
Register stencil_ref_mask_front_reg, stencil_ref_mask_back_reg;
if (primitive_polygonal && normalized_depth_control.backface_enable) {
@ -3288,13 +3361,15 @@ void VulkanCommandProcessor::UpdateDynamicState(
stencil_ref_mask_front.stencilmask;
dynamic_stencil_compare_mask_front_ = stencil_ref_mask_front.stencilmask;
dynamic_stencil_compare_mask_back_update_needed_ |=
dynamic_stencil_compare_mask_back_ != stencil_ref_mask_back.stencilmask;
dynamic_stencil_compare_mask_back_ !=
stencil_ref_mask_back.stencilmask;
dynamic_stencil_compare_mask_back_ = stencil_ref_mask_back.stencilmask;
// Write mask.
dynamic_stencil_write_mask_front_update_needed_ |=
dynamic_stencil_write_mask_front_ !=
stencil_ref_mask_front.stencilwritemask;
dynamic_stencil_write_mask_front_ = stencil_ref_mask_front.stencilwritemask;
dynamic_stencil_write_mask_front_ =
stencil_ref_mask_front.stencilwritemask;
dynamic_stencil_write_mask_back_update_needed_ |=
dynamic_stencil_write_mask_back_ !=
stencil_ref_mask_back.stencilwritemask;
@ -3307,14 +3382,15 @@ void VulkanCommandProcessor::UpdateDynamicState(
dynamic_stencil_reference_back_ != stencil_ref_mask_back.stencilref;
dynamic_stencil_reference_back_ = stencil_ref_mask_back.stencilref;
}
// Using VK_STENCIL_FACE_FRONT_AND_BACK for higher safety when running on the
// Vulkan portability subset without separateStencilMaskRef.
// Using VK_STENCIL_FACE_FRONT_AND_BACK for higher safety when running on
// the Vulkan portability subset without separateStencilMaskRef.
if (dynamic_stencil_compare_mask_front_update_needed_ ||
dynamic_stencil_compare_mask_back_update_needed_) {
if (dynamic_stencil_compare_mask_front_ ==
dynamic_stencil_compare_mask_back_) {
deferred_command_buffer_.CmdVkSetStencilCompareMask(
VK_STENCIL_FACE_FRONT_AND_BACK, dynamic_stencil_compare_mask_front_);
VK_STENCIL_FACE_FRONT_AND_BACK,
dynamic_stencil_compare_mask_front_);
} else {
if (dynamic_stencil_compare_mask_front_update_needed_) {
deferred_command_buffer_.CmdVkSetStencilCompareMask(
@ -3330,7 +3406,8 @@ void VulkanCommandProcessor::UpdateDynamicState(
}
if (dynamic_stencil_write_mask_front_update_needed_ ||
dynamic_stencil_write_mask_back_update_needed_) {
if (dynamic_stencil_write_mask_front_ == dynamic_stencil_write_mask_back_) {
if (dynamic_stencil_write_mask_front_ ==
dynamic_stencil_write_mask_back_) {
deferred_command_buffer_.CmdVkSetStencilWriteMask(
VK_STENCIL_FACE_FRONT_AND_BACK, dynamic_stencil_write_mask_front_);
} else {
@ -3364,6 +3441,7 @@ void VulkanCommandProcessor::UpdateDynamicState(
dynamic_stencil_reference_front_update_needed_ = false;
dynamic_stencil_reference_back_update_needed_ = false;
}
}
// TODO(Triang3l): VK_EXT_extended_dynamic_state and
// VK_EXT_extended_dynamic_state2.
@ -3373,23 +3451,67 @@ void VulkanCommandProcessor::UpdateSystemConstantValues(
bool primitive_polygonal,
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
bool shader_32bit_index_dma, const draw_util::ViewportInfo& viewport_info,
uint32_t used_texture_mask) {
uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control,
uint32_t normalized_color_mask) {
#if XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu");
#endif // XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
const RegisterFile& regs = *register_file_;
auto pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32;
auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>();
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
auto rb_stencilrefmask = regs.Get<reg::RB_STENCILREFMASK>();
auto rb_stencilrefmask_bf =
regs.Get<reg::RB_STENCILREFMASK>(XE_GPU_REG_RB_STENCILREFMASK_BF);
auto rb_surface_info = regs.Get<reg::RB_SURFACE_INFO>();
auto vgt_draw_initiator = regs.Get<reg::VGT_DRAW_INITIATOR>();
int32_t vgt_indx_offset = int32_t(regs[XE_GPU_REG_VGT_INDX_OFFSET].u32);
// Get the color info register values for each render target.
bool edram_fragment_shader_interlock =
render_target_cache_->GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
uint32_t draw_resolution_scale_x = texture_cache_->draw_resolution_scale_x();
uint32_t draw_resolution_scale_y = texture_cache_->draw_resolution_scale_y();
// Get the color info register values for each render target. Also, for FSI,
// exclude components that don't exist in the format from the write mask.
// Don't exclude fully overlapping render targets, however - two render
// targets with the same base address are used in the lighting pass of
// 4D5307E6, for example, with the needed one picked with dynamic control
// flow.
reg::RB_COLOR_INFO color_infos[xenos::kMaxColorRenderTargets];
float rt_clamp[4][4];
// Two UINT32_MAX if no components actually existing in the RT are written.
uint32_t rt_keep_masks[4][2];
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
color_infos[i] = regs.Get<reg::RB_COLOR_INFO>(
auto color_info = regs.Get<reg::RB_COLOR_INFO>(
reg::RB_COLOR_INFO::rt_register_indices[i]);
color_infos[i] = color_info;
if (edram_fragment_shader_interlock) {
RenderTargetCache::GetPSIColorFormatInfo(
color_info.color_format, (normalized_color_mask >> (i * 4)) & 0b1111,
rt_clamp[i][0], rt_clamp[i][1], rt_clamp[i][2], rt_clamp[i][3],
rt_keep_masks[i][0], rt_keep_masks[i][1]);
}
}
// Disable depth and stencil if it aliases a color render target (for
// instance, during the XBLA logo in 58410954, though depth writing is already
// disabled there).
bool depth_stencil_enabled = normalized_depth_control.stencil_enable ||
normalized_depth_control.z_enable;
if (edram_fragment_shader_interlock && depth_stencil_enabled) {
for (uint32_t i = 0; i < 4; ++i) {
if (rb_depth_info.depth_base == color_infos[i].color_base &&
(rt_keep_masks[i][0] != UINT32_MAX ||
rt_keep_masks[i][1] != UINT32_MAX)) {
depth_stencil_enabled = false;
break;
}
}
}
bool dirty = false;
@ -3433,6 +3555,13 @@ void VulkanCommandProcessor::UpdateSystemConstantValues(
if (draw_util::IsPrimitiveLine(regs)) {
flags |= SpirvShaderTranslator::kSysFlag_PrimitiveLine;
}
// MSAA sample count.
flags |= uint32_t(rb_surface_info.msaa_samples)
<< SpirvShaderTranslator::kSysFlag_MsaaSamples_Shift;
// Depth format.
if (rb_depth_info.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) {
flags |= SpirvShaderTranslator::kSysFlag_DepthFloat24;
}
// Alpha test.
xenos::CompareFunction alpha_test_function =
rb_colorcontrol.alpha_test_enable ? rb_colorcontrol.alpha_func
@ -3447,6 +3576,30 @@ void VulkanCommandProcessor::UpdateSystemConstantValues(
flags |= SpirvShaderTranslator::kSysFlag_ConvertColor0ToGamma << i;
}
}
if (edram_fragment_shader_interlock && depth_stencil_enabled) {
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthStencil;
if (normalized_depth_control.z_enable) {
flags |= uint32_t(normalized_depth_control.zfunc)
<< SpirvShaderTranslator::kSysFlag_FSIDepthPassIfLess_Shift;
if (normalized_depth_control.z_write_enable) {
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthWrite;
}
} else {
// In case stencil is used without depth testing - always pass, and
// don't modify the stored depth.
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthPassIfLess |
SpirvShaderTranslator::kSysFlag_FSIDepthPassIfEqual |
SpirvShaderTranslator::kSysFlag_FSIDepthPassIfGreater;
}
if (normalized_depth_control.stencil_enable) {
flags |= SpirvShaderTranslator::kSysFlag_FSIStencilTest;
}
// Hint - if not applicable to the shader, will not have effect.
if (alpha_test_function == xenos::CompareFunction::kAlways &&
!rb_colorcontrol.alpha_to_mask_enable) {
flags |= SpirvShaderTranslator::kSysFlag_FSIDepthStencilEarlyWrite;
}
}
dirty |= system_constants_.flags != flags;
system_constants_.flags = flags;
@ -3506,10 +3659,10 @@ void VulkanCommandProcessor::UpdateSystemConstantValues(
// to radius conversion to avoid multiplying the per-vertex diameter by an
// additional constant in the shader.
float point_screen_diameter_to_ndc_radius_x =
(/* 0.5f * 2.0f * */ float(texture_cache_->draw_resolution_scale_x())) /
(/* 0.5f * 2.0f * */ float(draw_resolution_scale_x)) /
std::max(viewport_info.xy_extent[0], uint32_t(1));
float point_screen_diameter_to_ndc_radius_y =
(/* 0.5f * 2.0f * */ float(texture_cache_->draw_resolution_scale_y())) /
(/* 0.5f * 2.0f * */ float(draw_resolution_scale_y)) /
std::max(viewport_info.xy_extent[1], uint32_t(1));
dirty |= system_constants_.point_screen_diameter_to_ndc_radius[0] !=
point_screen_diameter_to_ndc_radius_x;
@ -3574,7 +3727,25 @@ void VulkanCommandProcessor::UpdateSystemConstantValues(
dirty |= system_constants_.alpha_test_reference != rb_alpha_ref;
system_constants_.alpha_test_reference = rb_alpha_ref;
// Color exponent bias.
uint32_t edram_tile_dwords_scaled =
xenos::kEdramTileWidthSamples * xenos::kEdramTileHeightSamples *
(draw_resolution_scale_x * draw_resolution_scale_y);
// EDRAM pitch for FSI render target writing.
if (edram_fragment_shader_interlock) {
// Align, then multiply by 32bpp tile size in dwords.
uint32_t edram_32bpp_tile_pitch_dwords_scaled =
((rb_surface_info.surface_pitch *
(rb_surface_info.msaa_samples >= xenos::MsaaSamples::k4X ? 2 : 1)) +
(xenos::kEdramTileWidthSamples - 1)) /
xenos::kEdramTileWidthSamples * edram_tile_dwords_scaled;
dirty |= system_constants_.edram_32bpp_tile_pitch_dwords_scaled !=
edram_32bpp_tile_pitch_dwords_scaled;
system_constants_.edram_32bpp_tile_pitch_dwords_scaled =
edram_32bpp_tile_pitch_dwords_scaled;
}
// Color exponent bias and FSI render target writing.
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
reg::RB_COLOR_INFO color_info = color_infos[i];
// Exponent bias is in bits 20:25 of RB_COLOR_INFO.
@ -3595,6 +3766,148 @@ void VulkanCommandProcessor::UpdateSystemConstantValues(
UINT32_C(0x3F800000) + (color_exp_bias << 23);
dirty |= system_constants_.color_exp_bias[i] != color_exp_bias_scale;
system_constants_.color_exp_bias[i] = color_exp_bias_scale;
if (edram_fragment_shader_interlock) {
dirty |=
system_constants_.edram_rt_keep_mask[i][0] != rt_keep_masks[i][0];
system_constants_.edram_rt_keep_mask[i][0] = rt_keep_masks[i][0];
dirty |=
system_constants_.edram_rt_keep_mask[i][1] != rt_keep_masks[i][1];
system_constants_.edram_rt_keep_mask[i][1] = rt_keep_masks[i][1];
if (rt_keep_masks[i][0] != UINT32_MAX ||
rt_keep_masks[i][1] != UINT32_MAX) {
uint32_t rt_base_dwords_scaled =
color_info.color_base * edram_tile_dwords_scaled;
dirty |= system_constants_.edram_rt_base_dwords_scaled[i] !=
rt_base_dwords_scaled;
system_constants_.edram_rt_base_dwords_scaled[i] =
rt_base_dwords_scaled;
uint32_t format_flags =
RenderTargetCache::AddPSIColorFormatFlags(color_info.color_format);
dirty |= system_constants_.edram_rt_format_flags[i] != format_flags;
system_constants_.edram_rt_format_flags[i] = format_flags;
uint32_t blend_factors_ops =
regs[reg::RB_BLENDCONTROL::rt_register_indices[i]].u32 & 0x1FFF1FFF;
dirty |= system_constants_.edram_rt_blend_factors_ops[i] !=
blend_factors_ops;
system_constants_.edram_rt_blend_factors_ops[i] = blend_factors_ops;
// Can't do float comparisons here because NaNs would result in always
// setting the dirty flag.
dirty |= std::memcmp(system_constants_.edram_rt_clamp[i], rt_clamp[i],
4 * sizeof(float)) != 0;
std::memcpy(system_constants_.edram_rt_clamp[i], rt_clamp[i],
4 * sizeof(float));
}
}
}
if (edram_fragment_shader_interlock) {
uint32_t depth_base_dwords_scaled =
rb_depth_info.depth_base * edram_tile_dwords_scaled;
dirty |= system_constants_.edram_depth_base_dwords_scaled !=
depth_base_dwords_scaled;
system_constants_.edram_depth_base_dwords_scaled = depth_base_dwords_scaled;
// For non-polygons, front polygon offset is used, and it's enabled if
// POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back
// are used.
float poly_offset_front_scale = 0.0f, poly_offset_front_offset = 0.0f;
float poly_offset_back_scale = 0.0f, poly_offset_back_offset = 0.0f;
if (primitive_polygonal) {
if (pa_su_sc_mode_cntl.poly_offset_front_enable) {
poly_offset_front_scale =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32;
poly_offset_front_offset =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_OFFSET].f32;
}
if (pa_su_sc_mode_cntl.poly_offset_back_enable) {
poly_offset_back_scale =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_BACK_SCALE].f32;
poly_offset_back_offset =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_BACK_OFFSET].f32;
}
} else {
if (pa_su_sc_mode_cntl.poly_offset_para_enable) {
poly_offset_front_scale =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_SCALE].f32;
poly_offset_front_offset =
regs[XE_GPU_REG_PA_SU_POLY_OFFSET_FRONT_OFFSET].f32;
poly_offset_back_scale = poly_offset_front_scale;
poly_offset_back_offset = poly_offset_front_offset;
}
}
// With non-square resolution scaling, make sure the worst-case impact is
// reverted (slope only along the scaled axis), thus max. More bias is
// better than less bias, because less bias means Z fighting with the
// background is more likely.
float poly_offset_scale_factor =
xenos::kPolygonOffsetScaleSubpixelUnit *
std::max(draw_resolution_scale_x, draw_resolution_scale_y);
poly_offset_front_scale *= poly_offset_scale_factor;
poly_offset_back_scale *= poly_offset_scale_factor;
dirty |= system_constants_.edram_poly_offset_front_scale !=
poly_offset_front_scale;
system_constants_.edram_poly_offset_front_scale = poly_offset_front_scale;
dirty |= system_constants_.edram_poly_offset_front_offset !=
poly_offset_front_offset;
system_constants_.edram_poly_offset_front_offset = poly_offset_front_offset;
dirty |= system_constants_.edram_poly_offset_back_scale !=
poly_offset_back_scale;
system_constants_.edram_poly_offset_back_scale = poly_offset_back_scale;
dirty |= system_constants_.edram_poly_offset_back_offset !=
poly_offset_back_offset;
system_constants_.edram_poly_offset_back_offset = poly_offset_back_offset;
if (depth_stencil_enabled && normalized_depth_control.stencil_enable) {
uint32_t stencil_front_reference_masks =
rb_stencilrefmask.value & 0xFFFFFF;
dirty |= system_constants_.edram_stencil_front_reference_masks !=
stencil_front_reference_masks;
system_constants_.edram_stencil_front_reference_masks =
stencil_front_reference_masks;
uint32_t stencil_func_ops =
(normalized_depth_control.value >> 8) & ((1 << 12) - 1);
dirty |=
system_constants_.edram_stencil_front_func_ops != stencil_func_ops;
system_constants_.edram_stencil_front_func_ops = stencil_func_ops;
if (primitive_polygonal && normalized_depth_control.backface_enable) {
uint32_t stencil_back_reference_masks =
rb_stencilrefmask_bf.value & 0xFFFFFF;
dirty |= system_constants_.edram_stencil_back_reference_masks !=
stencil_back_reference_masks;
system_constants_.edram_stencil_back_reference_masks =
stencil_back_reference_masks;
uint32_t stencil_func_ops_bf =
(normalized_depth_control.value >> 20) & ((1 << 12) - 1);
dirty |= system_constants_.edram_stencil_back_func_ops !=
stencil_func_ops_bf;
system_constants_.edram_stencil_back_func_ops = stencil_func_ops_bf;
} else {
dirty |= std::memcmp(system_constants_.edram_stencil_back,
system_constants_.edram_stencil_front,
2 * sizeof(uint32_t)) != 0;
std::memcpy(system_constants_.edram_stencil_back,
system_constants_.edram_stencil_front,
2 * sizeof(uint32_t));
}
}
dirty |= system_constants_.edram_blend_constant[0] !=
regs[XE_GPU_REG_RB_BLEND_RED].f32;
system_constants_.edram_blend_constant[0] =
regs[XE_GPU_REG_RB_BLEND_RED].f32;
dirty |= system_constants_.edram_blend_constant[1] !=
regs[XE_GPU_REG_RB_BLEND_GREEN].f32;
system_constants_.edram_blend_constant[1] =
regs[XE_GPU_REG_RB_BLEND_GREEN].f32;
dirty |= system_constants_.edram_blend_constant[2] !=
regs[XE_GPU_REG_RB_BLEND_BLUE].f32;
system_constants_.edram_blend_constant[2] =
regs[XE_GPU_REG_RB_BLEND_BLUE].f32;
dirty |= system_constants_.edram_blend_constant[3] !=
regs[XE_GPU_REG_RB_BLEND_ALPHA].f32;
system_constants_.edram_blend_constant[3] =
regs[XE_GPU_REG_RB_BLEND_ALPHA].f32;
}
if (dirty) {

View File

@ -16,6 +16,7 @@
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@ -259,6 +260,9 @@ class VulkanCommandProcessor final : public CommandProcessor {
void SetViewport(const VkViewport& viewport);
void SetScissor(const VkRect2D& scissor);
// Returns the text to display in the GPU backend name in the window title.
std::string GetWindowTitleText() const;
protected:
bool SetupContext() override;
void ShutdownContext() override;
@ -443,7 +447,8 @@ class VulkanCommandProcessor final : public CommandProcessor {
bool primitive_polygonal,
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
bool shader_32bit_index_dma, const draw_util::ViewportInfo& viewport_info,
uint32_t used_texture_mask);
uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control,
uint32_t normalized_color_mask);
bool UpdateBindings(const VulkanShader* vertex_shader,
const VulkanShader* pixel_shader);
// Allocates a descriptor set and fills one or two VkWriteDescriptorSet
@ -520,12 +525,12 @@ class VulkanCommandProcessor final : public CommandProcessor {
// Descriptor set layouts used by different shaders.
VkDescriptorSetLayout descriptor_set_layout_empty_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout_shared_memory_and_edram_ =
VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout_constants_ = VK_NULL_HANDLE;
std::array<VkDescriptorSetLayout,
size_t(SingleTransientDescriptorLayout::kCount)>
descriptor_set_layouts_single_transient_{};
VkDescriptorSetLayout descriptor_set_layout_shared_memory_and_edram_ =
VK_NULL_HANDLE;
// Descriptor set layouts are referenced by pipeline_layouts_.
std::unordered_map<TextureDescriptorSetLayoutKey, VkDescriptorSetLayout,
@ -655,6 +660,9 @@ class VulkanCommandProcessor final : public CommandProcessor {
// declared as dynamic in the pipeline) invalidates such dynamic state.
VkViewport dynamic_viewport_;
VkRect2D dynamic_scissor_;
// Dynamic fixed-function depth bias, blend constants, stencil state are
// applicable only to the render target implementations where they are
// actually involved.
float dynamic_depth_bias_constant_factor_;
float dynamic_depth_bias_slope_factor_;
float dynamic_blend_constants_[4];

View File

@ -21,6 +21,15 @@ VulkanGraphicsSystem::VulkanGraphicsSystem() {}
VulkanGraphicsSystem::~VulkanGraphicsSystem() {}
std::string VulkanGraphicsSystem::name() const {
auto vulkan_command_processor =
static_cast<VulkanCommandProcessor*>(command_processor());
if (vulkan_command_processor != nullptr) {
return vulkan_command_processor->GetWindowTitleText();
}
return "Vulkan - HEAVILY INCOMPLETE, early development";
}
X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::WindowedAppContext* app_context,

View File

@ -26,9 +26,7 @@ class VulkanGraphicsSystem : public GraphicsSystem {
static bool IsAvailable() { return true; }
std::string name() const override {
return "Vulkan - HEAVILY INCOMPLETE, early development";
}
std::string name() const override;
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
ui::WindowedAppContext* app_context,

View File

@ -15,6 +15,7 @@
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
#include "third_party/fmt/include/fmt/format.h"
#include "third_party/glslang/SPIRV/SpvBuilder.h"
@ -53,8 +54,32 @@ bool VulkanPipelineCache::Initialize() {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
bool edram_fragment_shader_interlock =
render_target_cache_.GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
shader_translator_ = std::make_unique<SpirvShaderTranslator>(
SpirvShaderTranslator::Features(provider));
SpirvShaderTranslator::Features(provider),
render_target_cache_.msaa_2x_attachments_supported(),
render_target_cache_.msaa_2x_no_attachments_supported(),
edram_fragment_shader_interlock);
if (edram_fragment_shader_interlock) {
std::vector<uint8_t> depth_only_fragment_shader_code =
shader_translator_->CreateDepthOnlyFragmentShader();
depth_only_fragment_shader_ = ui::vulkan::util::CreateShaderModule(
provider,
reinterpret_cast<const uint32_t*>(
depth_only_fragment_shader_code.data()),
depth_only_fragment_shader_code.size());
if (depth_only_fragment_shader_ == VK_NULL_HANDLE) {
XELOGE(
"VulkanPipelineCache: Failed to create the depth/stencil-only "
"fragment shader for the fragment shader interlock render backend "
"implementation");
return false;
}
}
return true;
}
@ -75,6 +100,8 @@ void VulkanPipelineCache::Shutdown() {
pipelines_.clear();
// Destroy all internal shaders.
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device,
depth_only_fragment_shader_);
for (const auto& geometry_shader_pair : geometry_shaders_) {
if (geometry_shader_pair.second != VK_NULL_HANDLE) {
dfn.vkDestroyShaderModule(device, geometry_shader_pair.second, nullptr);
@ -179,6 +206,8 @@ VulkanPipelineCache::GetCurrentPixelShaderModification(
modification.pixel.param_gen_point = 0;
}
if (render_target_cache_.GetPath() ==
RenderTargetCache::Path::kHostRenderTargets) {
using DepthStencilMode =
SpirvShaderTranslator::Modification::DepthStencilMode;
if (shader.implicit_early_z_write_allowed() &&
@ -189,6 +218,7 @@ VulkanPipelineCache::GetCurrentPixelShaderModification(
} else {
modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers;
}
}
return modification;
}
@ -303,7 +333,11 @@ bool VulkanPipelineCache::ConfigurePipeline(
}
}
VkRenderPass render_pass =
render_target_cache_.GetRenderPass(render_pass_key);
render_target_cache_.GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock
? render_target_cache_.GetFragmentShaderInterlockRenderPass()
: render_target_cache_.GetHostRenderTargetsRenderPass(
render_pass_key);
if (render_pass == VK_NULL_HANDLE) {
return false;
}
@ -603,9 +637,8 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
description_out.polygon_mode = PipelinePolygonMode::kFill;
}
// TODO(Triang3l): Skip depth / stencil and color state for the fragment
// shader interlock RB implementation.
if (render_target_cache_.GetPath() ==
RenderTargetCache::Path::kHostRenderTargets) {
if (render_pass_key.depth_and_color_used & 1) {
if (normalized_depth_control.z_enable) {
description_out.depth_write_enable =
@ -646,8 +679,8 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
}
}
// Color blending and write masks (filled only for the attachments present in
// the render pass object).
// Color blending and write masks (filled only for the attachments present
// in the render pass object).
uint32_t render_pass_color_rts = render_pass_key.depth_and_color_used >> 1;
if (device_features.independentBlend) {
uint32_t render_pass_color_rts_remaining = render_pass_color_rts;
@ -665,8 +698,10 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
// Take the blend control for the first render target that the guest wants
// to write to (consider it the most important) and use it for all render
// targets, if any.
// TODO(Triang3l): Implement an option for independent blending via multiple
// draw calls with different pipelines maybe? Though independent blending
// TODO(Triang3l): Implement an option for independent blending via
// replaying the render pass for each set of render targets with unique
// blending parameters, with depth / stencil saved before the first and
// restored before each of the rest maybe? Though independent blending
// support is pretty wide, with a quite prominent exception of Adreno 4xx
// apparently.
uint32_t render_pass_color_rts_remaining = render_pass_color_rts;
@ -678,15 +713,16 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
PipelineRenderTarget& render_pass_first_color_rt =
description_out.render_targets[render_pass_first_color_rt_index];
uint32_t common_blend_rt_index;
if (xe::bit_scan_forward(normalized_color_mask, &common_blend_rt_index)) {
if (xe::bit_scan_forward(normalized_color_mask,
&common_blend_rt_index)) {
common_blend_rt_index >>= 2;
// If a common write mask will be used for multiple render targets, use
// the original RB_COLOR_MASK instead of the normalized color mask as
// the normalized color mask has non-existent components forced to
// If a common write mask will be used for multiple render targets,
// use the original RB_COLOR_MASK instead of the normalized color mask
// as the normalized color mask has non-existent components forced to
// written (don't need reading to be preserved), while the number of
// components may vary between render targets. The attachments in the
// pass that must not be written to at all will be excluded via a shader
// modification.
// pass that must not be written to at all will be excluded via a
// shader modification.
WritePipelineRenderTargetDescription(
regs.Get<reg::RB_BLENDCONTROL>(
reg::RB_BLENDCONTROL::rt_register_indices
@ -700,7 +736,8 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
render_pass_first_color_rt);
} else {
// No render targets are written to, though the render pass still may
// contain color attachments - set them to not written and not blending.
// contain color attachments - set them to not written and not
// blending.
render_pass_first_color_rt.src_color_blend_factor =
PipelineBlendFactor::kOne;
render_pass_first_color_rt.dst_color_blend_factor =
@ -723,6 +760,7 @@ bool VulkanPipelineCache::GetCurrentStateDescription(
}
}
}
}
return true;
}
@ -1929,6 +1967,10 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
command_processor_.GetVulkanProvider();
const VkPhysicalDeviceFeatures& device_features = provider.device_features();
bool edram_fragment_shader_interlock =
render_target_cache_.GetPath() ==
RenderTargetCache::Path::kPixelShaderInterlock;
std::array<VkPipelineShaderStageCreateInfo, 3> shader_stages;
uint32_t shader_stage_count = 0;
@ -1962,12 +2004,7 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
shader_stage_geometry.pName = "main";
shader_stage_geometry.pSpecializationInfo = nullptr;
}
// Pixel shader.
if (creation_arguments.pixel_shader) {
assert_true(creation_arguments.pixel_shader->is_translated());
if (!creation_arguments.pixel_shader->is_valid()) {
return false;
}
// Fragment shader.
VkPipelineShaderStageCreateInfo& shader_stage_fragment =
shader_stages[shader_stage_count++];
shader_stage_fragment.sType =
@ -1975,11 +2012,24 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
shader_stage_fragment.pNext = nullptr;
shader_stage_fragment.flags = 0;
shader_stage_fragment.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shader_stage_fragment.module = VK_NULL_HANDLE;
shader_stage_fragment.pName = "main";
shader_stage_fragment.pSpecializationInfo = nullptr;
if (creation_arguments.pixel_shader) {
assert_true(creation_arguments.pixel_shader->is_translated());
if (!creation_arguments.pixel_shader->is_valid()) {
return false;
}
shader_stage_fragment.module =
creation_arguments.pixel_shader->shader_module();
assert_true(shader_stage_fragment.module != VK_NULL_HANDLE);
shader_stage_fragment.pName = "main";
shader_stage_fragment.pSpecializationInfo = nullptr;
} else {
if (edram_fragment_shader_interlock) {
shader_stage_fragment.module = depth_only_fragment_shader_;
}
}
if (shader_stage_fragment.module == VK_NULL_HANDLE) {
--shader_stage_count;
}
VkPipelineVertexInputStateCreateInfo vertex_input_state = {};
@ -2087,10 +2137,10 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
// formula, though Z has no effect on anything if a depth attachment is not
// used (the guest shader can't access Z), enabling only when there's a
// depth / stencil attachment for correctness.
// TODO(Triang3l): Disable the depth bias for the fragment shader interlock RB
// implementation.
rasterization_state.depthBiasEnable =
(description.render_pass_key.depth_and_color_used & 0b1) ? VK_TRUE
(!edram_fragment_shader_interlock &&
(description.render_pass_key.depth_and_color_used & 0b1))
? VK_TRUE
: VK_FALSE;
// TODO(Triang3l): Wide lines.
rasterization_state.lineWidth = 1.0f;
@ -2101,6 +2151,7 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
if (description.render_pass_key.msaa_samples == xenos::MsaaSamples::k2X &&
!render_target_cache_.IsMsaa2xSupported(
!edram_fragment_shader_interlock &&
description.render_pass_key.depth_and_color_used != 0)) {
// Using sample 0 as 0 and 3 as 1 for 2x instead (not exactly the same
// sample locations, but still top-left and bottom-right - however, this can
@ -2119,13 +2170,15 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
depth_stencil_state.sType =
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil_state.pNext = nullptr;
if (!edram_fragment_shader_interlock) {
if (description.depth_write_enable ||
description.depth_compare_op != xenos::CompareFunction::kAlways) {
depth_stencil_state.depthTestEnable = VK_TRUE;
depth_stencil_state.depthWriteEnable =
description.depth_write_enable ? VK_TRUE : VK_FALSE;
depth_stencil_state.depthCompareOp = VkCompareOp(
uint32_t(VK_COMPARE_OP_NEVER) + uint32_t(description.depth_compare_op));
depth_stencil_state.depthCompareOp =
VkCompareOp(uint32_t(VK_COMPARE_OP_NEVER) +
uint32_t(description.depth_compare_op));
}
if (description.stencil_test_enable) {
depth_stencil_state.stencilTestEnable = VK_TRUE;
@ -2154,9 +2207,14 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
VkCompareOp(uint32_t(VK_COMPARE_OP_NEVER) +
uint32_t(description.stencil_back_compare_op));
}
}
VkPipelineColorBlendStateCreateInfo color_blend_state = {};
color_blend_state.sType =
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
VkPipelineColorBlendAttachmentState
color_blend_attachments[xenos::kMaxColorRenderTargets] = {};
if (!edram_fragment_shader_interlock) {
uint32_t color_rts_used =
description.render_pass_key.depth_and_color_used >> 1;
{
@ -2223,9 +2281,6 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
}
}
}
VkPipelineColorBlendStateCreateInfo color_blend_state = {};
color_blend_state.sType =
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
color_blend_state.attachmentCount = 32 - xe::lzcnt(color_rts_used);
color_blend_state.pAttachments = color_blend_attachments;
if (color_rts_used && !device_features.independentBlend) {
@ -2241,6 +2296,7 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
color_blend_attachments[first_color_rt_index];
}
}
}
std::array<VkDynamicState, 7> dynamic_states;
VkPipelineDynamicStateCreateInfo dynamic_state;
@ -2255,6 +2311,7 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
// invalidated (again, even if it has no effect).
dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT;
dynamic_states[dynamic_state.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR;
if (!edram_fragment_shader_interlock) {
dynamic_states[dynamic_state.dynamicStateCount++] =
VK_DYNAMIC_STATE_DEPTH_BIAS;
dynamic_states[dynamic_state.dynamicStateCount++] =
@ -2265,6 +2322,7 @@ bool VulkanPipelineCache::EnsurePipelineCreated(
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK;
dynamic_states[dynamic_state.dynamicStateCount++] =
VK_DYNAMIC_STATE_STENCIL_REFERENCE;
}
VkGraphicsPipelineCreateInfo pipeline_create_info;
pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;

View File

@ -314,6 +314,10 @@ class VulkanPipelineCache {
GeometryShaderKey::Hasher>
geometry_shaders_;
// Empty depth-only pixel shader for writing to depth buffer using fragment
// shader interlock when no Xenos pixel shader provided.
VkShaderModule depth_only_fragment_shader_ = VK_NULL_HANDLE;
std::unordered_map<PipelineDescription, Pipeline, PipelineDescription::Hasher>
pipelines_;

View File

@ -22,6 +22,7 @@
#include "third_party/glslang/SPIRV/GLSL.std.450.h"
#include "third_party/glslang/SPIRV/SpvBuilder.h"
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/gpu/draw_util.h"
@ -33,6 +34,27 @@
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/vulkan_util.h"
DEFINE_string(
render_target_path_vulkan, "",
"Render target emulation path to use on Vulkan.\n"
"Use: [any, fbo, fsi]\n"
" fbo:\n"
" Host framebuffers and fixed-function blending and depth / stencil "
"testing, copying between render targets when needed.\n"
" Lower accuracy (limited pixel format support).\n"
" Performance limited primarily by render target layout changes requiring "
"copying, but generally higher.\n"
" fsi:\n"
" Manual pixel packing, blending and depth / stencil testing, with free "
"render target layout changes.\n"
" Requires a GPU supporting fragment shader interlock.\n"
" Highest accuracy (all pixel formats handled in software).\n"
" Performance limited primarily by overdraw.\n"
" Any other value:\n"
" Choose what is considered the most optimal for the system (currently "
"always FB because the FSI path is much slower now).",
"GPU");
namespace xe {
namespace gpu {
namespace vulkan {
@ -43,6 +65,10 @@ namespace shaders {
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/host_depth_store_2xmsaa_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/host_depth_store_4xmsaa_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/passthrough_position_xy_vs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_clear_32bpp_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_clear_32bpp_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_clear_64bpp_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_clear_64bpp_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_fast_32bpp_1x2xmsaa_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_fast_32bpp_1x2xmsaa_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_fast_32bpp_4xmsaa_cs.h"
@ -180,13 +206,61 @@ VulkanRenderTargetCache::VulkanRenderTargetCache(
VulkanRenderTargetCache::~VulkanRenderTargetCache() { Shutdown(true); }
bool VulkanRenderTargetCache::Initialize() {
bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::InstanceFunctions& ifn = provider.ifn();
VkPhysicalDevice physical_device = provider.physical_device();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VkPhysicalDeviceLimits& device_limits =
provider.device_properties().limits;
if (cvars::render_target_path_vulkan == "fsi") {
path_ = Path::kPixelShaderInterlock;
} else {
path_ = Path::kHostRenderTargets;
}
// Fragment shader interlock is a feature implemented by pretty advanced GPUs,
// closer to Direct3D 11 / OpenGL ES 3.2 level mainly, not Direct3D 10 /
// OpenGL ES 3.1. Thus, it's fine to demand a wide range of other optional
// features for the fragment shader interlock backend to work.
if (path_ == Path::kPixelShaderInterlock) {
const VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT&
device_fragment_shader_interlock_features =
provider.device_fragment_shader_interlock_features();
const VkPhysicalDeviceFeatures& device_features =
provider.device_features();
// Interlocking between fragments with common sample coverage is enough, but
// interlocking more is acceptable too (fragmentShaderShadingRateInterlock
// would be okay too, but it's unlikely that an implementation would
// advertise only it and not any other ones, as it's a very specific feature
// interacting with another optional feature that is variable shading rate,
// so there's no need to overcomplicate the checks and the shader execution
// mode setting).
// Sample-rate shading is required by certain SPIR-V revisions to access the
// sample mask fragment shader input.
// Stanard sample locations are needed for calculating the depth at the
// samples.
// It's unlikely that a device exposing fragment shader interlock won't have
// a large enough storage buffer range and a sufficient SSBO slot count for
// all the shared memory buffers and the EDRAM buffer - an in a conflict
// between, for instance, the ability to vfetch and memexport in fragment
// shaders, and the usage of fragment shader interlock, prefer the former
// for simplicity.
if (!provider.device_extensions().ext_fragment_shader_interlock ||
!(device_fragment_shader_interlock_features
.fragmentShaderSampleInterlock ||
device_fragment_shader_interlock_features
.fragmentShaderPixelInterlock) ||
!device_features.fragmentStoresAndAtomics ||
!device_features.sampleRateShading ||
!device_limits.standardSampleLocations ||
shared_memory_binding_count >=
device_limits.maxDescriptorSetStorageBuffers) {
path_ = Path::kHostRenderTargets;
}
}
// Format support.
constexpr VkFormatFeatureFlags kUsedDepthFormatFeatures =
@ -199,6 +273,30 @@ bool VulkanRenderTargetCache::Initialize() {
(depth_unorm24_properties.optimalTilingFeatures &
kUsedDepthFormatFeatures) == kUsedDepthFormatFeatures;
// 2x MSAA support.
// TODO(Triang3l): Handle sampledImageIntegerSampleCounts 4 not supported in
// transfers.
if (cvars::native_2x_msaa) {
// Multisampled integer sampled images are optional in Vulkan and in Xenia.
msaa_2x_attachments_supported_ =
(device_limits.framebufferColorSampleCounts &
device_limits.framebufferDepthSampleCounts &
device_limits.framebufferStencilSampleCounts &
device_limits.sampledImageColorSampleCounts &
device_limits.sampledImageDepthSampleCounts &
device_limits.sampledImageStencilSampleCounts &
VK_SAMPLE_COUNT_2_BIT) &&
(device_limits.sampledImageIntegerSampleCounts &
(VK_SAMPLE_COUNT_2_BIT | VK_SAMPLE_COUNT_4_BIT)) !=
VK_SAMPLE_COUNT_4_BIT;
msaa_2x_no_attachments_supported_ =
(device_limits.framebufferNoAttachmentsSampleCounts &
VK_SAMPLE_COUNT_2_BIT) != 0;
} else {
msaa_2x_attachments_supported_ = false;
msaa_2x_no_attachments_supported_ = false;
}
// Descriptor set layouts.
VkDescriptorSetLayoutBinding descriptor_set_layout_bindings[2];
descriptor_set_layout_bindings[0].binding = 0;
@ -429,32 +527,10 @@ bool VulkanRenderTargetCache::Initialize() {
// TODO(Triang3l): All paths (FSI).
depth_float24_round_ = cvars::depth_float24_round;
if (path_ == Path::kHostRenderTargets) {
// Host render targets.
// TODO(Triang3l): Handle sampledImageIntegerSampleCounts 4 not supported in
// transfers.
if (cvars::native_2x_msaa) {
const VkPhysicalDeviceLimits& device_limits =
provider.device_properties().limits;
// Multisampled integer sampled images are optional in Vulkan and in Xenia.
msaa_2x_attachments_supported_ =
(device_limits.framebufferColorSampleCounts &
device_limits.framebufferDepthSampleCounts &
device_limits.framebufferStencilSampleCounts &
device_limits.sampledImageColorSampleCounts &
device_limits.sampledImageDepthSampleCounts &
device_limits.sampledImageStencilSampleCounts &
VK_SAMPLE_COUNT_2_BIT) &&
(device_limits.sampledImageIntegerSampleCounts &
(VK_SAMPLE_COUNT_2_BIT | VK_SAMPLE_COUNT_4_BIT)) !=
VK_SAMPLE_COUNT_4_BIT;
msaa_2x_no_attachments_supported_ =
(device_limits.framebufferNoAttachmentsSampleCounts &
VK_SAMPLE_COUNT_2_BIT) != 0;
} else {
msaa_2x_attachments_supported_ = false;
msaa_2x_no_attachments_supported_ = false;
}
depth_float24_round_ = cvars::depth_float24_round;
// Host depth storing pipeline layout.
VkDescriptorSetLayout host_depth_store_descriptor_set_layouts[] = {
@ -464,7 +540,8 @@ bool VulkanRenderTargetCache::Initialize() {
descriptor_set_layout_sampled_image_x2_,
};
VkPushConstantRange host_depth_store_push_constant_range;
host_depth_store_push_constant_range.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
host_depth_store_push_constant_range.stageFlags =
VK_SHADER_STAGE_COMPUTE_BIT;
host_depth_store_push_constant_range.offset = 0;
host_depth_store_push_constant_range.size = sizeof(HostDepthStoreConstants);
VkPipelineLayoutCreateInfo host_depth_store_pipeline_layout_create_info;
@ -505,8 +582,8 @@ bool VulkanRenderTargetCache::Initialize() {
host_depth_store_shader.first, host_depth_store_shader.second);
if (host_depth_store_pipeline == VK_NULL_HANDLE) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the {}-sample host depth "
"storing pipeline",
"VulkanRenderTargetCache: Failed to create the {}-sample host "
"depth storing pipeline",
uint32_t(1) << i);
Shutdown();
return false;
@ -529,8 +606,8 @@ bool VulkanRenderTargetCache::Initialize() {
sizeof(shaders::passthrough_position_xy_vs));
if (transfer_passthrough_vertex_shader_ == VK_NULL_HANDLE) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the render target ownership "
"transfer vertex shader");
"VulkanRenderTargetCache: Failed to create the render target "
"ownership transfer vertex shader");
Shutdown();
return false;
}
@ -558,8 +635,8 @@ bool VulkanRenderTargetCache::Initialize() {
uint32_t transfer_pipeline_layout_descriptor_sets_remaining =
transfer_pipeline_layout_info.used_descriptor_sets;
uint32_t transfer_pipeline_layout_descriptor_set_index;
while (
xe::bit_scan_forward(transfer_pipeline_layout_descriptor_sets_remaining,
while (xe::bit_scan_forward(
transfer_pipeline_layout_descriptor_sets_remaining,
&transfer_pipeline_layout_descriptor_set_index)) {
transfer_pipeline_layout_descriptor_sets_remaining &=
~(uint32_t(1) << transfer_pipeline_layout_descriptor_set_index);
@ -590,7 +667,8 @@ bool VulkanRenderTargetCache::Initialize() {
}
transfer_pipeline_layout_push_constant_range.size = uint32_t(
sizeof(uint32_t) *
xe::bit_count(transfer_pipeline_layout_info.used_push_constant_dwords));
xe::bit_count(
transfer_pipeline_layout_info.used_push_constant_dwords));
transfer_pipeline_layout_create_info.pushConstantRangeCount =
transfer_pipeline_layout_info.used_push_constant_dwords ? 1 : 0;
if (dfn.vkCreatePipelineLayout(
@ -631,8 +709,8 @@ bool VulkanRenderTargetCache::Initialize() {
dump_pipeline_layout_create_info.pPushConstantRanges =
&dump_pipeline_layout_push_constant_range;
if (dfn.vkCreatePipelineLayout(device, &dump_pipeline_layout_create_info,
nullptr,
&dump_pipeline_layout_color_) != VK_SUCCESS) {
nullptr, &dump_pipeline_layout_color_) !=
VK_SUCCESS) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the color render target "
"dumping pipeline layout");
@ -642,14 +720,162 @@ bool VulkanRenderTargetCache::Initialize() {
dump_pipeline_layout_descriptor_set_layouts[kDumpDescriptorSetSource] =
descriptor_set_layout_sampled_image_x2_;
if (dfn.vkCreatePipelineLayout(device, &dump_pipeline_layout_create_info,
nullptr,
&dump_pipeline_layout_depth_) != VK_SUCCESS) {
nullptr, &dump_pipeline_layout_depth_) !=
VK_SUCCESS) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the depth render target "
"dumping pipeline layout");
Shutdown();
return false;
}
} else if (path_ == Path::kPixelShaderInterlock) {
// Pixel (fragment) shader interlock.
// Blending is done in linear space directly in shaders.
gamma_render_target_as_srgb_ = false;
// Always true float24 depth rounded to the nearest even.
depth_float24_round_ = true;
// The pipeline layout and the pipelines for clearing the EDRAM buffer in
// resolves.
VkPushConstantRange resolve_fsi_clear_push_constant_range;
resolve_fsi_clear_push_constant_range.stageFlags =
VK_SHADER_STAGE_COMPUTE_BIT;
resolve_fsi_clear_push_constant_range.offset = 0;
resolve_fsi_clear_push_constant_range.size =
sizeof(draw_util::ResolveClearShaderConstants);
VkPipelineLayoutCreateInfo resolve_fsi_clear_pipeline_layout_create_info;
resolve_fsi_clear_pipeline_layout_create_info.sType =
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
resolve_fsi_clear_pipeline_layout_create_info.pNext = nullptr;
resolve_fsi_clear_pipeline_layout_create_info.flags = 0;
resolve_fsi_clear_pipeline_layout_create_info.setLayoutCount = 1;
resolve_fsi_clear_pipeline_layout_create_info.pSetLayouts =
&descriptor_set_layout_storage_buffer_;
resolve_fsi_clear_pipeline_layout_create_info.pushConstantRangeCount = 1;
resolve_fsi_clear_pipeline_layout_create_info.pPushConstantRanges =
&resolve_fsi_clear_push_constant_range;
if (dfn.vkCreatePipelineLayout(
device, &resolve_fsi_clear_pipeline_layout_create_info, nullptr,
&resolve_fsi_clear_pipeline_layout_) != VK_SUCCESS) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the resolve EDRAM buffer "
"clear pipeline layout");
Shutdown();
return false;
}
resolve_fsi_clear_32bpp_pipeline_ = ui::vulkan::util::CreateComputePipeline(
provider, resolve_fsi_clear_pipeline_layout_,
draw_resolution_scaled ? shaders::resolve_clear_32bpp_scaled_cs
: shaders::resolve_clear_32bpp_cs,
draw_resolution_scaled ? sizeof(shaders::resolve_clear_32bpp_scaled_cs)
: sizeof(shaders::resolve_clear_32bpp_cs));
if (resolve_fsi_clear_32bpp_pipeline_ == VK_NULL_HANDLE) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the 32bpp resolve EDRAM "
"buffer clear pipeline");
Shutdown();
return false;
}
resolve_fsi_clear_64bpp_pipeline_ = ui::vulkan::util::CreateComputePipeline(
provider, resolve_fsi_clear_pipeline_layout_,
draw_resolution_scaled ? shaders::resolve_clear_64bpp_scaled_cs
: shaders::resolve_clear_64bpp_cs,
draw_resolution_scaled ? sizeof(shaders::resolve_clear_64bpp_scaled_cs)
: sizeof(shaders::resolve_clear_64bpp_cs));
if (resolve_fsi_clear_32bpp_pipeline_ == VK_NULL_HANDLE) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the 64bpp resolve EDRAM "
"buffer clear pipeline");
Shutdown();
return false;
}
// Common render pass.
VkSubpassDescription fsi_subpass = {};
fsi_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
// Fragment shader interlock provides synchronization and ordering within a
// subpass, create an external by-region dependency to maintain interlocking
// between passes. Framebuffer-global dependencies will be made with
// explicit barriers when the addressing of the EDRAM buffer relatively to
// the fragment coordinates is changed.
VkSubpassDependency fsi_subpass_dependencies[2];
fsi_subpass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
fsi_subpass_dependencies[0].dstSubpass = 0;
fsi_subpass_dependencies[0].srcStageMask =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
fsi_subpass_dependencies[0].dstStageMask =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
fsi_subpass_dependencies[0].srcAccessMask =
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
fsi_subpass_dependencies[0].dstAccessMask =
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
fsi_subpass_dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
fsi_subpass_dependencies[1] = fsi_subpass_dependencies[0];
std::swap(fsi_subpass_dependencies[1].srcSubpass,
fsi_subpass_dependencies[1].dstSubpass);
VkRenderPassCreateInfo fsi_render_pass_create_info;
fsi_render_pass_create_info.sType =
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
fsi_render_pass_create_info.pNext = nullptr;
fsi_render_pass_create_info.flags = 0;
fsi_render_pass_create_info.attachmentCount = 0;
fsi_render_pass_create_info.pAttachments = nullptr;
fsi_render_pass_create_info.subpassCount = 1;
fsi_render_pass_create_info.pSubpasses = &fsi_subpass;
fsi_render_pass_create_info.dependencyCount =
uint32_t(xe::countof(fsi_subpass_dependencies));
fsi_render_pass_create_info.pDependencies = fsi_subpass_dependencies;
if (dfn.vkCreateRenderPass(device, &fsi_render_pass_create_info, nullptr,
&fsi_render_pass_) != VK_SUCCESS) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the fragment shader "
"interlock render backend render pass");
Shutdown();
return false;
}
// Common framebuffer.
VkFramebufferCreateInfo fsi_framebuffer_create_info;
fsi_framebuffer_create_info.sType =
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fsi_framebuffer_create_info.pNext = nullptr;
fsi_framebuffer_create_info.flags = 0;
fsi_framebuffer_create_info.renderPass = fsi_render_pass_;
fsi_framebuffer_create_info.attachmentCount = 0;
fsi_framebuffer_create_info.pAttachments = nullptr;
fsi_framebuffer_create_info.width = std::min(
xenos::kTexture2DCubeMaxWidthHeight * draw_resolution_scale_x(),
device_limits.maxFramebufferWidth);
fsi_framebuffer_create_info.height = std::min(
xenos::kTexture2DCubeMaxWidthHeight * draw_resolution_scale_y(),
device_limits.maxFramebufferHeight);
fsi_framebuffer_create_info.layers = 1;
if (dfn.vkCreateFramebuffer(device, &fsi_framebuffer_create_info, nullptr,
&fsi_framebuffer_.framebuffer) != VK_SUCCESS) {
XELOGE(
"VulkanRenderTargetCache: Failed to create the fragment shader "
"interlock render backend framebuffer");
Shutdown();
return false;
}
fsi_framebuffer_.host_extent.width = fsi_framebuffer_create_info.width;
fsi_framebuffer_.host_extent.height = fsi_framebuffer_create_info.height;
} else {
assert_unhandled_case(path_);
Shutdown();
return false;
}
// Reset the last update structures, to keep the defaults consistent between
// paths regardless of whether the update for the path actually modifies them.
last_update_render_pass_key_ = RenderPassKey();
last_update_render_pass_ = VK_NULL_HANDLE;
last_update_framebuffer_pitch_tiles_at_32bpp_ = 0;
std::memset(last_update_framebuffer_attachments_, 0,
sizeof(last_update_framebuffer_attachments_));
last_update_framebuffer_ = VK_NULL_HANDLE;
InitializeCommon();
return true;
@ -667,6 +893,18 @@ void VulkanRenderTargetCache::Shutdown(bool from_destructor) {
// already too late.
DestroyAllRenderTargets(true);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
resolve_fsi_clear_64bpp_pipeline_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
resolve_fsi_clear_32bpp_pipeline_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device,
resolve_fsi_clear_pipeline_layout_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyFramebuffer, device,
fsi_framebuffer_.framebuffer);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device,
fsi_render_pass_);
for (const auto& dump_pipeline_pair : dump_pipelines_) {
// May be null to prevent recreation attempts.
if (dump_pipeline_pair.second != VK_NULL_HANDLE) {
@ -951,8 +1189,8 @@ bool VulkanRenderTargetCache::Resolve(const Memory& memory,
bool clear_depth = resolve_info.IsClearingDepth();
bool clear_color = resolve_info.IsClearingColor();
if (clear_depth || clear_color) {
// TODO(Triang3l): Fragment shader interlock path EDRAM buffer clearing.
if (GetPath() == Path::kHostRenderTargets) {
switch (GetPath()) {
case Path::kHostRenderTargets: {
Transfer::Rectangle clear_rectangle;
RenderTarget* clear_render_targets[2];
// If PrepareHostRenderTargetsResolveClear returns false, may be just an
@ -970,6 +1208,62 @@ bool VulkanRenderTargetCache::Resolve(const Memory& memory,
&clear_rectangle);
}
cleared = true;
} break;
case Path::kPixelShaderInterlock: {
UseEdramBuffer(EdramBufferUsage::kComputeWrite);
// Should be safe to only commit once (if was accessed as unordered or
// with fragment shader interlock previously - if there was nothing to
// copy, only to clear, for some reason, for instance), overlap of the
// depth and the color ranges is highly unlikely.
CommitEdramBufferShaderWrites();
command_buffer.CmdVkBindDescriptorSets(
VK_PIPELINE_BIND_POINT_COMPUTE, resolve_fsi_clear_pipeline_layout_,
0, 1, &edram_storage_buffer_descriptor_set_, 0, nullptr);
std::pair<uint32_t, uint32_t> clear_group_count =
resolve_info.GetClearShaderGroupCount(draw_resolution_scale_x(),
draw_resolution_scale_y());
assert_true(clear_group_count.first && clear_group_count.second);
if (clear_depth) {
command_processor_.BindExternalComputePipeline(
resolve_fsi_clear_32bpp_pipeline_);
draw_util::ResolveClearShaderConstants depth_clear_constants;
resolve_info.GetDepthClearShaderConstants(depth_clear_constants);
command_buffer.CmdVkPushConstants(
resolve_fsi_clear_pipeline_layout_, VK_SHADER_STAGE_COMPUTE_BIT,
0, sizeof(depth_clear_constants), &depth_clear_constants);
command_processor_.SubmitBarriers(true);
command_buffer.CmdVkDispatch(clear_group_count.first,
clear_group_count.second, 1);
}
if (clear_color) {
command_processor_.BindExternalComputePipeline(
resolve_info.color_edram_info.format_is_64bpp
? resolve_fsi_clear_64bpp_pipeline_
: resolve_fsi_clear_32bpp_pipeline_);
draw_util::ResolveClearShaderConstants color_clear_constants;
resolve_info.GetColorClearShaderConstants(color_clear_constants);
if (clear_depth) {
// Non-RT-specific constants have already been set.
command_buffer.CmdVkPushConstants(
resolve_fsi_clear_pipeline_layout_, VK_SHADER_STAGE_COMPUTE_BIT,
uint32_t(offsetof(draw_util::ResolveClearShaderConstants,
rt_specific)),
sizeof(color_clear_constants.rt_specific),
&color_clear_constants.rt_specific);
} else {
command_buffer.CmdVkPushConstants(
resolve_fsi_clear_pipeline_layout_, VK_SHADER_STAGE_COMPUTE_BIT,
0, sizeof(color_clear_constants), &color_clear_constants);
}
command_processor_.SubmitBarriers(true);
command_buffer.CmdVkDispatch(clear_group_count.first,
clear_group_count.second, 1);
}
MarkEdramBufferModified();
cleared = true;
} break;
default:
assert_unhandled_case(GetPath());
}
} else {
cleared = true;
@ -987,8 +1281,15 @@ bool VulkanRenderTargetCache::Update(
return false;
}
// TODO(Triang3l): All paths (FSI).
auto rb_surface_info = register_file().Get<reg::RB_SURFACE_INFO>();
RenderPassKey render_pass_key;
// Needed even with the fragment shader interlock render backend for passing
// the sample count to the pipeline cache.
render_pass_key.msaa_samples = rb_surface_info.msaa_samples;
switch (GetPath()) {
case Path::kHostRenderTargets: {
RenderTarget* const* depth_and_color_render_targets =
last_update_accumulated_render_targets();
@ -996,14 +1297,11 @@ bool VulkanRenderTargetCache::Update(
depth_and_color_render_targets,
last_update_transfers());
auto rb_surface_info = register_file().Get<reg::RB_SURFACE_INFO>();
uint32_t render_targets_are_srgb =
gamma_render_target_as_srgb_
? last_update_accumulated_color_targets_are_gamma()
: 0;
RenderPassKey render_pass_key;
render_pass_key.msaa_samples = rb_surface_info.msaa_samples;
if (depth_and_color_render_targets[0]) {
render_pass_key.depth_and_color_used |= 1 << 0;
render_pass_key.depth_format =
@ -1043,7 +1341,7 @@ bool VulkanRenderTargetCache::Update(
? last_update_render_pass_
: VK_NULL_HANDLE;
if (render_pass == VK_NULL_HANDLE) {
render_pass = GetRenderPass(render_pass_key);
render_pass = GetHostRenderTargetsRenderPass(render_pass_key);
if (render_pass == VK_NULL_HANDLE) {
return false;
}
@ -1052,12 +1350,13 @@ bool VulkanRenderTargetCache::Update(
}
uint32_t pitch_tiles_at_32bpp =
((rb_surface_info.surface_pitch
<< uint32_t(rb_surface_info.msaa_samples >= xenos::MsaaSamples::k4X)) +
((rb_surface_info.surface_pitch << uint32_t(
rb_surface_info.msaa_samples >= xenos::MsaaSamples::k4X)) +
(xenos::kEdramTileWidthSamples - 1)) /
xenos::kEdramTileWidthSamples;
if (framebuffer) {
if (last_update_framebuffer_pitch_tiles_at_32bpp_ != pitch_tiles_at_32bpp ||
if (last_update_framebuffer_pitch_tiles_at_32bpp_ !=
pitch_tiles_at_32bpp ||
std::memcmp(last_update_framebuffer_attachments_,
depth_and_color_render_targets,
sizeof(last_update_framebuffer_attachments_))) {
@ -1065,7 +1364,8 @@ bool VulkanRenderTargetCache::Update(
}
}
if (!framebuffer) {
framebuffer = GetFramebuffer(render_pass_key, pitch_tiles_at_32bpp,
framebuffer = GetHostRenderTargetsFramebuffer(
render_pass_key, pitch_tiles_at_32bpp,
depth_and_color_render_targets);
if (!framebuffer) {
return false;
@ -1101,14 +1401,41 @@ bool VulkanRenderTargetCache::Update(
vulkan_rt.current_stage_mask(), rt_dst_stage_mask,
vulkan_rt.current_access_mask(), rt_dst_access_mask,
vulkan_rt.current_layout(), rt_new_layout);
vulkan_rt.SetUsage(rt_dst_stage_mask, rt_dst_access_mask, rt_new_layout);
vulkan_rt.SetUsage(rt_dst_stage_mask, rt_dst_access_mask,
rt_new_layout);
}
} break;
case Path::kPixelShaderInterlock: {
// For FSI, only the barrier is needed - already scheduled if required.
// But the buffer will be used for FSI drawing now.
UseEdramBuffer(EdramBufferUsage::kFragmentReadWrite);
// Commit preceding unordered (but not FSI) writes like clears as they
// aren't synchronized with FSI accesses.
CommitEdramBufferShaderWrites(
EdramBufferModificationStatus::kViaUnordered);
// TODO(Triang3l): Check if this draw call modifies color or depth /
// stencil, at least coarsely, to prevent useless barriers.
MarkEdramBufferModified(
EdramBufferModificationStatus::kViaFragmentShaderInterlock);
last_update_render_pass_key_ = render_pass_key;
last_update_render_pass_ = fsi_render_pass_;
last_update_framebuffer_ = &fsi_framebuffer_;
} break;
default:
assert_unhandled_case(GetPath());
return false;
}
return true;
}
VkRenderPass VulkanRenderTargetCache::GetRenderPass(RenderPassKey key) {
auto it = render_passes_.find(key.key);
VkRenderPass VulkanRenderTargetCache::GetHostRenderTargetsRenderPass(
RenderPassKey key) {
assert_true(GetPath() == Path::kHostRenderTargets);
auto it = render_passes_.find(key);
if (it != render_passes_.end()) {
return it->second;
}
@ -1244,10 +1571,10 @@ VkRenderPass VulkanRenderTargetCache::GetRenderPass(RenderPassKey key) {
if (dfn.vkCreateRenderPass(device, &render_pass_create_info, nullptr,
&render_pass) != VK_SUCCESS) {
XELOGE("VulkanRenderTargetCache: Failed to create a render pass");
render_passes_.emplace(key.key, VK_NULL_HANDLE);
render_passes_.emplace(key, VK_NULL_HANDLE);
return VK_NULL_HANDLE;
}
render_passes_.emplace(key.key, render_pass);
render_passes_.emplace(key, render_pass);
return render_pass;
}
@ -1353,15 +1680,17 @@ VulkanRenderTargetCache::VulkanRenderTarget::~VulkanRenderTarget() {
}
uint32_t VulkanRenderTargetCache::GetMaxRenderTargetWidth() const {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
return provider.device_properties().limits.maxFramebufferWidth;
const VkPhysicalDeviceLimits& device_limits =
command_processor_.GetVulkanProvider().device_properties().limits;
return std::min(device_limits.maxFramebufferWidth,
device_limits.maxImageDimension2D);
}
uint32_t VulkanRenderTargetCache::GetMaxRenderTargetHeight() const {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
return provider.device_properties().limits.maxFramebufferHeight;
const VkPhysicalDeviceLimits& device_limits =
command_processor_.GetVulkanProvider().device_properties().limits;
return std::min(device_limits.maxFramebufferHeight,
device_limits.maxImageDimension2D);
}
RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget(
@ -1615,6 +1944,12 @@ bool VulkanRenderTargetCache::IsHostDepthEncodingDifferent(
return false;
}
void VulkanRenderTargetCache::RequestPixelShaderInterlockBarrier() {
if (edram_buffer_usage_ == EdramBufferUsage::kFragmentReadWrite) {
CommitEdramBufferShaderWrites();
}
}
void VulkanRenderTargetCache::GetEdramBufferUsageMasks(
EdramBufferUsage usage, VkPipelineStageFlags& stage_mask_out,
VkAccessFlags& access_mask_out) {
@ -1715,7 +2050,7 @@ void VulkanRenderTargetCache::CommitEdramBufferShaderWrites(
}
const VulkanRenderTargetCache::Framebuffer*
VulkanRenderTargetCache::GetFramebuffer(
VulkanRenderTargetCache::GetHostRenderTargetsFramebuffer(
RenderPassKey render_pass_key, uint32_t pitch_tiles_at_32bpp,
const RenderTarget* const* depth_and_color_render_targets) {
FramebufferKey key;
@ -1749,8 +2084,10 @@ VulkanRenderTargetCache::GetFramebuffer(
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VkPhysicalDeviceLimits& device_limits =
provider.device_properties().limits;
VkRenderPass render_pass = GetRenderPass(render_pass_key);
VkRenderPass render_pass = GetHostRenderTargetsRenderPass(render_pass_key);
if (render_pass == VK_NULL_HANDLE) {
return nullptr;
}
@ -1789,12 +2126,19 @@ VulkanRenderTargetCache::GetFramebuffer(
render_pass_key.msaa_samples);
} else {
assert_zero(render_pass_key.depth_and_color_used);
host_extent.width = 0;
host_extent.height = 0;
// Still needed for occlusion queries.
host_extent.width = xenos::kTexture2DCubeMaxWidthHeight;
host_extent.height = xenos::kTexture2DCubeMaxWidthHeight;
}
// Vulkan requires width and height greater than 0.
framebuffer_create_info.width = std::max(host_extent.width, uint32_t(1));
framebuffer_create_info.height = std::max(host_extent.height, uint32_t(1));
// Limiting to the device limit for the case of no attachments, for which
// there's no limit imposed by the sizes of the attachments that have been
// created successfully.
host_extent.width = std::min(host_extent.width * draw_resolution_scale_x(),
device_limits.maxFramebufferWidth);
host_extent.height = std::min(host_extent.height * draw_resolution_scale_y(),
device_limits.maxFramebufferHeight);
framebuffer_create_info.width = host_extent.width;
framebuffer_create_info.height = host_extent.height;
framebuffer_create_info.layers = 1;
VkFramebuffer framebuffer;
if (dfn.vkCreateFramebuffer(device, &framebuffer_create_info, nullptr,
@ -4070,7 +4414,8 @@ VkPipeline const* VulkanRenderTargetCache::GetTransferPipelines(
: nullptr;
}
VkRenderPass render_pass = GetRenderPass(key.render_pass_key);
VkRenderPass render_pass =
GetHostRenderTargetsRenderPass(key.render_pass_key);
VkShaderModule fragment_shader_module = GetTransferShader(key.shader_key);
if (render_pass == VK_NULL_HANDLE ||
fragment_shader_module == VK_NULL_HANDLE) {
@ -4643,7 +4988,8 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears(
dest_rt_key.GetColorFormat();
transfer_render_pass_key.color_rts_use_transfer_formats = 1;
}
VkRenderPass transfer_render_pass = GetRenderPass(transfer_render_pass_key);
VkRenderPass transfer_render_pass =
GetHostRenderTargetsRenderPass(transfer_render_pass_key);
if (transfer_render_pass == VK_NULL_HANDLE) {
continue;
}
@ -4651,7 +4997,7 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears(
transfer_framebuffer_render_targets[1 + xenos::kMaxColorRenderTargets] =
{};
transfer_framebuffer_render_targets[dest_rt_key.is_depth ? 0 : 1] = dest_rt;
const Framebuffer* transfer_framebuffer = GetFramebuffer(
const Framebuffer* transfer_framebuffer = GetHostRenderTargetsFramebuffer(
transfer_render_pass_key, dest_rt_key.pitch_tiles_at_32bpp,
transfer_framebuffer_render_targets);
if (!transfer_framebuffer) {

View File

@ -43,6 +43,10 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
// true 4x MSAA passes (framebuffers because render target cache render
// targets are different for 2x and 4x guest MSAA, pipelines because the
// sample mask will have 2 samples excluded for 2x-as-4x).
// This has effect only on the attachments, but even in cases when there
// are no attachments, it can be used to the sample count between
// subsystems, for instance, to specify the desired number of samples to
// use when there are no attachments in pipelines.
xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits; // 2
// << 0 is depth, << 1...4 is color.
uint32_t depth_and_color_used : 1 + xenos::kMaxColorRenderTargets; // 7
@ -81,8 +85,9 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
static_assert_size(RenderPassKey, sizeof(uint32_t));
struct Framebuffer {
VkFramebuffer framebuffer;
VkExtent2D host_extent;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
VkExtent2D host_extent{};
Framebuffer() = default;
Framebuffer(VkFramebuffer framebuffer, const VkExtent2D& host_extent)
: framebuffer(framebuffer), host_extent(host_extent) {}
};
@ -96,15 +101,16 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
// Transient descriptor set layouts must be initialized in the command
// processor.
bool Initialize();
bool Initialize(uint32_t shared_memory_binding_count);
void Shutdown(bool from_destructor = false);
void ClearCache() override;
void CompletedSubmissionUpdated();
void EndSubmission();
// TODO(Triang3l): Fragment shader interlock.
Path GetPath() const override { return Path::kHostRenderTargets; }
Path GetPath() const override { return path_; }
VkBuffer edram_buffer() const { return edram_buffer_; }
// Performs the resolve to a shared memory area according to the current
// register values, and also clears the render targets if needed. Must be in a
@ -161,7 +167,11 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
// Returns the render pass object, or VK_NULL_HANDLE if failed to create.
// A render pass managed by the render target cache may be ended and resumed
// at any time (to allow for things like copying and texture loading).
VkRenderPass GetRenderPass(RenderPassKey key);
VkRenderPass GetHostRenderTargetsRenderPass(RenderPassKey key);
VkRenderPass GetFragmentShaderInterlockRenderPass() const {
assert_true(GetPath() == Path::kPixelShaderInterlock);
return fsi_render_pass_;
}
VkFormat GetDepthVulkanFormat(xenos::DepthRenderTargetFormat format) const;
VkFormat GetColorVulkanFormat(xenos::ColorRenderTargetFormat format) const;
@ -178,6 +188,8 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
bool IsHostDepthEncodingDifferent(
xenos::DepthRenderTargetFormat format) const override;
void RequestPixelShaderInterlockBarrier() override;
private:
enum class EdramBufferUsage {
// There's no need for combined fragment and compute usages.
@ -251,6 +263,8 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
VulkanCommandProcessor& command_processor_;
TraceWriter& trace_writer_;
Path path_ = Path::kHostRenderTargets;
// Accessible in fragment and compute shaders.
VkDescriptorSetLayout descriptor_set_layout_storage_buffer_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout_sampled_image_ = VK_NULL_HANDLE;
@ -276,9 +290,18 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
std::array<VkPipeline, size_t(draw_util::ResolveCopyShaderIndex::kCount)>
resolve_copy_pipelines_{};
// RenderPassKey::key -> VkRenderPass.
// VK_NULL_HANDLE if failed to create.
std::unordered_map<uint32_t, VkRenderPass> render_passes_;
// On the fragment shader interlock path, the render pass key is used purely
// for passing parameters to pipeline setup - there's always only one render
// pass.
RenderPassKey last_update_render_pass_key_;
VkRenderPass last_update_render_pass_ = VK_NULL_HANDLE;
// The pitch is not used on the fragment shader interlock path.
uint32_t last_update_framebuffer_pitch_tiles_at_32bpp_ = 0;
// The attachments are not used on the fragment shader interlock path.
const RenderTarget* const*
last_update_framebuffer_attachments_[1 + xenos::kMaxColorRenderTargets] =
{};
const Framebuffer* last_update_framebuffer_ = VK_NULL_HANDLE;
// For host render targets.
@ -809,7 +832,7 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
};
// Returns the framebuffer object, or VK_NULL_HANDLE if failed to create.
const Framebuffer* GetFramebuffer(
const Framebuffer* GetHostRenderTargetsFramebuffer(
RenderPassKey render_pass_key, uint32_t pitch_tiles_at_32bpp,
const RenderTarget* const* depth_and_color_render_targets);
@ -845,17 +868,13 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
bool msaa_2x_attachments_supported_ = false;
bool msaa_2x_no_attachments_supported_ = false;
// VK_NULL_HANDLE if failed to create.
std::unordered_map<RenderPassKey, VkRenderPass, RenderPassKey::Hasher>
render_passes_;
std::unordered_map<FramebufferKey, Framebuffer, FramebufferKey::Hasher>
framebuffers_;
RenderPassKey last_update_render_pass_key_;
VkRenderPass last_update_render_pass_ = VK_NULL_HANDLE;
uint32_t last_update_framebuffer_pitch_tiles_at_32bpp_ = 0;
const RenderTarget* const*
last_update_framebuffer_attachments_[1 + xenos::kMaxColorRenderTargets] =
{};
const Framebuffer* last_update_framebuffer_ = VK_NULL_HANDLE;
// Set 0 - EDRAM storage buffer, set 1 - source depth sampled image (and
// unused stencil from the transfer descriptor set), HostDepthStoreConstants
// passed via push constants.
@ -895,6 +914,15 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
// Temporary storage for DumpRenderTargets.
std::vector<ResolveCopyDumpRectangle> dump_rectangles_;
std::vector<DumpInvocation> dump_invocations_;
// For pixel (fragment) shader interlock.
VkRenderPass fsi_render_pass_ = VK_NULL_HANDLE;
Framebuffer fsi_framebuffer_;
VkPipelineLayout resolve_fsi_clear_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline resolve_fsi_clear_32bpp_pipeline_ = VK_NULL_HANDLE;
VkPipeline resolve_fsi_clear_64bpp_pipeline_ = VK_NULL_HANDLE;
};
} // namespace vulkan

View File

@ -1,5 +1,7 @@
// VK_KHR_get_physical_device_properties2 functions used in Xenia.
// Promoted to Vulkan 1.1 core.
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceFeatures2KHR,
vkGetPhysicalDeviceFeatures2)
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceMemoryProperties2KHR,
vkGetPhysicalDeviceMemoryProperties2)
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceProperties2KHR,

View File

@ -696,6 +696,7 @@ bool VulkanProvider::Initialize() {
device_extensions_.khr_shader_float_controls = true;
device_extensions_.khr_spirv_1_4 = true;
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 3, 0)) {
device_extensions_.ext_shader_demote_to_helper_invocation = true;
device_extensions_.khr_maintenance4 = true;
}
}
@ -709,6 +710,8 @@ bool VulkanProvider::Initialize() {
{"VK_EXT_fragment_shader_interlock",
offsetof(DeviceExtensions, ext_fragment_shader_interlock)},
{"VK_EXT_memory_budget", offsetof(DeviceExtensions, ext_memory_budget)},
{"VK_EXT_shader_demote_to_helper_invocation",
offsetof(DeviceExtensions, ext_shader_demote_to_helper_invocation)},
{"VK_EXT_shader_stencil_export",
offsetof(DeviceExtensions, ext_shader_stencil_export)},
{"VK_KHR_bind_memory2", offsetof(DeviceExtensions, khr_bind_memory2)},
@ -816,6 +819,16 @@ bool VulkanProvider::Initialize() {
// Get additional device properties.
std::memset(&device_float_controls_properties_, 0,
sizeof(device_float_controls_properties_));
device_float_controls_properties_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES_KHR;
std::memset(&device_fragment_shader_interlock_features_, 0,
sizeof(device_fragment_shader_interlock_features_));
device_fragment_shader_interlock_features_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT;
std::memset(&device_shader_demote_to_helper_invocation_features_, 0,
sizeof(device_shader_demote_to_helper_invocation_features_));
device_shader_demote_to_helper_invocation_features_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES_EXT;
if (instance_extensions_.khr_get_physical_device_properties2) {
VkPhysicalDeviceProperties2KHR device_properties_2;
device_properties_2.sType =
@ -824,8 +837,6 @@ bool VulkanProvider::Initialize() {
VkPhysicalDeviceProperties2KHR* device_properties_2_last =
&device_properties_2;
if (device_extensions_.khr_shader_float_controls) {
device_float_controls_properties_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES_KHR;
device_float_controls_properties_.pNext = nullptr;
device_properties_2_last->pNext = &device_float_controls_properties_;
device_properties_2_last =
@ -836,6 +847,28 @@ bool VulkanProvider::Initialize() {
ifn_.vkGetPhysicalDeviceProperties2KHR(physical_device_,
&device_properties_2);
}
VkPhysicalDeviceFeatures2KHR device_features_2;
device_features_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
device_features_2.pNext = nullptr;
VkPhysicalDeviceFeatures2KHR* device_features_2_last = &device_features_2;
if (device_extensions_.ext_fragment_shader_interlock) {
device_fragment_shader_interlock_features_.pNext = nullptr;
device_features_2_last->pNext =
&device_fragment_shader_interlock_features_;
device_features_2_last = reinterpret_cast<VkPhysicalDeviceFeatures2KHR*>(
&device_fragment_shader_interlock_features_);
}
if (device_extensions_.ext_shader_demote_to_helper_invocation) {
device_shader_demote_to_helper_invocation_features_.pNext = nullptr;
device_features_2_last->pNext =
&device_shader_demote_to_helper_invocation_features_;
device_features_2_last = reinterpret_cast<VkPhysicalDeviceFeatures2KHR*>(
&device_shader_demote_to_helper_invocation_features_);
}
if (device_features_2_last != &device_features_2) {
ifn_.vkGetPhysicalDeviceFeatures2KHR(physical_device_,
&device_features_2);
}
}
// Create the device.
@ -888,6 +921,21 @@ bool VulkanProvider::Initialize() {
device_create_info_last = reinterpret_cast<VkDeviceCreateInfo*>(
&device_portability_subset_features_);
}
if (device_extensions_.ext_fragment_shader_interlock) {
// TODO(Triang3l): Enable only needed fragment shader interlock features.
device_fragment_shader_interlock_features_.pNext = nullptr;
device_create_info_last->pNext =
&device_fragment_shader_interlock_features_;
device_create_info_last = reinterpret_cast<VkDeviceCreateInfo*>(
&device_fragment_shader_interlock_features_);
}
if (device_extensions_.ext_shader_demote_to_helper_invocation) {
device_shader_demote_to_helper_invocation_features_.pNext = nullptr;
device_create_info_last->pNext =
&device_shader_demote_to_helper_invocation_features_;
device_create_info_last = reinterpret_cast<VkDeviceCreateInfo*>(
&device_shader_demote_to_helper_invocation_features_);
}
if (ifn_.vkCreateDevice(physical_device_, &device_create_info, nullptr,
&device_) != VK_SUCCESS) {
XELOGE("Failed to create a Vulkan device");
@ -995,8 +1043,30 @@ bool VulkanProvider::Initialize() {
XELOGVK("Vulkan device extensions:");
XELOGVK("* VK_EXT_fragment_shader_interlock: {}",
device_extensions_.ext_fragment_shader_interlock ? "yes" : "no");
if (device_extensions_.ext_fragment_shader_interlock) {
XELOGVK(
" * Sample interlock: {}",
device_fragment_shader_interlock_features_.fragmentShaderSampleInterlock
? "yes"
: "no");
XELOGVK(
" * Pixel interlock: {}",
device_fragment_shader_interlock_features_.fragmentShaderPixelInterlock
? "yes"
: "no");
}
XELOGVK("* VK_EXT_memory_budget: {}",
device_extensions_.ext_memory_budget ? "yes" : "no");
XELOGVK(
"* VK_EXT_shader_demote_to_helper_invocation: {}",
device_extensions_.ext_shader_demote_to_helper_invocation ? "yes" : "no");
if (device_extensions_.ext_shader_demote_to_helper_invocation) {
XELOGVK(" * Demote to helper invocation: {}",
device_shader_demote_to_helper_invocation_features_
.shaderDemoteToHelperInvocation
? "yes"
: "no");
}
XELOGVK("* VK_EXT_shader_stencil_export: {}",
device_extensions_.ext_shader_stencil_export ? "yes" : "no");
XELOGVK("* VK_KHR_bind_memory2: {}",

View File

@ -133,6 +133,8 @@ class VulkanProvider : public GraphicsProvider {
struct DeviceExtensions {
bool ext_fragment_shader_interlock;
bool ext_memory_budget;
// Core since 1.3.0.
bool ext_shader_demote_to_helper_invocation;
bool ext_shader_stencil_export;
// Core since 1.1.0.
bool khr_bind_memory2;
@ -198,6 +200,14 @@ class VulkanProvider : public GraphicsProvider {
device_float_controls_properties() const {
return device_float_controls_properties_;
}
const VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT&
device_fragment_shader_interlock_features() const {
return device_fragment_shader_interlock_features_;
}
const VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT&
device_shader_demote_to_helper_invocation_features() const {
return device_shader_demote_to_helper_invocation_features_;
}
struct Queue {
VkQueue queue = VK_NULL_HANDLE;
@ -320,6 +330,10 @@ class VulkanProvider : public GraphicsProvider {
uint32_t queue_family_graphics_compute_;
uint32_t queue_family_sparse_binding_;
VkPhysicalDeviceFloatControlsPropertiesKHR device_float_controls_properties_;
VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT
device_fragment_shader_interlock_features_;
VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT
device_shader_demote_to_helper_invocation_features_;
VkDevice device_ = VK_NULL_HANDLE;
DeviceFunctions dfn_ = {};

View File

@ -191,9 +191,10 @@
this.translationComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.translationComboBox.FormattingEnabled = true;
this.translationComboBox.Items.AddRange(new object[] {
"DXBC (RTV/DSV RB)",
"DXBC (ROV RB)",
"SPIR-V"});
"DXBC (render target RB)",
"DXBC (rasterizer-ordered view RB)",
"SPIR-V (framebuffer RB)",
"SPIR-V (fragment shader interlock RB)"});
this.translationComboBox.Location = new System.Drawing.Point(1224, 0);
this.translationComboBox.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.translationComboBox.Name = "translationComboBox";

View File

@ -235,6 +235,7 @@ namespace shader_playground {
outputType = "dxbctext";
break;
case 2:
case 3:
outputType = "spirvtext";
break;
}
@ -269,8 +270,9 @@ namespace shader_playground {
"--vertex_shader_output_type=" + vertexShaderType,
"--dxbc_source_map=true",
};
if (translationComboBox.SelectedIndex == 1) {
startArguments.Add("--shader_output_dxbc_rov=true");
if (translationComboBox.SelectedIndex == 1 ||
translationComboBox.SelectedIndex == 3) {
startArguments.Add("--shader_output_pixel_shader_interlock=true");
}
startInfo = new ProcessStartInfo(compilerPath_);