[GPU] Viewport/clipping cleanup, don't clamp oDepth

This commit is contained in:
Triang3l
2021-05-04 21:39:34 +03:00
parent 95031d9471
commit fb01ccaac6
7 changed files with 428 additions and 260 deletions

View File

@@ -184,167 +184,353 @@ bool IsPixelShaderNeededWithRasterization(const Shader& shader,
}
void GetHostViewportInfo(const RegisterFile& regs, uint32_t resolution_scale,
bool origin_bottom_left, float x_max, float y_max,
bool allow_reverse_z, bool convert_z_to_float24,
bool full_float24_in_0_to_1,
bool origin_bottom_left, uint32_t x_max,
uint32_t y_max, bool allow_reverse_z,
bool convert_z_to_float24, bool full_float24_in_0_to_1,
bool pixel_shader_writes_depth,
ViewportInfo& viewport_info_out) {
assert_true(resolution_scale >= 1);
assert_true(x_max >= 1.0f);
assert_true(y_max >= 1.0f);
assert_not_zero(resolution_scale);
// PA_CL_VTE_CNTL contains whether offsets and scales are enabled.
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
// In games, either all are enabled (for regular drawing) or none are (for
// rectangle lists usually).
// A vertex position goes the following path:
//
// If scale/offset is enabled, the Xenos shader is writing (neglecting W
// division) position in the NDC (-1, -1, dx_clip_space_def - 1) -> (1, 1, 1)
// box. If it's not, the position is in screen space. Since we can only use
// the NDC in PC APIs, we use a viewport of the largest possible size, and
// divide the position by it in translated shaders.
// = Vertex shader output in clip space, (-w, -w, 0) ... (w, w, w) for
// Direct3D or (-w, -w, -w) ... (w, w, w) for OpenGL.
// > Clipping to the boundaries of the clip space if enabled.
// > Division by W if not pre-divided.
// = Normalized device coordinates, (-1, -1, 0) ... (1, 1, 1) for Direct3D or
// (-1, -1, -1) ... (1, 1, 1) for OpenGL.
// > Viewport scaling.
// > Viewport, window and half-pixel offsetting.
// = Actual position in render target pixels used for rasterization and depth
// buffer coordinates.
//
// On modern PC graphics APIs, all drawing is done with clipping enabled (only
// Z clipping can be replaced with viewport depth range clamping).
//
// On the Xbox 360, however, there are two cases:
//
// - Clipping is enabled:
//
// Drawing "as normal", primarily for the game world. Draws are clipped to
// the (-w, -w, 0) ... (w, w, w) or (-w, -w, -w) ... (w, w, w) clip space.
//
// Ideally all offsets in pixels (window offset, half-pixel offset) are
// post-clip, and thus they would need to be applied via the host viewport
// (also the Direct3D 11.3 specification defines this as the correct way of
// reproducing the original Direct3D 9 half-pixel offset behavior).
//
// However, in reality, only WARP actually truly clips to -W...W, with the
// viewport fractional offset actually accurately making samples outside the
// fractional rectangle unable to be covered. AMD, Intel and Nvidia, in
// Direct3D 12, all don't truly clip even a really huge primitive to -W...W.
// Instead, primitives still overflow the fractional rectangle and cover
// samples outside of it. The actual viewport scissor is floor(TopLeftX,
// TopLeftY) ... floor(TopLeftX + Width, TopLeftY + Height), with flooring
// and addition in float32 (with 0x3F7FFFFF TopLeftXY, or 1.0f - ULP, all
// the samples in the top row / left column can be covered, while with
// 0x3F800000, or 1.0f, none of them can be).
//
// We are reproducing the same behavior here - what would happen if we'd be
// passing the guest values directly to Direct3D 12. Also, for consistency
// across hardware and APIs (especially Vulkan with viewportSubPixelBits
// being 0 rather than at least 8 on some devices - Arm Mali, Imagination
// PowerVR), and for simplicity of math, and also for exact calculations in
// bounds checking in validation layers of the host APIs, we are returning
// integer viewport coordinates, handling the fractional offset in the
// vertex shaders instead, via ndc_scale and ndc_offset - it shouldn't
// significantly affect precision that we will be doing the offsetting in
// W-scaled rather than W-divided units, the ratios of exponents involved in
// the calculations stay the same, and everything ends up being 16.8 anyway
// on most hardware, so small precision differences are very unlikely to
// affect coverage.
//
// FIXME(Triang3l): Overestimate or more properly round the viewport scissor
// boundaries if this flooring causes gaps on the bottom / right side in real
// games if any are found using fractional viewport coordinates. Viewport
// scissoring is not an inherent result of the viewport scale / offset, these
// are used merely for transformation of coordinates; rather, it's done by
// intersecting the viewport and scissor rectangles in the guest driver and
// writing the common portion to PA_SC_WINDOW_SCISSOR, so how the scissor is
// computed for a fractional viewport is entirely up to the guest.
//
// Even though Xbox 360 games are designed for Direct3D, with 0...W range of
// Z in clip space, the GPU also allows -W...W. Since Xenia is not targeting
// OpenGL (where it would be toggled via glClipControl - or, on ES, it would
// always be -W...W), this function always remaps it to 0...W, though
// numerically not precisely (0 is moved to 0.5, locking the exponent near
// what was the truly floating-point 0 originally). It is the guest
// viewport's responsibility (haven't checked, but it's logical) to remap
// from -1...1 in the NDC to glDepthRange within the 0...1 range. Also -Z
// pointing forward in OpenGL doesn't matter here (the -W...W clip space is
// symmetric).
//
// - Clipping is disabled:
//
// The most common case of drawing without clipping in games is screen-space
// draws, most prominently clears, directly in render target coordinates.
//
// In this particular case (though all the general case arithmetic still
// applies), the vertex shader returns a position in pixels, pre-divided by
// W (though this doesn't matter if W is 1).
//
// Because clipping is disabled, this huge polygon with, for example,
// a (1280, 720, 0, 1) vertex, is not clipped to (-w, -w) ... (w, w), so the
// vertex becomes (1280, 720) in the NDC as well (even though in regular 3D
// draws with clipping, disregarding the guard band for simplicity, it can't
// be bigger than (1, 1) after clipping and the division by W).
//
// For these draws, the viewport is also usually disabled (though, again, it
// doesn't have to be - an enabled viewport would likely still work as
// usual) by disabling PA_CL_VTE_CNTL::VPORT_X/Y/Z_SCALE/OFFSET_ENA - which
// equals to having a viewport scale of (1, 1, 1) and offset of (0, 0, 0).
// This results in the NDC being treated directly as pixel coordinates.
// Normally, with clipping, this would make only a tiny 1x1 area in the
// corner of the render target being possible to cover (and 3 unreachable
// pixels outside of the render target). The window offset is then applied,
// if needed, as well as the half-pixel offset.
//
// It's also possible (though not verified) that without clipping, Z (as a
// result of, for instance, polygon offset, or explicit calculations in the
// vertex shader) may end up outside the viewport Z range. Direct3D 10
// requires clamping to the viewport Z bounds in all cases in the
// output-merger according to the Direct3D 11.3 functional specification. A
// different behavior is likely on the Xbox 360, however, because while
// Direct3D 10-compatible AMD GPUs such as the R600 have
// PA_SC_VPORT_ZMIN/ZMAX registers, the Adreno 200 doesn't seem to have any
// equivalents, neither in PA nor in RB. This probably also applies to
// shader depth output - possibly doesn't need to be clamped as well.
//
// On the PC, we need to emulate disabled clipping by using a viewport at
// least as large as the scissor region within the render target, as well as
// the full viewport depth range (plus changing Z clipping to Z clamping on
// the host if possible), and rescale from the guest clip space to the host
// "no clip" clip space, as well as apply the viewport, the window offset,
// and the half-pixel offset, in the vertex shader. Ideally, the host
// viewport should have a power of 2 size - so scaling doesn't affect
// precision, and is merely an exponent bias.
//
// NDC XY point towards +XY on the render target - the viewport scale sign
// handles the remapping from Direct3D 9 -Y towards +U to a generic
// transformation from the NDC to pixel coordinates.
//
// TODO(Triang3l): Investigate the need for clamping of oDepth to 0...1 for
// D24FS8 as well.
auto pa_cl_clip_cntl = regs.Get<reg::PA_CL_CLIP_CNTL>();
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>();
auto pa_su_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
float viewport_left, viewport_top;
float viewport_width, viewport_height;
float ndc_scale_x, ndc_scale_y;
float ndc_offset_x, ndc_offset_y;
// To avoid zero size viewports, which would harm division and aren't allowed
// on Vulkan. Nothing will ever be covered by a viewport of this size - this
// is 2 orders of magnitude smaller than a .8 subpixel, and thus shouldn't
// have any effect on rounding, n and n + 1 / 1024 would be rounded to the
// same .8 fixed-point value, thus in fixed-point, the viewport would have
// zero size.
const float size_min = 1.0f / 1024.0f;
float viewport_offset_x = pa_cl_vte_cntl.vport_x_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32
: 0.0f;
float viewport_offset_y = pa_cl_vte_cntl.vport_y_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32
: 0.0f;
// Obtain the original viewport values in a normalized way.
float scale_xy[] = {
pa_cl_vte_cntl.vport_x_scale_ena ? regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32
: 1.0f,
pa_cl_vte_cntl.vport_y_scale_ena ? regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32
: 1.0f,
};
float scale_z = pa_cl_vte_cntl.vport_z_scale_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
: 1.0f;
float offset_base_xy[] = {
pa_cl_vte_cntl.vport_x_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32
: 0.0f,
pa_cl_vte_cntl.vport_y_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32
: 0.0f,
};
float offset_z = pa_cl_vte_cntl.vport_z_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
: 0.0f;
// Calculate all the integer.0 or integer.5 offsetting exactly at full
// precision, separately so it can be used in other integer calculations
// without double rounding if needed.
float offset_add_xy[2] = {};
if (pa_su_sc_mode_cntl.vtx_window_offset_enable) {
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
viewport_offset_x += float(pa_sc_window_offset.window_x_offset);
viewport_offset_y += float(pa_sc_window_offset.window_y_offset);
offset_add_xy[0] += float(pa_sc_window_offset.window_x_offset);
offset_add_xy[1] += float(pa_sc_window_offset.window_y_offset);
}
if (pa_cl_vte_cntl.vport_x_scale_ena) {
float pa_cl_vport_xscale = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32;
float viewport_scale_x_abs =
std::abs(pa_cl_vport_xscale) * resolution_scale;
viewport_left = viewport_offset_x * resolution_scale - viewport_scale_x_abs;
float viewport_right = viewport_left + viewport_scale_x_abs * 2.0f;
// Keep the viewport in the positive quarter-plane for simplicity of
// clamping to the maximum supported bounds.
float cutoff_left = std::fmax(-viewport_left, 0.0f);
float cutoff_right = std::fmax(viewport_right - x_max, 0.0f);
viewport_left = std::fmax(viewport_left, 0.0f);
viewport_right = std::fmin(viewport_right, x_max);
viewport_width = viewport_right - viewport_left;
if (viewport_width > size_min) {
ndc_scale_x =
(viewport_width + cutoff_left + cutoff_right) / viewport_width;
if (pa_cl_vport_xscale < 0.0f) {
ndc_scale_x = -ndc_scale_x;
}
ndc_offset_x =
((cutoff_right - cutoff_left) * (0.5f * 2.0f)) / viewport_width;
} else {
// Empty viewport, but don't pass 0 because that's against the Vulkan
// specification.
viewport_left = 0.0f;
viewport_width = size_min;
ndc_scale_x = 0.0f;
ndc_offset_x = 0.0f;
}
} else {
// Drawing without a viewport and without clipping to one - use a viewport
// covering the entire potential guest render target or the positive part of
// the host viewport area, whichever is smaller, and apply the offset, if
// enabled, via the shader.
viewport_left = 0.0f;
viewport_width = std::min(
float(xenos::kTexture2DCubeMaxWidthHeight) * resolution_scale, x_max);
ndc_scale_x = (2.0f * resolution_scale) / viewport_width;
ndc_offset_x = viewport_offset_x * ndc_scale_x - 1.0f;
}
if (pa_cl_vte_cntl.vport_y_scale_ena) {
float pa_cl_vport_yscale = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32;
float viewport_scale_y_abs =
std::abs(pa_cl_vport_yscale) * resolution_scale;
viewport_top = viewport_offset_y * resolution_scale - viewport_scale_y_abs;
float viewport_bottom = viewport_top + viewport_scale_y_abs * 2.0f;
float cutoff_top = std::fmax(-viewport_top, 0.0f);
float cutoff_bottom = std::fmax(viewport_bottom - y_max, 0.0f);
viewport_top = std::fmax(viewport_top, 0.0f);
viewport_bottom = std::fmin(viewport_bottom, y_max);
viewport_height = viewport_bottom - viewport_top;
if (viewport_height > size_min) {
ndc_scale_y =
(viewport_height + cutoff_top + cutoff_bottom) / viewport_height;
if (pa_cl_vport_yscale < 0.0f) {
ndc_scale_y = -ndc_scale_y;
}
ndc_offset_y =
((cutoff_bottom - cutoff_top) * (0.5f * 2.0f)) / viewport_height;
} else {
// Empty viewport, but don't pass 0 because that's against the Vulkan
// specification.
viewport_top = 0.0f;
viewport_height = size_min;
ndc_scale_y = 0.0f;
ndc_offset_y = 0.0f;
}
} else {
viewport_top = 0.0f;
viewport_height = std::min(
float(xenos::kTexture2DCubeMaxWidthHeight) * resolution_scale, y_max);
ndc_scale_y = (2.0f * resolution_scale) / viewport_height;
ndc_offset_y = viewport_offset_y * ndc_scale_y - 1.0f;
}
// Apply the vertex half-pixel offset via the shader (it must not affect
// clipping, otherwise with resolution scale, samples in the left/top half
// will never be covered).
if (cvars::half_pixel_offset && !pa_su_vtx_cntl.pix_center) {
float half_pixel_offset_ndc_scale = 0.5f * 2.0f * resolution_scale;
ndc_offset_x += half_pixel_offset_ndc_scale / viewport_width;
ndc_offset_y += half_pixel_offset_ndc_scale / viewport_height;
offset_add_xy[0] += 0.5f;
offset_add_xy[1] += 0.5f;
}
if (origin_bottom_left) {
ndc_scale_y = -ndc_scale_y;
ndc_offset_y = -ndc_offset_y;
// The maximum value is at least the maximum host render target size anyway -
// and a guest pixel is always treated as a whole with resolution scaling.
uint32_t xy_max_unscaled[] = {x_max / resolution_scale,
y_max / resolution_scale};
assert_not_zero(xy_max_unscaled[0]);
assert_not_zero(xy_max_unscaled[1]);
float z_min;
float z_max;
float ndc_scale[3];
float ndc_offset[3];
if (pa_cl_clip_cntl.clip_disable) {
// Clipping is disabled - use a huge host viewport, perform pixel and depth
// offsetting in the vertex shader.
// XY.
for (uint32_t i = 0; i < 2; ++i) {
viewport_info_out.xy_offset[i] = 0;
uint32_t extent_axis_unscaled =
std::min(xenos::kTexture2DCubeMaxWidthHeight, xy_max_unscaled[i]);
viewport_info_out.xy_extent[i] = extent_axis_unscaled * resolution_scale;
float extent_axis_unscaled_float = float(extent_axis_unscaled);
float pixels_to_ndc_axis = 2.0f / extent_axis_unscaled_float;
ndc_scale[i] = scale_xy[i] * pixels_to_ndc_axis;
ndc_offset[i] = (offset_base_xy[i] - extent_axis_unscaled_float * 0.5f +
offset_add_xy[i]) *
pixels_to_ndc_axis;
}
// Z.
z_min = 0.0f;
z_max = 1.0f;
ndc_scale[2] = scale_z;
ndc_offset[2] = offset_z;
} else {
// Clipping is enabled - perform pixel and depth offsetting via the host
// viewport.
// XY.
for (uint32_t i = 0; i < 2; ++i) {
// With resolution scaling, do all viewport XY scissoring in guest pixels
// if fractional and for the half-pixel offset - we treat guest pixels as
// a whole, and also the half-pixel offset would be irreversible in guest
// vertices if we did flooring in host pixels. Instead of flooring, also
// doing truncation for simplicity - since maxing with 0 is done anyway
// (we only return viewports in the positive quarter-plane).
float offset_axis = offset_base_xy[i] + offset_add_xy[i];
float scale_axis = scale_xy[i];
float scale_axis_abs = std::abs(scale_xy[i]);
float axis_0 = offset_axis - scale_axis_abs;
float axis_1 = offset_axis + scale_axis_abs;
float axis_max_unscaled_float = float(xy_max_unscaled[i]);
// fmax to drop NaN and < 0, min as float (axis_max_unscaled_float is well
// below 2^24) to safely drop very large values.
uint32_t axis_0_int =
uint32_t(std::min(std::fmax(axis_0, 0.0f), axis_max_unscaled_float));
uint32_t axis_1_int =
uint32_t(std::min(std::fmax(axis_1, 0.0f), axis_max_unscaled_float));
uint32_t axis_extent_int = axis_1_int - axis_0_int;
viewport_info_out.xy_offset[i] = axis_0_int * resolution_scale;
viewport_info_out.xy_extent[i] = axis_extent_int * resolution_scale;
float ndc_scale_axis;
float ndc_offset_axis;
if (axis_extent_int) {
// Rescale from the old bounds to the new ones, and also apply the sign.
// If the new bounds are smaller than the old, for instance, we're
// cropping - the new -W...W clip space is a subregion of the old one -
// the scale should be > 1 so the area being cut off ends up outside
// -W...W. If the new region should include more than the original clip
// space, a region previously outside -W...W should end up within it, so
// the scale should be < 1.
float axis_extent_rounded = float(axis_extent_int);
ndc_scale_axis = scale_axis * 2.0f / axis_extent_rounded;
// Move the origin of the snapped coordinates back to the original one.
ndc_offset_axis = (float(offset_axis) -
(float(axis_0_int) + axis_extent_rounded * 0.5f)) *
2.0f / axis_extent_rounded;
} else {
// Empty viewport (everything outside the viewport scissor).
ndc_scale_axis = 1.0f;
ndc_offset_axis = 0.0f;
}
ndc_scale[i] = ndc_scale_axis;
ndc_offset[i] = ndc_offset_axis;
}
// Z.
float host_clip_offset_z;
float host_clip_scale_z;
if (pa_cl_clip_cntl.dx_clip_space_def) {
host_clip_offset_z = offset_z;
host_clip_scale_z = scale_z;
ndc_scale[2] = 1.0f;
ndc_offset[2] = 0.0f;
} else {
// Normalizing both Direct3D / Vulkan 0...W and OpenGL -W...W clip spaces
// to 0...W. We are not targeting OpenGL, but there we could accept the
// wanted clip space (Direct3D, OpenGL, or any) and return the actual one
// (Direct3D or OpenGL).
//
// If the guest wants to use -W...W clip space (-1...1 NDC) and a 0...1
// depth range in the end, it's expected to use ZSCALE of 0.5 and ZOFFSET
// of 0.5.
//
// We are providing the near and the far (or offset and offset + scale)
// plane distances to the host API in a way that the near maps to Z = 0
// and the far maps to Z = W in clip space (or Z = 1 in NDC).
//
// With D3D offset and scale that we want, assuming D3D clip space input,
// the formula for the depth would be:
//
// depth = offset_d3d + scale_d3d * ndc_z_d3d
//
// We are remapping the incoming OpenGL Z from -W...W to 0...W by scaling
// it by 0.5 and adding 0.5 * W to the result. So, our depth formula would
// be:
//
// depth = offset_d3d + scale_d3d * (ndc_z_gl * 0.5 + 0.5)
//
// The guest registers, however, contain the offset and the scale for
// remapping not from 0...W to near...far, but from -W...W to near...far,
// or:
//
// depth = offset_gl + scale_gl * ndc_z_gl
//
// Knowing offset_gl, scale_gl and how ndc_z_d3d can be obtained from
// ndc_z_gl, we need to derive the formulas for the needed offset_d3d and
// scale_d3d to apply them to the incoming ndc_z_d3d.
//
// depth = offset_gl + scale_gl * (ndc_z_d3d * 2 - 1)
//
// Expanding:
//
// depth = offset_gl + (scale_gl * ndc_z_d3d * 2 - scale_gl)
//
// Reordering:
//
// depth = (offset_gl - scale_gl) + (scale_gl * 2) * ndc_z_d3d
// offset_d3d = offset_gl - scale_gl
// scale_d3d = scale_gl * 2
host_clip_offset_z = offset_z - scale_z;
host_clip_scale_z = scale_z * 2.0f;
// Need to remap -W...W clip space to 0...W via ndc_scale and ndc_offset -
// by scaling Z by 0.5 and adding 0.5 * W to it.
ndc_scale[2] = 0.5f;
ndc_offset[2] = 0.5f;
}
if (pixel_shader_writes_depth) {
// Allow the pixel shader to write any depth value since
// PA_SC_VPORT_ZMIN/ZMAX isn't present on the Adreno 200; guest pixel
// shaders don't have access to the original Z in the viewport space
// anyway and likely must write the depth on all execution paths.
z_min = 0.0f;
z_max = 1.0f;
} else {
// This clamping is not very correct, but just for safety. Direct3D
// doesn't allow an unrestricted depth range. Vulkan does, as an
// extension. But cases when this really matters are yet to be found -
// trying to fix this will result in more correct depth values, but
// incorrect clipping.
z_min = std::min(std::fmax(host_clip_offset_z, 0.0f), 1.0f);
z_max = std::min(std::fmax(host_clip_offset_z + host_clip_scale_z, 0.0f),
1.0f);
// Direct3D 12 doesn't allow reverse depth range - on some drivers it
// works, on some drivers it doesn't, actually, but it was never
// explicitly allowed by the specification.
if (!allow_reverse_z && z_min > z_max) {
std::swap(z_min, z_max);
ndc_scale[2] = -ndc_scale[2];
ndc_offset[2] = 1.0f - ndc_offset[2];
}
}
}
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
: 1.0f;
float viewport_offset_z = pa_cl_vte_cntl.vport_z_offset_ena
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
: 0.0f;
// Vulkan requires the depth bounds to be in the 0 to 1 range without
// VK_EXT_depth_range_unrestricted (which isn't used on the Xbox 360).
float viewport_z_min = std::min(std::fmax(viewport_offset_z, 0.0f), 1.0f);
float viewport_z_max =
std::min(std::fmax(viewport_offset_z + viewport_scale_z, 0.0f), 1.0f);
// When VPORT_Z_SCALE_ENA is disabled, Z/W is directly what is expected to be
// written to the depth buffer, and for some reason DX_CLIP_SPACE_DEF isn't
// set in this case in draws in games.
bool gl_clip_space_def =
!pa_cl_clip_cntl.dx_clip_space_def && pa_cl_vte_cntl.vport_z_scale_ena;
float ndc_scale_z = gl_clip_space_def ? 0.5f : 1.0f;
float ndc_offset_z = gl_clip_space_def ? 0.5f : 0.0f;
if (viewport_z_min > viewport_z_max && !allow_reverse_z) {
std::swap(viewport_z_min, viewport_z_max);
ndc_scale_z = -ndc_scale_z;
ndc_offset_z = 1.0f - ndc_offset_z;
}
if (GetDepthControlForCurrentEdramMode(regs).z_enable &&
regs.Get<reg::RB_DEPTH_INFO>().depth_format ==
xenos::DepthRenderTargetFormat::kD24FS8) {
@@ -352,34 +538,30 @@ void GetHostViewportInfo(const RegisterFile& regs, uint32_t resolution_scale,
// Need to adjust the bounds that the resulting depth values will be
// clamped to after the pixel shader. Preferring adding some error to
// interpolated Z instead if conversion can't be done exactly, without
// modifying clipping bounds by adjusting Z in vertex shaders, as that may
// cause polygons placed explicitly at Z = 0 or Z = W to be clipped.
viewport_z_min =
xenos::Float20e4To32(xenos::Float32To20e4(viewport_z_min));
viewport_z_max =
xenos::Float20e4To32(xenos::Float32To20e4(viewport_z_max));
// modifying clipping bounds by adjusting Z in vertex shaders, as that
// may cause polygons placed explicitly at Z = 0 or Z = W to be clipped.
z_min = xenos::Float20e4To32(xenos::Float32To20e4(z_min));
z_max = xenos::Float20e4To32(xenos::Float32To20e4(z_max));
}
if (full_float24_in_0_to_1) {
// Remap the full [0...2) float24 range to [0...1) support data round-trip
// during render target ownership transfer of EDRAM tiles through depth
// input without unrestricted depth range.
viewport_z_min *= 0.5f;
viewport_z_max *= 0.5f;
z_min *= 0.5f;
z_max *= 0.5f;
}
}
viewport_info_out.z_min = z_min;
viewport_info_out.z_max = z_max;
viewport_info_out.left = viewport_left;
viewport_info_out.top = viewport_top;
viewport_info_out.width = viewport_width;
viewport_info_out.height = viewport_height;
viewport_info_out.z_min = viewport_z_min;
viewport_info_out.z_max = viewport_z_max;
viewport_info_out.ndc_scale[0] = ndc_scale_x;
viewport_info_out.ndc_scale[1] = ndc_scale_y;
viewport_info_out.ndc_scale[2] = ndc_scale_z;
viewport_info_out.ndc_offset[0] = ndc_offset_x;
viewport_info_out.ndc_offset[1] = ndc_offset_y;
viewport_info_out.ndc_offset[2] = ndc_offset_z;
if (origin_bottom_left) {
ndc_scale[1] = -ndc_scale[1];
ndc_offset[1] = -ndc_offset[1];
}
for (uint32_t i = 0; i < 3; ++i) {
viewport_info_out.ndc_scale[i] = ndc_scale[i];
viewport_info_out.ndc_offset[i] = ndc_offset[i];
}
}
void GetScissor(const RegisterFile& regs, Scissor& scissor_out) {
@@ -420,10 +602,10 @@ void GetScissor(const RegisterFile& regs, Scissor& scissor_out) {
// console, but no evidence of such has ever been seen).
br_x = std::max(br_x, tl_x);
br_y = std::max(br_y, tl_y);
scissor_out.left = tl_x;
scissor_out.top = tl_y;
scissor_out.width = br_x - tl_x;
scissor_out.height = br_y - tl_y;
scissor_out.offset[0] = tl_x;
scissor_out.offset[1] = tl_y;
scissor_out.extent[0] = br_x - tl_x;
scissor_out.extent[1] = br_y - tl_y;
}
xenos::CopySampleSelect SanitizeCopySampleSelect(