[GPU] Common resolve code based on compute shaders, swap MSAA samples 1 and 2, change ROV write rounding, random refactoring

This commit is contained in:
Triang3l
2020-08-09 21:33:10 +03:00
parent 72e41e2bf8
commit bd5f77bc3b
378 changed files with 72288 additions and 37207 deletions

View File

@@ -10,8 +10,11 @@
#ifndef XENIA_GPU_XENOS_H_
#define XENIA_GPU_XENOS_H_
#include <algorithm>
#include "xenia/base/assert.h"
#include "xenia/base/byte_order.h"
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
namespace xe {
@@ -196,16 +199,64 @@ enum class SurfaceNumFormat : uint32_t {
kFloat = 7,
};
// The EDRAM is an opaque block of memory accessible by the RB pipeline stage of
// the GPU, which performs output-merger functionality (color render target
// writing and blending, depth and stencil testing) and resolve (copy)
// operations.
//
// Data in the 10 MiB of EDRAM is laid out as 2048 tiles on 80x16 32bpp MSAA
// samples. With 2x MSAA, one pixel consists of 1x2 samples, and with 4x, it
// consists of 2x2 samples. Thus, for a 32bpp render target, one tile contains
// 80x16 pixels without MSAA, samples of 80x8 pixels with 2x MSAA, or samples of
// 40x8 pixels with 4x MSAA. The base is specified in tiles, the pitch is also
// treated as tiles (so a 256x single-sampled surface will be stored in the
// EDRAM as 320x).
//
// XGSurfaceSize code in game executables calculates the size in tiles in the
// following order:
// 1) If MSAA is >=2x, multiply the height by 2.
// 2) If MSAA is 4x, multiply the width by 2.
// 3) 80x16-align width and height in samples.
// 4) Multiply width*height in samples by 4 or 8 depending on the pixel format.
// 5) Divide the byte size by 5120.
// This means that when working with layout of surfaces in the EDRAM, it should
// be assumed that a multisampled surface is the same as a single-sampled
// surface with 2x height and (with 4x MSAA) width - however, format size
// doesn't effect the dimensions, 64bpp surfaces take twice as many tiles as
// 32bpp surfaces.
//
// From this, it follows that the tile row pitch in tiles can be multiplied by
// 64bpp too. In the formula for calculating the tile count:
// (height rounded up to 16) * (width rounded up to 80) * (4 or 8) / 5120
// the fraction can be reduced because the numerator is always divisible by
// 5120 - it changes in 80 * 16 * 4 = 5120 increments - in tile increments -
// resulting in:
// (height in tiles) * (width in tiles) * (1 or 2)
// Here we get only multiplication, which (disregarding the variable size) is
// associative for integers, so:
// ((height in tiles) * (width in tiles)) * (1 or 2)
// is identical to:
// (height in tiles) * ((width in tiles) * (1 or 2))
//
// Depth surfaces are also stored as 32bpp tiles, however, as opposed to color
// surfaces, 40x16-sample halves of each tile are swapped - game shaders (for
// example, in GTA IV, Halo 3) perform this swapping when writing specific
// depth/stencil values by drawing to a depth buffer's memory through a color
// render target (to reupload a depth/stencil surface previously evicted from
// the EDRAM to the main memory, for instance).
enum class MsaaSamples : uint32_t {
k1X = 0,
k2X = 1,
k4X = 2,
};
constexpr uint32_t kMsaaSamplesBits = 2;
constexpr uint32_t kMaxColorRenderTargets = 4;
enum class ColorRenderTargetFormat : uint32_t {
// D3DFMT_A8R8G8B8 (or ABGR?).
k_8_8_8_8 = 0,
// D3DFMT_A8R8G8B8 with gamma correction.
k_8_8_8_8_GAMMA = 1,
k_2_10_10_10 = 2,
// 7e3 [0, 32) RGB, unorm alpha.
@@ -219,17 +270,90 @@ enum class ColorRenderTargetFormat : uint32_t {
k_16_16_FLOAT = 6,
k_16_16_16_16_FLOAT = 7,
k_2_10_10_10_AS_10_10_10_10 = 10,
// 16-bit fixed point at half speed, with full blending.
// http://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf
k_2_10_10_10_FLOAT_AS_16_16_16_16 = 12,
k_32_FLOAT = 14,
k_32_32_FLOAT = 15,
};
const char* GetColorRenderTargetFormatName(ColorRenderTargetFormat format);
constexpr bool IsColorRenderTargetFormat64bpp(ColorRenderTargetFormat format) {
return format == ColorRenderTargetFormat::k_16_16_16_16 ||
format == ColorRenderTargetFormat::k_16_16_16_16_FLOAT ||
format == ColorRenderTargetFormat::k_32_32_FLOAT;
}
enum class DepthRenderTargetFormat : uint32_t {
kD24S8 = 0,
// 20e4 [0, 2).
kD24FS8 = 1,
};
const char* GetDepthRenderTargetFormatName(DepthRenderTargetFormat format);
// Converts Xenos floating-point depth in bits 0:23 (not clamping) to an
// IEEE-754 32-bit floating-point number.
float Float20e4To32(uint32_t f24);
constexpr uint32_t kColorRenderTargetFormatBits = 4;
constexpr uint32_t kDepthRenderTargetFormatBits = 1;
constexpr uint32_t kRenderTargetFormatBits =
std::max(kColorRenderTargetFormatBits, kDepthRenderTargetFormatBits);
constexpr uint32_t kEdramTileWidthSamples = 80;
constexpr uint32_t kEdramTileHeightSamples = 16;
constexpr uint32_t kEdramTileCount = 2048;
constexpr uint32_t kEdramSizeBytes = kEdramTileCount * kEdramTileHeightSamples *
kEdramTileWidthSamples * sizeof(uint32_t);
// RB_SURFACE_INFO::surface_pitch width.
constexpr uint32_t kEdramPitchPixelsBits = 14;
// RB_COLOR_INFO::color_base/RB_DEPTH_INFO::depth_base width (though for the
// Xbox 360 only 11 make sense, but to avoid bounds checks).
constexpr uint32_t kEdramBaseTilesBits = 12;
inline uint32_t GetSurfacePitchTiles(uint32_t pitch_pixels,
MsaaSamples msaa_samples, bool is_64bpp) {
uint32_t pitch_samples = pitch_pixels
<< uint32_t(msaa_samples >= MsaaSamples::k4X);
uint32_t pitch_tiles =
(pitch_samples + (kEdramTileWidthSamples - 1)) / kEdramTileWidthSamples;
if (is_64bpp) {
pitch_tiles <<= 1;
}
return pitch_tiles;
}
// log2_ceil of the maximum value of GetSurfacePitchTiles, assuming 16383 being
// the maximum pitch in pixels (not sure about the validity of values above
// 8192, but to avoid bounds checking).
// log2_ceil of 16383, multiplied by 2 for 4x MSAA, rounded to 80 samples,
// multiplied by 2 for 64bpp.
constexpr uint32_t kEdramPitchTilesBits = 10;
// Returns the maximum height of a render target, in pixels, that may fit in the
// EDRAM starting from the specified base address, until the end of the EDRAM or
// the specified tile (for instance, until the next render target).
inline uint32_t GetMaxRenderTargetHeight(
uint32_t base_tiles, uint32_t pitch_pixels, MsaaSamples msaa_samples,
bool is_64bpp, uint32_t until_tile = kEdramTileCount) {
if (base_tiles >= until_tile) {
return 0;
}
uint32_t pitch_tiles =
GetSurfacePitchTiles(pitch_pixels, msaa_samples, is_64bpp);
if (!pitch_tiles) {
return 0;
}
return ((until_tile - base_tiles) / pitch_tiles) *
(kEdramTileHeightSamples >>
uint32_t(msaa_samples >= MsaaSamples::k2X));
}
constexpr uint32_t kFormatBits = 6;
// a2xx_sq_surfaceformat +
// https://github.com/indirivacua/RAGE-Console-Texture-Editor/blob/master/Console.Xbox360.Graphics.pas
enum class TextureFormat : uint32_t {
@@ -331,6 +455,36 @@ enum class ColorFormat : uint32_t {
k_11_11_10_AS_16_16_16_16 = 56,
};
// Resolve writes unsigned data for fixed-point formats (so k_16_16 and
// k_16_16_16_16 render target formats, which are signed and also have a
// different range, are not equivalent to the respective texture formats).
constexpr bool IsColorResolveFormatBitwiseEquivalent(
ColorRenderTargetFormat render_target_format, ColorFormat color_format) {
switch (render_target_format) {
case ColorRenderTargetFormat::k_8_8_8_8:
// Shaders fetch data copied from k_8_8_8_8_GAMMA with TextureSign::kGamma.
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
// TODO(Triang3l): Investigate k_8_8_8_8_A.
return color_format == ColorFormat::k_8_8_8_8 ||
color_format == ColorFormat::k_8_8_8_8_A ||
color_format == ColorFormat::k_8_8_8_8_AS_16_16_16_16;
case ColorRenderTargetFormat::k_2_10_10_10:
case ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
return color_format == ColorFormat::k_2_10_10_10 ||
color_format == ColorFormat::k_2_10_10_10_AS_16_16_16_16;
case ColorRenderTargetFormat::k_16_16_FLOAT:
return color_format == ColorFormat::k_16_16_FLOAT;
case ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
return color_format == ColorFormat::k_16_16_16_16_FLOAT;
case ColorRenderTargetFormat::k_32_FLOAT:
return color_format == ColorFormat::k_32_FLOAT;
case ColorRenderTargetFormat::k_32_32_FLOAT:
return color_format == ColorFormat::k_32_32_FLOAT;
default:
return false;
}
}
enum class VertexFormat : uint32_t {
kUndefined = 0,
k_8_8_8_8 = 6,
@@ -611,6 +765,87 @@ enum class ModeControl : uint32_t {
kCopy = 6,
};
// Xenos copies EDRAM contents to a tiled 2D or 3D texture (resolves - from
// "MSAA resolve", but this name is also used for single-sampled copying) by
// drawing primitives with the EDRAM mode ModeControl::kCopy. Pixels covered by
// the drawn geometry are copied. It's likely that only rectangular regions can
// be resolved.
//
// Resolve operation can write color data in ColorFormat formats, with or
// without MSAA color sample averaging, endian swap, red/blue swap, and exponent
// bias. Depth resolving likely has a lot more restrictions, considering sample
// averaging, red/blue swap and exponent bias would be pretty meaningless for it
// (also, Direct3D 9 specifies k_8_8_8_8 as RB_COPY_DEST_INFO::copy_dest_format
// for depth, which is clearly not true - the right format would be k_24_8 or
// k_24_8_FLOAT, so depth resolving likely doesn't support format conversion),
// though endian swap is supported.
//
// In addition, a resolve draw may clear the region it copies (this feature is
// commonly used when going to the next tile with predicated tiling). While one
// resolve draw call may copy just one color or depth buffer, it may clear both
// color and depth at once (or just color or depth, or nothing) if copying a
// color buffer (the color render target cleared is the same as the one copied -
// however, depth resolves have RB_COPY_CONTROL::copy_src_select 4, so they
// can't clear color).
//
// Direct3D 9 does resolving by drawing kRectangleList with 3 vertices with a
// vertex shader that accepts k_32_32_FLOAT vertices with k8in32 endianness in
// SHADER_CONSTANT_FETCH_00_0, with the half-pixel offset, according to the
// PA_SU_VTX_CNTL::pix_center setting, pre-applied to the vertices (for Direct3D
// 9 pixel centers, 0.5 must be added to the vertex positions to get the
// coordinates of the corners).
//
// The rectangle is used for both the source render target and the destination
// texture, according to how it's used in Tales of Vesperia.
//
// Direct3D 9 gives the rectangle in source render target coordinates (for
// example, in Halo 3, the sniper rifle scope has a (128,64)->(448,256)
// rectangle). It doesn't adjust the EDRAM base pointer, otherwise (taking into
// account that 4x MSAA is used for the scope) it would have been
// (8,0)->(328,192), but it's not. However, it adjusts the destination texture
// address so (0,0) relative to the destination address is (0,0) relative to
// the render target (if resolving a part of a render target to the top-left
// corner of a texture, Direct3D 9 actually moves the destination pointer before
// the start of the texture, with tiled offset internally calculated for a
// negative offset). When copying, the pointer needs to be adjusted to the first
// 32x32 tile that will actually be modified, by adding the value of
// XGAddress2D/3DTiledOffset called for left/top & ~31.
//
// RB_COPY_DEST_PITCH's purpose appears to be not clamping or something like
// that, but just specifying pitch for going between rows, and height for going
// between 3D texture slices. copy_dest_pitch is rounded to 32 by Direct3D 9,
// copy_dest_height is not. In the Halo 3 sniper rifle scope example,
// copy_dest_pitch is 320, and copy_dest_height is 192 - the same as the resolve
// rectangle size (resolving from a 320x192 portion of the surface at 128,64 to
// the whole texture, at 0,0). Relative to RB_COPY_DEST_BASE, the height should
// have been 256, but it's not. Adreno doesn't have copy_dest_height at all (as
// well as RB_COPY_DEST_INFO::copy_dest_slice), suggesting (alongside the name
// of the register) that it exists purely to be able to go between 3D texture
// slices.
//
// Window scissor must also be applied - in the jigsaw puzzle in Banjo-Tooie,
// there are 1280x720 resolve rectangles, but only the scissored 1280x256
// needs to be copied, otherwise it overflows even beyond the EDRAM, and the
// depth buffer is visible on the screen. It also ensures the coordinates are
// not negative (in F.E.A.R., for example, the right tile is resolved with
// vertices (-640,0)->(640,720), however, the destination texture pointer is
// adjusted properly to the right half of the texture, and the source render
// target has a pitch of 800).
// Granularity of offset and size in resolve operations is 8x8 pixels
// (GPU_RESOLVE_ALIGNMENT - for example, Halo 3 resolves a 24x16 region for a
// 18x10 texture, 8x8 region for a 1x1 texture).
// https://github.com/jmfauvel/CSGO-SDK/blob/master/game/client/view.cpp#L944
// https://github.com/stanriders/hl2-asw-port/blob/master/src/game/client/vgui_int.cpp#L901
constexpr uint32_t kResolveAlignmentPixelsLog2 = 3;
constexpr uint32_t kResolveAlignmentPixels = 1 << kResolveAlignmentPixelsLog2;
// Same as RB_SURFACE_INFO::surface_pitch, RB_COPY_DEST_PITCH::copy_dest_pitch
// and RB_COPY_DEST_PITCH::copy_dest_height.
constexpr uint32_t kResolveSizeBits = 14;
constexpr uint32_t kMaxResolveSize =
(1 << kResolveSizeBits) - kResolveAlignmentPixels;
enum class CopyCommand : uint32_t {
kRaw = 0,
kConvert = 1,
@@ -629,6 +864,11 @@ enum class CopySampleSelect : uint32_t {
k0123,
};
constexpr bool IsSingleCopySampleSelected(CopySampleSelect copy_sample_select) {
return copy_sample_select >= CopySampleSelect::k0 &&
copy_sample_select <= CopySampleSelect::k3;
}
#define XE_GPU_MAKE_SWIZZLE(x, y, z, w) \
(((XE_GPU_SWIZZLE_##x) << 0) | ((XE_GPU_SWIZZLE_##y) << 3) | \
((XE_GPU_SWIZZLE_##z) << 6) | ((XE_GPU_SWIZZLE_##w) << 9))
@@ -724,20 +964,47 @@ XEPACKEDUNION(xe_gpu_vertex_fetch_t, {
});
});
// Byte alignment of texture subresources in memory - of each mip and stack
// slice / cube face (and of textures themselves), this number of bits is also
// omitted from base_address and mip_address.
constexpr uint32_t kTextureSubresourceAlignmentBytesLog2 = 12;
constexpr uint32_t kTextureSubresourceAlignmentBytes =
1 << kTextureSubresourceAlignmentBytesLog2;
// Texture fetch constant size field widths.
constexpr uint32_t kTexture1DMaxWidthLog2 = 24;
constexpr uint32_t kTexture1DMaxWidth = uint32_t(1) << kTexture1DMaxWidthLog2;
constexpr uint32_t kTexture1DMaxWidth = 1 << kTexture1DMaxWidthLog2;
constexpr uint32_t kTexture2DCubeMaxWidthHeightLog2 = 13;
constexpr uint32_t kTexture2DCubeMaxWidthHeight =
uint32_t(1) << kTexture2DCubeMaxWidthHeightLog2;
1 << kTexture2DCubeMaxWidthHeightLog2;
constexpr uint32_t kTexture2DMaxStackDepthLog2 = 6;
constexpr uint32_t kTexture2DMaxStackDepth = uint32_t(1)
<< kTexture2DMaxStackDepthLog2;
constexpr uint32_t kTexture2DMaxStackDepth = 1 << kTexture2DMaxStackDepthLog2;
constexpr uint32_t kTexture3DMaxWidthHeightLog2 = 11;
constexpr uint32_t kTexture3DMaxWidthHeight = uint32_t(1)
<< kTexture3DMaxWidthHeightLog2;
constexpr uint32_t kTexture3DMaxWidthHeight = 1 << kTexture3DMaxWidthHeightLog2;
constexpr uint32_t kTexture3DMaxDepthLog2 = 10;
constexpr uint32_t kTexture3DMaxDepth = uint32_t(1) << kTexture3DMaxDepthLog2;
constexpr uint32_t kTexture3DMaxDepth = 1 << kTexture3DMaxDepthLog2;
// Tiled texture sizes are in 32x32 increments for 2D, 32x32x4 for 3D.
// 2DTiledOffset(X * 32 + x, Y * 32 + y) ==
// 2DTiledOffset(X * 32, Y * 32) + 2DTiledOffset(x, y)
// 3DTiledOffset(X * 32 + x, Y * 32 + y, Z * 8 + z) ==
// 3DTiledOffset(X * 32, Y * 32, Z * 8) + 3DTiledOffset(x, y, z)
// Both are true for negative offsets too.
constexpr uint32_t kTextureTileWidthHeightLog2 = 5;
constexpr uint32_t kTextureTileWidthHeight = 1 << kTextureTileWidthHeightLog2;
// 3D tiled texture slices 0:3 and 4:7 are stored separately in memory, in
// non-overlapping ranges, but addressing in 4:7 is different than in 0:3.
constexpr uint32_t kTextureTiledDepthGranularityLog2 = 2;
constexpr uint32_t kTextureTiledDepthGranularity =
1 << kTextureTiledDepthGranularityLog2;
constexpr uint32_t kTextureTiledZBaseGranularityLog2 = 3;
constexpr uint32_t kTextureTiledZBaseGranularity =
1 << kTextureTiledZBaseGranularityLog2;
// Row pitch alignment of non-tiled textures.
constexpr uint32_t kTextureLinearRowAlignmentBytesLog2 = 8;
constexpr uint32_t kTextureLinearRowAlignmentBytes =
1 << kTextureLinearRowAlignmentBytesLog2;
// XE_GPU_REG_SHADER_CONSTANT_FETCH_*
XEPACKEDUNION(xe_gpu_texture_fetch_t, {