add split_map class for mapping keys to values in a way that optimizes for frequent searches and infrequent insertions/removals remove jump table implementation of GetColorRenderTargetFormatComponentCount, it was appearing relatively high in profiles. instead pack the component counts into a single 32 bit word, which is indexed by shifting Add cvar to align all basic blocks to a boundary Add mmio aware load paths liberally apply XE_RESTRICT in ringbuffer related code Removed the IS_TRUE and IS_FALSE opcodes, they were pointless duplicates of COMPARE_EQ/COMPARE_NE and i want to simplify our set of opcodes for future backends More work on LVSR/LVSL/STVR/STVL opcodes Optimized X64 translated code emission, now only compute instrkey once Add code for pre-computing integer division magic numbers Optimized GetHostViewportInfo a little Move args for GetHostViewportInfo into a class, cache the result and compare for future queries. moved GetHostViewportInfo far lower on the profile Add (currently not functional, and very racy) asynchronous memcpy code. will improve it and actually use it in future commits. Add non-temporal memcpy function for huge page-aligned allocations. Used for copying to shared memory/readback hoist are_accumulated_render_targets_valid_ check out of loop in render_target_cache already bound check. Add stosb/movsb code for small constant memcpys/memsets that arent worth the overhead of memcpy/memset
744 lines
32 KiB
C++
744 lines
32 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_GPU_DRAW_UTIL_H_
|
|
#define XENIA_GPU_DRAW_UTIL_H_
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <utility>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/gpu/register_file.h"
|
|
#include "xenia/gpu/registers.h"
|
|
#include "xenia/gpu/shader.h"
|
|
#include "xenia/gpu/trace_writer.h"
|
|
#include "xenia/gpu/xenos.h"
|
|
#include "xenia/memory.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace draw_util {
|
|
|
|
constexpr bool IsPrimitiveLine(bool vgt_output_path_is_tessellation_enable,
|
|
xenos::PrimitiveType type) {
|
|
if (vgt_output_path_is_tessellation_enable &&
|
|
type == xenos::PrimitiveType::kLinePatch) {
|
|
// For patch primitive types, the major mode is always explicit, so just
|
|
// checking if VGT_OUTPUT_PATH_CNTL::path_select is kTessellationEnable is
|
|
// enough.
|
|
return true;
|
|
}
|
|
switch (type) {
|
|
case xenos::PrimitiveType::kLineList:
|
|
case xenos::PrimitiveType::kLineStrip:
|
|
case xenos::PrimitiveType::kLineLoop:
|
|
case xenos::PrimitiveType::k2DLineStrip:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool IsPrimitiveLine(const RegisterFile& regs) {
|
|
return IsPrimitiveLine(regs.Get<reg::VGT_OUTPUT_PATH_CNTL>().path_select ==
|
|
xenos::VGTOutputPath::kTessellationEnable,
|
|
regs.Get<reg::VGT_DRAW_INITIATOR>().prim_type);
|
|
}
|
|
|
|
// Polygonal primitive types (not including points and lines) are rasterized as
|
|
// triangles, have front and back faces, and also support face culling and fill
|
|
// modes (polymode_front_ptype, polymode_back_ptype). Other primitive types are
|
|
// always "front" (but don't support front face and back face culling, according
|
|
// to OpenGL and Vulkan specifications - even if glCullFace is
|
|
// GL_FRONT_AND_BACK, points and lines are still drawn), and may in some cases
|
|
// use the "para" registers instead of "front" or "back" (for "parallelogram" -
|
|
// like poly_offset_para_enable).
|
|
constexpr bool IsPrimitivePolygonal(bool vgt_output_path_is_tessellation_enable,
|
|
xenos::PrimitiveType type) {
|
|
if (vgt_output_path_is_tessellation_enable &&
|
|
(type == xenos::PrimitiveType::kTrianglePatch ||
|
|
type == xenos::PrimitiveType::kQuadPatch)) {
|
|
// For patch primitive types, the major mode is always explicit, so just
|
|
// checking if VGT_OUTPUT_PATH_CNTL::path_select is kTessellationEnable is
|
|
// enough.
|
|
return true;
|
|
}
|
|
switch (type) {
|
|
case xenos::PrimitiveType::kTriangleList:
|
|
case xenos::PrimitiveType::kTriangleFan:
|
|
case xenos::PrimitiveType::kTriangleStrip:
|
|
case xenos::PrimitiveType::kTriangleWithWFlags:
|
|
case xenos::PrimitiveType::kQuadList:
|
|
case xenos::PrimitiveType::kQuadStrip:
|
|
case xenos::PrimitiveType::kPolygon:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
// TODO(Triang3l): Investigate how kRectangleList should be treated - possibly
|
|
// actually drawn as two polygons on the console, however, the current
|
|
// geometry shader doesn't care about the winding order - allowing backface
|
|
// culling for rectangles currently breaks 4D53082D.
|
|
return false;
|
|
}
|
|
|
|
inline bool IsPrimitivePolygonal(const RegisterFile& regs) {
|
|
return IsPrimitivePolygonal(
|
|
regs.Get<reg::VGT_OUTPUT_PATH_CNTL>().path_select ==
|
|
xenos::VGTOutputPath::kTessellationEnable,
|
|
regs.Get<reg::VGT_DRAW_INITIATOR>().prim_type);
|
|
}
|
|
|
|
// Whether with the current state, any samples to rasterize (for any reason, not
|
|
// only to write something to a render target, but also to do sample counting or
|
|
// pixel shader memexport) can be generated. Finally dropping draw calls can
|
|
// only be done if the vertex shader doesn't memexport. Checks mostly special
|
|
// cases (for both the guest and usual host implementations), not everything
|
|
// like whether viewport / scissor are empty (until this truly matters in any
|
|
// game, of course).
|
|
bool IsRasterizationPotentiallyDone(const RegisterFile& regs,
|
|
bool primitive_polygonal);
|
|
|
|
// Direct3D 10.1+ standard sample positions, also used in Vulkan, for
|
|
// calculations related to host MSAA, in 1/16th of a pixel.
|
|
extern const int8_t kD3D10StandardSamplePositions2x[2][2];
|
|
extern const int8_t kD3D10StandardSamplePositions4x[4][2];
|
|
|
|
reg::RB_DEPTHCONTROL GetNormalizedDepthControl(const RegisterFile& regs);
|
|
|
|
// Direct3D 9 and Xenos constant polygon offset is an absolute floating-point
|
|
// value.
|
|
// It's possibly treated just as an absolute offset by the Xenos too - the
|
|
// PA_SU_POLY_OFFSET_DB_FMT_CNTL::POLY_OFFSET_DB_IS_FLOAT_FMT switch was added
|
|
// later in the R6xx, though this needs verification.
|
|
// 5454082B, for example, for float24, sets the bias to 0.000002 - or slightly
|
|
// above 2^-19.
|
|
// The total polygon offset formula specified by Direct3D 9 is:
|
|
// `offset = slope * slope factor + constant offset`
|
|
//
|
|
// Direct3D 10, Metal, OpenGL and Vulkan, however, take the constant polygon
|
|
// offset factor as a relative value, with the formula being:
|
|
// `offset = slope * slope factor +
|
|
// maximum resolvable difference * constant factor`
|
|
// where the maximum resolvable difference is:
|
|
// - For a fixed-point depth buffer, the minimum representable non-zero value in
|
|
// the depth buffer, that is 1 / (2^24 - 1) for unorm24 (on Vulkan though it's
|
|
// allowed to be up to 2 / 2^24 in this case).
|
|
// - For a floating-point depth buffer, it's:
|
|
// 2 ^ (exponent of the maximum Z in the primitive - the number of explicitly
|
|
// stored mantissa bits)
|
|
// (23 explicitly stored bits for float32 - and 20 explicitly stored bits for
|
|
// float24).
|
|
//
|
|
// While the polygon offset is a fixed-function feature in the pipeline, and the
|
|
// formula can't be toggled between absolute and relative, it's important that
|
|
// Xenia translates the guest absolute depth bias into the host relative depth
|
|
// bias in a way that the values that separate coplanar geometry on the guest
|
|
// qualitatively also still correctly separate them on the host.
|
|
//
|
|
// It also should be taken into account that on Xenia, float32 depth values may
|
|
// be snapped to float24 directly in the translated pixel shaders (to prevent
|
|
// data loss if after reuploading a depth buffer to the EDRAM there's no way to
|
|
// recover the full-precision value, that results in the inability to perform
|
|
// more rendering passes for the same geometry), and not only to the nearest
|
|
// value, but also just truncating them (so in case of data loss, the "greater
|
|
// or equal" depth test function still works).
|
|
//
|
|
// Because of this, the depth bias may be lost if Xenia translates it into a too
|
|
// small value, and the conversion of the depth is done in the pixel shader.
|
|
// Specifically, Xenia should not simply convert a value that separates coplanar
|
|
// primitives as float24 just into something that still separates them as
|
|
// float32. Essentially, if conversion to float24 is done in the pixel shader,
|
|
// Xenia should make sure the polygon offset on the host is calculated as if the
|
|
// host had float24 depth too, not float32.
|
|
|
|
// Applies to both native host unorm24, and unorm24 emulated as host float32.
|
|
// For native unorm24, this is exactly the inverse of the minimum representable
|
|
// non-zero value.
|
|
// For unorm24 emulated as float32, the minimum representable non-zero value for
|
|
// a primitive in the [0.5, 1) range (the worst case that forward depth reaches
|
|
// very quickly, at nearly `2 * near clipping plane distance`) is 2 ^ (-1 - 23),
|
|
// or 2^-24, and this factor is almost 2^24.
|
|
constexpr float kD3D10PolygonOffsetFactorUnorm24 =
|
|
float((UINT32_C(1) << 24) - 1);
|
|
|
|
// For a host floating-point depth buffer, the integer value of the depth bias
|
|
// is roughly how many ULPs primitives should be separated by.
|
|
//
|
|
// Float24, however, has 3 mantissa bits fewer than float32 - so one float24 ULP
|
|
// corresponds to 8 float32 ULPs - which means each conceptual "layer" of the
|
|
// guest value should correspond to a polygon offset of 8. So, after the guest
|
|
// absolute value is converted to "layers", it should be multiplied by 8 before
|
|
// being used on the host with a float32 depth buffer.
|
|
//
|
|
// The scale for converting the guest absolute depth bias to the "layers" needs
|
|
// to be determined for the worst case - specifically, the [0.5, 1) range (1 is
|
|
// a single value, so there's no need to take it into consideration). In this
|
|
// range, Z values have the exponent of -1. Therefore, for float24, the absolute
|
|
// offset is obtained from the "layer index" in this range as (disregarding the
|
|
// slope term):
|
|
// offset = 2 ^ (-1 - 20) * constant factor
|
|
// Thus, to obtain the constant factor from the absolute offset in the range
|
|
// with the lowest absolute precision, the offset needs to be multiplied by
|
|
// 2^21.
|
|
//
|
|
// Finally, the 0...0.5 range may be used on the host to represent the 0...1
|
|
// guest depth range to be able to copy all possible encodings, which are
|
|
// [0, 2), via a [0, 1] depth output variable, during EDRAM contents
|
|
// reinterpretation. This is done by scaling the viewport depth bounds by 0.5.
|
|
// However, there's no need to do anything to handle this scenario in the
|
|
// polygon offset - it's calculated after applying the viewport transformation,
|
|
// and the maximum Z value in the primitive will have an exponent lowered by 1,
|
|
// thus the result will also have an exponent lowered by 1 - exactly what's
|
|
// needed for remapping 0...1 to 0...0.5.
|
|
constexpr float kD3D10PolygonOffsetFactorFloat24 =
|
|
float(UINT32_C(1) << (21 + 3));
|
|
|
|
inline int32_t GetD3D10IntegerPolygonOffset(
|
|
xenos::DepthRenderTargetFormat depth_format, float polygon_offset) {
|
|
bool is_float24 = depth_format == xenos::DepthRenderTargetFormat::kD24FS8;
|
|
// Using `ceil` because more offset is better, especially if flooring would
|
|
// result in 0 - conceptually, if the offset is used at all, primitives need
|
|
// to be separated in the depth buffer.
|
|
int32_t polygon_offset_int = int32_t(
|
|
std::ceil(std::abs(polygon_offset) *
|
|
(is_float24 ? kD3D10PolygonOffsetFactorFloat24 * (1.0f / 8.0f)
|
|
: kD3D10PolygonOffsetFactorUnorm24)));
|
|
// For float24, the conversion may be done in the translated pixel shaders,
|
|
// including via truncation rather than rounding to the nearest. So, making
|
|
// the integer bias always in the increments of 2^3 (2 ^ the difference in the
|
|
// mantissa bit count between float32 and float24), and because of that, doing
|
|
// `ceil` before changing the units from float24 ULPs to float32 ULPs.
|
|
if (is_float24) {
|
|
polygon_offset_int <<= 3;
|
|
}
|
|
return polygon_offset < 0 ? -polygon_offset_int : polygon_offset_int;
|
|
}
|
|
|
|
// For hosts not supporting separate front and back polygon offsets, returns the
|
|
// polygon offset for the face which likely needs the offset the most (and that
|
|
// will not be culled). The values returned will have the units of the original
|
|
// registers (the scale is for 1/16 subpixels, multiply by
|
|
// xenos::kPolygonOffsetScaleSubpixelUnit outside if the value for pixels is
|
|
// needed).
|
|
void GetPreferredFacePolygonOffset(const RegisterFile& regs,
|
|
bool primitive_polygonal, float& scale_out,
|
|
float& offset_out);
|
|
|
|
inline bool DoesCoverageDependOnAlpha(reg::RB_COLORCONTROL rb_colorcontrol) {
|
|
return (rb_colorcontrol.alpha_test_enable &&
|
|
rb_colorcontrol.alpha_func != xenos::CompareFunction::kAlways) ||
|
|
rb_colorcontrol.alpha_to_mask_enable;
|
|
}
|
|
|
|
// Whether the pixel shader can be disabled on the host to speed up depth
|
|
// pre-passes and shadowmaps. The shader must have its ucode analyzed. If
|
|
// IsRasterizationPotentiallyDone, this shouldn't be called, and assumed false
|
|
// instead. Helps reject the pixel shader in some cases - memexport draws in
|
|
// 4D5307E6, and also most of some 1-point draws not covering anything done for
|
|
// some reason in different games with a leftover pixel shader from the previous
|
|
// draw, but with SQ_PROGRAM_CNTL destroyed, reducing the number of
|
|
// unpredictable unneeded translations of random shaders with different host
|
|
// modification bits, such as register count and depth format-related (though
|
|
// shaders with side effects on depth or memory export will still be preserved).
|
|
bool IsPixelShaderNeededWithRasterization(const Shader& shader,
|
|
const RegisterFile& regs);
|
|
|
|
struct ViewportInfo {
|
|
// Offset from render target UV = 0 to +UV.
|
|
// For simplicity of cropping to the maximum size on the host; to match the
|
|
// Direct3D 12 clipping / scissoring behavior with a fractional viewport, to
|
|
// floor(TopLeftXY) ... floor(TopLeftXY + WidthHeight), on the real AMD, Intel
|
|
// and Nvidia hardware (not WARP); as well as to hide the differences between
|
|
// 0 and 8+ viewportSubPixelBits on Vulkan, and to prevent any numerical error
|
|
// in bound checking in host APIs, viewport bounds are returned as integers.
|
|
// Also they're returned as non-negative, also to make it easier to crop (so
|
|
// Vulkan maxViewportDimensions and viewportBoundsRange don't have to be
|
|
// handled separately - maxViewportDimensions is greater than or equal to the
|
|
// largest framebuffer image size, so it's safe, and viewportBoundsRange is
|
|
// always bigger than maxViewportDimensions. All fractional offsetting,
|
|
// including the half-pixel offset, and cropping are handled via ndc_scale and
|
|
// ndc_offset.
|
|
uint32_t xy_offset[2];
|
|
// Extent can be zero for an empty viewport - host APIs not supporting empty
|
|
// viewports need to use an empty scissor rectangle.
|
|
uint32_t xy_extent[2];
|
|
float z_min;
|
|
float z_max;
|
|
// The scale is applied before the offset (like using multiply-add).
|
|
float ndc_scale[3];
|
|
float ndc_offset[3];
|
|
};
|
|
static_assert(sizeof(xenos::DepthRenderTargetFormat) == sizeof(uint32_t),
|
|
"Change in depthrendertargetformat throws off "
|
|
"getviewportinfoargs by a bit");
|
|
struct GetViewportInfoArgs {
|
|
union alignas(64) {
|
|
struct {
|
|
// group 1
|
|
uint32_t x_max;
|
|
uint32_t y_max;
|
|
union {
|
|
struct {
|
|
uint32_t origin_bottom_left : 1;
|
|
uint32_t allow_reverse_z : 1;
|
|
uint32_t convert_z_to_float24 : 1;
|
|
uint32_t full_float24_in_0_to_1 : 1;
|
|
uint32_t pixel_shader_writes_depth : 1;
|
|
xenos::DepthRenderTargetFormat depth_format : 1;
|
|
};
|
|
uint32_t packed_portions;
|
|
};
|
|
reg::RB_DEPTHCONTROL normalized_depth_control;
|
|
// group 2
|
|
reg::PA_CL_CLIP_CNTL pa_cl_clip_cntl;
|
|
reg::PA_CL_VTE_CNTL pa_cl_vte_cntl;
|
|
reg::PA_SU_SC_MODE_CNTL pa_su_sc_mode_cntl;
|
|
reg::PA_SU_VTX_CNTL pa_su_vtx_cntl;
|
|
// group 3
|
|
reg::PA_SC_WINDOW_OFFSET pa_sc_window_offset;
|
|
float PA_CL_VPORT_XSCALE;
|
|
float PA_CL_VPORT_YSCALE;
|
|
float PA_CL_VPORT_ZSCALE;
|
|
|
|
float PA_CL_VPORT_XOFFSET;
|
|
float PA_CL_VPORT_YOFFSET;
|
|
float PA_CL_VPORT_ZOFFSET;
|
|
uint32_t padding_set_to_0;
|
|
};
|
|
#if XE_ARCH_AMD64 == 1
|
|
struct {
|
|
__m128i first4; // x_max, y_max, packed_portions,
|
|
// normalized_depth_control
|
|
__m128i second4; // pa_cl_clip_cntl, pa_cl_vte_cntl, pa_su_sc_mode_cntl,
|
|
// pa_su_vtx_cntl
|
|
__m128i third4; // pa_sc_window_offset, PA_CL_VPORT_XSCALE,
|
|
// PA_CL_VPORT_YSCALE, PA_CL_VPORT_ZSCALE
|
|
__m128i last4; // PA_CL_VPORT_XOFFSET, PA_CL_VPORT_YOFFSET,
|
|
// PA_CL_VPORT_ZOFFSET, padding_set_to_0
|
|
};
|
|
#endif
|
|
};
|
|
|
|
// everything that follows here does not need to be compared
|
|
uint32_t draw_resolution_scale_x;
|
|
uint32_t draw_resolution_scale_y;
|
|
divisors::MagicDiv draw_resolution_scale_x_divisor;
|
|
divisors::MagicDiv draw_resolution_scale_y_divisor;
|
|
void Setup(uint32_t _draw_resolution_scale_x,
|
|
uint32_t _draw_resolution_scale_y,
|
|
divisors::MagicDiv _draw_resolution_scale_x_divisor,
|
|
divisors::MagicDiv _draw_resolution_scale_y_divisor,
|
|
bool _origin_bottom_left, uint32_t _x_max, uint32_t _y_max,
|
|
bool _allow_reverse_z,
|
|
reg::RB_DEPTHCONTROL _normalized_depth_control,
|
|
bool _convert_z_to_float24, bool _full_float24_in_0_to_1,
|
|
bool _pixel_shader_writes_depth) {
|
|
packed_portions = 0;
|
|
padding_set_to_0 = 0; // important to zero this
|
|
draw_resolution_scale_x = _draw_resolution_scale_x;
|
|
draw_resolution_scale_y = _draw_resolution_scale_y;
|
|
draw_resolution_scale_x_divisor = _draw_resolution_scale_x_divisor;
|
|
draw_resolution_scale_y_divisor = _draw_resolution_scale_y_divisor;
|
|
origin_bottom_left = _origin_bottom_left;
|
|
x_max = _x_max;
|
|
y_max = _y_max;
|
|
allow_reverse_z = _allow_reverse_z;
|
|
normalized_depth_control = _normalized_depth_control;
|
|
convert_z_to_float24 = _convert_z_to_float24;
|
|
full_float24_in_0_to_1 = _full_float24_in_0_to_1;
|
|
pixel_shader_writes_depth = _pixel_shader_writes_depth;
|
|
}
|
|
|
|
void SetupRegisterValues(const RegisterFile& regs) {
|
|
pa_cl_clip_cntl = regs.Get<reg::PA_CL_CLIP_CNTL>();
|
|
pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
|
|
pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
|
pa_su_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
|
|
PA_CL_VPORT_XSCALE = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32;
|
|
PA_CL_VPORT_YSCALE = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32;
|
|
PA_CL_VPORT_ZSCALE = regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32;
|
|
PA_CL_VPORT_XOFFSET = regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32;
|
|
PA_CL_VPORT_YOFFSET = regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32;
|
|
PA_CL_VPORT_ZOFFSET = regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32;
|
|
pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
|
|
depth_format = regs.Get<reg::RB_DEPTH_INFO>().depth_format;
|
|
}
|
|
XE_FORCEINLINE
|
|
bool operator==(const GetViewportInfoArgs& prev) {
|
|
#if XE_ARCH_AMD64 == 0
|
|
bool result = true;
|
|
|
|
auto accum_eq = [&result](auto x, auto y) { result &= (x == y); };
|
|
|
|
#define EQC(field) accum_eq(field, prev.field)
|
|
EQC(x_max);
|
|
EQC(y_max);
|
|
EQC(packed_portions);
|
|
EQC(normalized_depth_control.value);
|
|
EQC(pa_cl_clip_cntl.value);
|
|
EQC(pa_cl_vte_cntl.value);
|
|
|
|
EQC(pa_su_sc_mode_cntl.value);
|
|
EQC(pa_su_vtx_cntl.value);
|
|
EQC(PA_CL_VPORT_XSCALE);
|
|
EQC(PA_CL_VPORT_YSCALE);
|
|
EQC(PA_CL_VPORT_ZSCALE);
|
|
EQC(PA_CL_VPORT_XOFFSET);
|
|
EQC(PA_CL_VPORT_YOFFSET);
|
|
EQC(PA_CL_VPORT_ZOFFSET);
|
|
EQC(pa_sc_window_offset.value);
|
|
|
|
#undef EQC
|
|
return result;
|
|
#else
|
|
__m128i mask1 = _mm_cmpeq_epi32(first4, prev.first4);
|
|
__m128i mask2 = _mm_cmpeq_epi32(second4, prev.second4);
|
|
|
|
__m128i mask3 = _mm_cmpeq_epi32(third4, prev.third4);
|
|
__m128i unified1 = _mm_and_si128(mask1, mask2);
|
|
__m128i mask4 = _mm_cmpeq_epi32(last4, prev.last4);
|
|
|
|
__m128i unified2 = _mm_and_si128(unified1, mask3);
|
|
|
|
__m128i unified3 = _mm_and_si128(unified2, mask4);
|
|
|
|
return _mm_movemask_epi8(unified3) == 0xFFFF;
|
|
|
|
#endif
|
|
}
|
|
};
|
|
|
|
// Converts the guest viewport (or fakes one if drawing without a viewport) to
|
|
// a viewport, plus values to multiply-add the returned position by, usable on
|
|
// host graphics APIs such as Direct3D 11+ and Vulkan, also forcing it to the
|
|
// Direct3D clip space with 0...W Z rather than -W...W.
|
|
void GetHostViewportInfo(GetViewportInfoArgs* XE_RESTRICT args,
|
|
ViewportInfo& viewport_info_out);
|
|
|
|
struct Scissor {
|
|
// Offset from render target UV = 0 to +UV.
|
|
uint32_t offset[2];
|
|
// Extent can be zero.
|
|
uint32_t extent[2];
|
|
};
|
|
void GetScissor(const RegisterFile& regs, Scissor& scissor_out,
|
|
bool clamp_to_surface_pitch = true);
|
|
|
|
// Returns the color component write mask for the draw command taking into
|
|
// account which color targets are written to by the pixel shader, as well as
|
|
// components that don't exist in the formats of the render targets (render
|
|
// targets with only non-existent components written are skipped, but
|
|
// non-existent components are forced to written if some existing components of
|
|
// the render target are actually used to make sure the host driver doesn't try
|
|
// to take a slow path involving reading and mixing if there are any disabled
|
|
// components even if they don't actually exist).
|
|
uint32_t GetNormalizedColorMask(const RegisterFile& regs,
|
|
uint32_t pixel_shader_writes_color_targets);
|
|
|
|
// Never an identity conversion - can always write conditional move instructions
|
|
// to shaders that will be no-ops for conversion from guest to host samples.
|
|
// While we don't know the exact guest sample pattern, due to the way
|
|
// multisampled render targets are stored in the memory (like 1x2 single-sampled
|
|
// pixels with 2x MSAA, or like 2x2 single-sampled pixels with 4x), assuming
|
|
// that the sample 0 is the top sample, and the sample 1 is the bottom one.
|
|
inline uint32_t GetD3D10SampleIndexForGuest2xMSAA(
|
|
uint32_t guest_sample_index, bool native_2x_msaa_supported) {
|
|
assert(guest_sample_index <= 1);
|
|
if (native_2x_msaa_supported) {
|
|
// On Direct3D 10.1 with native 2x MSAA, the top-left sample is 1, and the
|
|
// bottom-right sample is 0.
|
|
return guest_sample_index ? 0 : 1;
|
|
}
|
|
// When native 2x MSAA is not supported, using the top-left (0) and the
|
|
// bottom-right (3) samples of the guaranteed 4x MSAA.
|
|
return guest_sample_index ? 3 : 0;
|
|
}
|
|
|
|
// To avoid passing values that the shader won't understand (even though
|
|
// Direct3D 9 shouldn't pass them anyway).
|
|
xenos::CopySampleSelect SanitizeCopySampleSelect(
|
|
xenos::CopySampleSelect copy_sample_select, xenos::MsaaSamples msaa_samples,
|
|
bool is_depth);
|
|
|
|
// Packed structures are small and can be passed to the shaders in root/push
|
|
// constants.
|
|
|
|
union ResolveEdramInfo {
|
|
uint32_t packed;
|
|
struct {
|
|
// With 32bpp/64bpp taken into account.
|
|
uint32_t pitch_tiles : xenos::kEdramPitchTilesBits;
|
|
xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits;
|
|
uint32_t is_depth : 1;
|
|
// With offset to the region that edram_offset_x/y_div_8 are relative to.
|
|
uint32_t base_tiles : xenos::kEdramBaseTilesBits;
|
|
uint32_t format : xenos::kRenderTargetFormatBits;
|
|
uint32_t format_is_64bpp : 1;
|
|
// Whether to fill the half-pixel offset gap on the left and the top sides
|
|
// of the resolve region with the contents of the first surely covered
|
|
// column / row with resolution scaling.
|
|
uint32_t fill_half_pixel_offset : 1;
|
|
};
|
|
ResolveEdramInfo() : packed(0) { static_assert_size(*this, sizeof(packed)); }
|
|
};
|
|
|
|
union ResolveCoordinateInfo {
|
|
uint32_t packed;
|
|
struct {
|
|
// In pixels relatively to the origin of the EDRAM base tile.
|
|
// 0...9 for 0...72.
|
|
uint32_t edram_offset_x_div_8 : 4;
|
|
// 0...1 for 0...8.
|
|
uint32_t edram_offset_y_div_8 : 1;
|
|
|
|
// In pixels.
|
|
// May be zero if the original rectangle was somehow specified in a
|
|
// totally broken way - in this case, the resolve must be dropped.
|
|
uint32_t width_div_8 : xenos::kResolveSizeBits -
|
|
xenos::kResolveAlignmentPixelsLog2;
|
|
|
|
// 1 to 7.
|
|
uint32_t draw_resolution_scale_x : 3;
|
|
uint32_t draw_resolution_scale_y : 3;
|
|
};
|
|
ResolveCoordinateInfo() : packed(0) {
|
|
static_assert_size(*this, sizeof(packed));
|
|
}
|
|
};
|
|
|
|
// Returns tiles actually covered by a resolve area. Row length used is width of
|
|
// the area in tiles, but the pitch between rows is edram_info.pitch_tiles.
|
|
void GetResolveEdramTileSpan(ResolveEdramInfo edram_info,
|
|
ResolveCoordinateInfo coordinate_info,
|
|
uint32_t height_div_8, uint32_t& base_out,
|
|
uint32_t& row_length_used_out, uint32_t& rows_out);
|
|
|
|
union ResolveCopyDestCoordinateInfo {
|
|
uint32_t packed;
|
|
struct {
|
|
// 0...16384/32.
|
|
uint32_t pitch_aligned_div_32 : xenos::kTexture2DCubeMaxWidthHeightLog2 +
|
|
2 - xenos::kTextureTileWidthHeightLog2;
|
|
uint32_t height_aligned_div_32 : xenos::kTexture2DCubeMaxWidthHeightLog2 +
|
|
2 - xenos::kTextureTileWidthHeightLog2;
|
|
|
|
// Up to the maximum period of the texture tiled address function (128x128
|
|
// for 2D 1bpb).
|
|
uint32_t offset_x_div_8 : 7 - xenos::kResolveAlignmentPixelsLog2;
|
|
uint32_t offset_y_div_8 : 7 - xenos::kResolveAlignmentPixelsLog2;
|
|
|
|
xenos::CopySampleSelect copy_sample_select : 3;
|
|
};
|
|
ResolveCopyDestCoordinateInfo() : packed(0) {
|
|
static_assert_size(*this, sizeof(packed));
|
|
}
|
|
};
|
|
|
|
// For backends with Shader Model 5-like compute, host shaders to use to perform
|
|
// copying in resolve operations.
|
|
enum class ResolveCopyShaderIndex {
|
|
kFast32bpp1x2xMSAA,
|
|
kFast32bpp4xMSAA,
|
|
kFast64bpp1x2xMSAA,
|
|
kFast64bpp4xMSAA,
|
|
|
|
kFull8bpp,
|
|
kFull16bpp,
|
|
kFull32bpp,
|
|
kFull64bpp,
|
|
kFull128bpp,
|
|
|
|
kCount,
|
|
kUnknown = kCount,
|
|
};
|
|
|
|
struct ResolveCopyShaderInfo {
|
|
// Debug name of the pipeline state object with this shader.
|
|
const char* debug_name;
|
|
// Whether the EDRAM source needs be bound as a raw buffer (ByteAddressBuffer
|
|
// in Direct3D) since it can load different numbers of 32-bit values at once
|
|
// on some hardware. If the host API doesn't support raw buffers, a typed
|
|
// buffer with source_bpe_log2-byte elements needs to be used instead.
|
|
bool source_is_raw;
|
|
// Log2 of bytes per element of the type of the EDRAM buffer bound to the
|
|
// shader (at least 2).
|
|
uint32_t source_bpe_log2;
|
|
// Log2 of bytes per element of the type of the destination buffer bound to
|
|
// the shader (at least 2 because of the 128 megatexel minimum requirement on
|
|
// Direct3D 10+ - D3D12_REQ_BUFFER_RESOURCE_TEXEL_COUNT_2_TO_EXP - that
|
|
// prevents binding the entire shared memory buffer with smaller element
|
|
// sizes).
|
|
uint32_t dest_bpe_log2;
|
|
// Log2 of number of pixels in a single thread group along X and Y. 64 threads
|
|
// per group preferred (GCN lane count).
|
|
uint32_t group_size_x_log2, group_size_y_log2;
|
|
};
|
|
|
|
extern const ResolveCopyShaderInfo
|
|
resolve_copy_shader_info[size_t(ResolveCopyShaderIndex::kCount)];
|
|
|
|
struct ResolveCopyShaderConstants {
|
|
// When the destination base is not needed (not binding the entire shared
|
|
// memory buffer - with resoluion scaling, for instance), only the
|
|
// DestRelative part may be passed to the shader to use less constants.
|
|
struct DestRelative {
|
|
ResolveEdramInfo edram_info;
|
|
ResolveCoordinateInfo coordinate_info;
|
|
reg::RB_COPY_DEST_INFO dest_info;
|
|
ResolveCopyDestCoordinateInfo dest_coordinate_info;
|
|
};
|
|
DestRelative dest_relative;
|
|
uint32_t dest_base;
|
|
};
|
|
|
|
struct ResolveClearShaderConstants {
|
|
// rt_specific is different for color and depth, the rest is the same and can
|
|
// be preserved in the root bindings when going from depth to color.
|
|
struct RenderTargetSpecific {
|
|
uint32_t clear_value[2];
|
|
ResolveEdramInfo edram_info;
|
|
};
|
|
RenderTargetSpecific rt_specific;
|
|
ResolveCoordinateInfo coordinate_info;
|
|
};
|
|
|
|
struct ResolveInfo {
|
|
reg::RB_COPY_CONTROL rb_copy_control;
|
|
|
|
// depth_edram_info / depth_original_base and color_edram_info /
|
|
// color_original_base are set up if copying or clearing color and depth
|
|
// respectively, according to RB_COPY_CONTROL.
|
|
ResolveEdramInfo depth_edram_info;
|
|
ResolveEdramInfo color_edram_info;
|
|
// Original bases, without adjustment to a 160x32 region for packed offsets,
|
|
// for locating host render targets to perform clears if host render targets
|
|
// are used for EDRAM emulation - the same as the base that the render target
|
|
// will likely be used for drawing next, to prevent unneeded tile ownership
|
|
// transfers between clears and first usage if clearing a subregion.
|
|
uint32_t depth_original_base;
|
|
uint32_t color_original_base;
|
|
|
|
ResolveCoordinateInfo coordinate_info;
|
|
// Like coordinate_info.width_div_8, but not needed for shaders.
|
|
// In pixels.
|
|
// May be zero if the original rectangle was somehow specified in a totally
|
|
// broken way - in this case, the resolve must be dropped.
|
|
uint32_t height_div_8;
|
|
|
|
reg::RB_COPY_DEST_INFO copy_dest_info;
|
|
ResolveCopyDestCoordinateInfo copy_dest_coordinate_info;
|
|
|
|
// The address of the texture or the location within the texture that
|
|
// copy_dest_coordinate_info.offset_x/y_div_8 - the origin of the copy
|
|
// destination - is relative to.
|
|
uint32_t copy_dest_base;
|
|
// Memory range that will potentially be modified by copying to the texture.
|
|
// copy_dest_extent_length may be zero if something is wrong with the
|
|
// destination, in this case, clearing may still be done, but copying must be
|
|
// dropped.
|
|
uint32_t copy_dest_extent_start;
|
|
uint32_t copy_dest_extent_length;
|
|
|
|
// The clear shaders always write to a uint4 view of EDRAM.
|
|
uint32_t rb_depth_clear;
|
|
uint32_t rb_color_clear;
|
|
uint32_t rb_color_clear_lo;
|
|
|
|
bool IsCopyingDepth() const {
|
|
return rb_copy_control.copy_src_select >= xenos::kMaxColorRenderTargets;
|
|
}
|
|
|
|
// See GetResolveEdramTileSpan documentation for explanation.
|
|
void GetCopyEdramTileSpan(uint32_t& base_out, uint32_t& row_length_used_out,
|
|
uint32_t& rows_out, uint32_t& pitch_out) const {
|
|
ResolveEdramInfo edram_info =
|
|
IsCopyingDepth() ? depth_edram_info : color_edram_info;
|
|
GetResolveEdramTileSpan(edram_info, coordinate_info, height_div_8, base_out,
|
|
row_length_used_out, rows_out);
|
|
pitch_out = edram_info.pitch_tiles;
|
|
}
|
|
|
|
ResolveCopyShaderIndex GetCopyShader(
|
|
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
|
|
ResolveCopyShaderConstants& constants_out, uint32_t& group_count_x_out,
|
|
uint32_t& group_count_y_out) const;
|
|
|
|
bool IsClearingDepth() const {
|
|
return rb_copy_control.depth_clear_enable != 0;
|
|
}
|
|
|
|
bool IsClearingColor() const {
|
|
return !IsCopyingDepth() && rb_copy_control.color_clear_enable != 0;
|
|
}
|
|
|
|
void GetDepthClearShaderConstants(
|
|
ResolveClearShaderConstants& constants_out) const {
|
|
assert_true(IsClearingDepth());
|
|
constants_out.rt_specific.clear_value[0] = rb_depth_clear;
|
|
constants_out.rt_specific.clear_value[1] = rb_depth_clear;
|
|
constants_out.rt_specific.edram_info = depth_edram_info;
|
|
constants_out.coordinate_info = coordinate_info;
|
|
}
|
|
|
|
void GetColorClearShaderConstants(
|
|
ResolveClearShaderConstants& constants_out) const {
|
|
assert_true(IsClearingColor());
|
|
// Not doing -32...32 to -1...1 clamping here as a hack for k_16_16 and
|
|
// k_16_16_16_16 blending emulation when using host render targets as it
|
|
// would be inconsistent with the usual way of clearing with a depth quad.
|
|
// TODO(Triang3l): Check which 32-bit portion is in which register.
|
|
constants_out.rt_specific.clear_value[0] = rb_color_clear;
|
|
constants_out.rt_specific.clear_value[1] = rb_color_clear_lo;
|
|
constants_out.rt_specific.edram_info = color_edram_info;
|
|
constants_out.coordinate_info = coordinate_info;
|
|
}
|
|
|
|
std::pair<uint32_t, uint32_t> GetClearShaderGroupCount(
|
|
uint32_t draw_resolution_scale_x,
|
|
uint32_t draw_resolution_scale_y) const {
|
|
// 8 guest MSAA samples per invocation.
|
|
uint32_t width_samples_div_8 = coordinate_info.width_div_8;
|
|
uint32_t height_samples_div_8 = height_div_8;
|
|
xenos::MsaaSamples samples = IsCopyingDepth()
|
|
? depth_edram_info.msaa_samples
|
|
: color_edram_info.msaa_samples;
|
|
if (samples >= xenos::MsaaSamples::k2X) {
|
|
height_samples_div_8 <<= 1;
|
|
if (samples >= xenos::MsaaSamples::k4X) {
|
|
width_samples_div_8 <<= 1;
|
|
}
|
|
}
|
|
width_samples_div_8 *= draw_resolution_scale_x;
|
|
height_samples_div_8 *= draw_resolution_scale_y;
|
|
return std::make_pair((width_samples_div_8 + uint32_t(7)) >> 3,
|
|
height_samples_div_8);
|
|
}
|
|
};
|
|
|
|
// Returns false if there was an error obtaining the info making it totally
|
|
// invalid. fixed_rg[ba]16_truncated_to_minus_1_to_1 is false if 16_16[_16_16]
|
|
// color render target formats are properly emulated as -32...32, true if
|
|
// emulated as snorm, with range limited to -1...1, but with correct blending
|
|
// within that range.
|
|
bool GetResolveInfo(const RegisterFile& regs, const Memory& memory,
|
|
TraceWriter& trace_writer, uint32_t draw_resolution_scale_x,
|
|
uint32_t draw_resolution_scale_y,
|
|
bool fixed_rg16_truncated_to_minus_1_to_1,
|
|
bool fixed_rgba16_truncated_to_minus_1_to_1,
|
|
ResolveInfo& info_out);
|
|
|
|
} // namespace draw_util
|
|
} // namespace gpu
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_GPU_DRAW_UTIL_H_
|