xenia-canary/src/xenia/gpu/xenos.h

1466 lines
55 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#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/memory.h"
#include "xenia/base/platform.h"
namespace xe {
namespace gpu {
namespace xenos {
// enum types used in the GPU registers or the microcode must be : uint32_t or
// : int32_t, as Visual C++ restarts bit field packing when a field requires
// different alignment than the previous one, so only 32-bit types must be used
// in bit fields (registers are 32-bit, and the microcode consists of triples of
// 32-bit words).
constexpr fourcc_t kSwapSignature = make_fourcc("SWAP");
enum class ShaderType : uint32_t {
kVertex = 0,
kPixel = 1,
};
// Only the lower 24 bits of the vertex index are used (tested on an Adreno 200
// phone using a GL_UNSIGNED_INT element array buffer with junk in the upper 8
// bits that had no effect on drawing).
constexpr uint32_t kVertexIndexBits = 24;
constexpr uint32_t kVertexIndexMask = (uint32_t(1) << kVertexIndexBits) - 1;
enum class PrimitiveType : uint32_t {
kNone = 0x00,
kPointList = 0x01,
kLineList = 0x02,
kLineStrip = 0x03,
kTriangleList = 0x04,
kTriangleFan = 0x05,
kTriangleStrip = 0x06,
kTriangleWithWFlags = 0x07,
kRectangleList = 0x08,
kLineLoop = 0x0C,
kQuadList = 0x0D,
kQuadStrip = 0x0E,
kPolygon = 0x0F,
// Starting with this primitive type, explicit major mode is assumed (in the
// R6xx/R7xx registers, k2DCopyRectListV0 is 22, and implicit major mode is
// only used for primitive types 0 through 21) - and tessellation patches use
// the range that starts from k2DCopyRectListV0.
// TODO(Triang3l): Verify if this is also true for the Xenos.
kExplicitMajorModeForceStart = 0x10,
k2DCopyRectListV0 = 0x10,
k2DCopyRectListV1 = 0x11,
k2DCopyRectListV2 = 0x12,
k2DCopyRectListV3 = 0x13,
k2DFillRectList = 0x14,
k2DLineStrip = 0x15,
k2DTriStrip = 0x16,
// Tessellation patches when VGT_OUTPUT_PATH_CNTL::path_select is
// VGTOutputPath::kTessellationEnable. The vertex shader receives patch index
// rather than control point indices.
kLinePatch = 0x10,
kTrianglePatch = 0x11,
kQuadPatch = 0x12,
};
// For the texture fetch constant (not the tfetch instruction), stacked stored
// as 2D.
enum class DataDimension : uint32_t {
k1D = 0,
k2DOrStacked = 1,
k3D = 2,
kCube = 3,
};
enum class ClampMode : uint32_t {
kRepeat = 0,
kMirroredRepeat = 1,
kClampToEdge = 2,
kMirrorClampToEdge = 3,
kClampToHalfway = 4,
kMirrorClampToHalfway = 5,
kClampToBorder = 6,
kMirrorClampToBorder = 7,
};
// TEX_FORMAT_COMP, known as GPUSIGN on the Xbox 360.
enum class TextureSign : uint32_t {
kUnsigned = 0,
// Two's complement texture data.
kSigned = 1,
// 2*color-1 - https://xboxforums.create.msdn.com/forums/t/107374.aspx
kUnsignedBiased = 2,
// Linearized when sampled.
kGamma = 3,
};
enum class TextureFilter : uint32_t {
kPoint = 0,
kLinear = 1,
kBaseMap = 2, // Only applicable for mip-filter - always fetch from level 0.
kUseFetchConst = 3,
};
enum class AnisoFilter : uint32_t {
kDisabled = 0,
kMax_1_1 = 1,
kMax_2_1 = 2,
kMax_4_1 = 3,
kMax_8_1 = 4,
kMax_16_1 = 5,
kUseFetchConst = 7,
};
enum class BorderColor : uint32_t {
k_AGBR_Black = 0,
k_AGBR_White = 1,
k_ACBYCR_BLACK = 2,
k_ACBCRY_BLACK = 3,
};
// For the tfetch instruction (not the fetch constant) and related instructions,
// stacked accessed using tfetch3D.
enum class FetchOpDimension : uint32_t {
k1D = 0,
k2D = 1,
k3DOrStacked = 2,
kCube = 3,
};
inline int GetFetchOpDimensionComponentCount(FetchOpDimension dimension) {
switch (dimension) {
case FetchOpDimension::k1D:
return 1;
case FetchOpDimension::k2D:
return 2;
case FetchOpDimension::k3DOrStacked:
case FetchOpDimension::kCube:
return 3;
default:
assert_unhandled_case(dimension);
return 1;
}
}
enum class SampleLocation : uint32_t {
kCentroid = 0,
kCenter = 1,
};
enum class Endian : uint32_t {
kNone = 0,
k8in16 = 1,
k8in32 = 2,
k16in32 = 3,
};
enum class Endian128 : uint32_t {
kNone = 0,
k8in16 = 1,
k8in32 = 2,
k16in32 = 3,
k8in64 = 4,
k8in128 = 5,
};
enum class IndexFormat : uint32_t {
kInt16,
kInt32,
};
// SurfaceNumberX from yamato_enum.h.
enum class SurfaceNumberFormat : uint32_t {
kUnsignedRepeatingFraction = 0,
// Microsoft-style, scale factor (2^(n-1))-1.
kSignedRepeatingFraction = 1,
kUnsignedInteger = 2,
kSignedInteger = 3,
kFloat = 7,
};
// The EDRAM is an opaque block of memory accessible by the RB (render backend)
// 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 4D5307E6 main menu, 545407F2) 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 {
k_8_8_8_8 = 0,
k_8_8_8_8_GAMMA = 1,
k_2_10_10_10 = 2,
// 7e3 [0, 32) RGB, unorm alpha.
// http://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/eg05-xenos-doggett.pdf
k_2_10_10_10_FLOAT = 3,
// Fixed point -32...32.
// http://www.students.science.uu.nl/~3220516/advancedgraphics/papers/inferred_lighting.pdf
k_16_16 = 4,
// Fixed point -32...32.
k_16_16_16_16 = 5,
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;
}
inline uint32_t GetColorRenderTargetFormatComponentCount(
ColorRenderTargetFormat format) {
switch (format) {
case ColorRenderTargetFormat::k_8_8_8_8:
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
case ColorRenderTargetFormat::k_2_10_10_10:
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT:
case ColorRenderTargetFormat::k_16_16_16_16:
case ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
case ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16:
return 4;
case ColorRenderTargetFormat::k_16_16:
case ColorRenderTargetFormat::k_16_16_FLOAT:
case ColorRenderTargetFormat::k_32_32_FLOAT:
return 2;
case ColorRenderTargetFormat::k_32_FLOAT:
return 1;
default:
assert_unhandled_case(format);
return 0;
}
}
// Returns the version of the format with the same packing and meaning of values
// stored in it, but without blending precision modifiers.
constexpr ColorRenderTargetFormat GetStorageColorFormat(
ColorRenderTargetFormat format) {
switch (format) {
case ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
return ColorRenderTargetFormat::k_2_10_10_10;
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16:
return ColorRenderTargetFormat::k_2_10_10_10_FLOAT;
default:
return format;
}
}
enum class DepthRenderTargetFormat : uint32_t {
kD24S8 = 0,
// 20e4 [0, 2).
kD24FS8 = 1,
};
const char* GetDepthRenderTargetFormatName(DepthRenderTargetFormat format);
// Converts Xenos floating-point 7e3 color value in bits 0:9 (not clamping) to
// an IEEE-754 32-bit floating-point number.
float Float7e3To32(uint32_t f10);
// Converts 24-bit unorm depth in the value (not clamping) to an IEEE-754 32-bit
// floating-point number.
// Converts an IEEE-754 32-bit floating-point number to Xenos floating-point
// depth, rounding to the nearest even.
uint32_t Float32To20e4(float f32);
// 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);
// Converts 24-bit unorm depth in the value (not clamping) to an IEEE-754 32-bit
// floating-point number.
constexpr float UNorm24To32(uint32_t n24) {
// Not 1.0f / 16777215.0f as that gives an incorrect result (like for a very
// common 0xC00000 which clears 2_10_10_10 to 0001). Division by 2^24 is just
// an exponent shift though, thus exact.
// Division by 16777215.0f behaves this way.
return float(n24 + (n24 >> 23)) * (1.0f / float(1 << 24));
}
// Scale for conversion of slope scales from PA_SU_POLY_OFFSET_FRONT/BACK_SCALE
// units to those used when the slope is computed from the difference between
// adjacent pixels, for conversion from the guest to common host APIs or to
// calculation using max(|ddx(z)|, |ddy(z)|).
// "slope computed in subpixels (1/12 or 1/16)" - R5xx Acceleration.
// But the correct scale for conversion of the slope scale from subpixels to
// pixels is likely 1/16 according to:
// https://github.com/mesa3d/mesa/blob/54ad9b444c8e73da498211870e785239ad3ff1aa/src/gallium/drivers/radeonsi/si_state.c#L946
constexpr float kPolygonOffsetScaleSubpixelUnit = 1.0f / 16.0f;
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;
constexpr 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;
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 {
k_1_REVERSE = 0,
k_1 = 1,
k_8 = 2,
k_1_5_5_5 = 3,
k_5_6_5 = 4,
k_6_5_5 = 5,
k_8_8_8_8 = 6,
k_2_10_10_10 = 7,
k_8_A = 8,
k_8_B = 9,
k_8_8 = 10,
k_Cr_Y1_Cb_Y0_REP = 11,
k_Y1_Cr_Y0_Cb_REP = 12,
k_16_16_EDRAM = 13,
k_8_8_8_8_A = 14,
k_4_4_4_4 = 15,
k_10_11_11 = 16,
k_11_11_10 = 17,
k_DXT1 = 18,
k_DXT2_3 = 19,
k_DXT4_5 = 20,
k_16_16_16_16_EDRAM = 21,
k_24_8 = 22,
k_24_8_FLOAT = 23,
k_16 = 24,
k_16_16 = 25,
k_16_16_16_16 = 26,
k_16_EXPAND = 27,
k_16_16_EXPAND = 28,
k_16_16_16_16_EXPAND = 29,
k_16_FLOAT = 30,
k_16_16_FLOAT = 31,
k_16_16_16_16_FLOAT = 32,
k_32 = 33,
k_32_32 = 34,
k_32_32_32_32 = 35,
k_32_FLOAT = 36,
k_32_32_FLOAT = 37,
k_32_32_32_32_FLOAT = 38,
k_32_AS_8 = 39,
k_32_AS_8_8 = 40,
k_16_MPEG = 41,
k_16_16_MPEG = 42,
k_8_INTERLACED = 43,
k_32_AS_8_INTERLACED = 44,
k_32_AS_8_8_INTERLACED = 45,
k_16_INTERLACED = 46,
k_16_MPEG_INTERLACED = 47,
k_16_16_MPEG_INTERLACED = 48,
k_DXN = 49,
k_8_8_8_8_AS_16_16_16_16 = 50,
k_DXT1_AS_16_16_16_16 = 51,
k_DXT2_3_AS_16_16_16_16 = 52,
k_DXT4_5_AS_16_16_16_16 = 53,
k_2_10_10_10_AS_16_16_16_16 = 54,
k_10_11_11_AS_16_16_16_16 = 55,
k_11_11_10_AS_16_16_16_16 = 56,
k_32_32_32_FLOAT = 57,
k_DXT3A = 58,
k_DXT5A = 59,
k_CTX1 = 60,
k_DXT3A_AS_1_1_1_1 = 61,
k_8_8_8_8_GAMMA_EDRAM = 62,
k_2_10_10_10_FLOAT_EDRAM = 63,
};
// Subset of a2xx_sq_surfaceformat - formats that RTs can be resolved to.
enum class ColorFormat : uint32_t {
k_8 = 2,
k_1_5_5_5 = 3,
k_5_6_5 = 4,
k_6_5_5 = 5,
k_8_8_8_8 = 6,
k_2_10_10_10 = 7,
k_8_A = 8,
k_8_B = 9,
k_8_8 = 10,
k_8_8_8_8_A = 14,
k_4_4_4_4 = 15,
k_10_11_11 = 16,
k_11_11_10 = 17,
k_16 = 24,
k_16_16 = 25,
k_16_16_16_16 = 26,
k_16_FLOAT = 30,
k_16_16_FLOAT = 31,
k_16_16_16_16_FLOAT = 32,
k_32_FLOAT = 36,
k_32_32_FLOAT = 37,
k_32_32_32_32_FLOAT = 38,
k_8_8_8_8_AS_16_16_16_16 = 50,
k_2_10_10_10_AS_16_16_16_16 = 54,
k_10_11_11_AS_16_16_16_16 = 55,
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,
k_2_10_10_10 = 7,
k_10_11_11 = 16,
k_11_11_10 = 17,
k_16_16 = 25,
k_16_16_16_16 = 26,
k_16_16_FLOAT = 31,
k_16_16_16_16_FLOAT = 32,
k_32 = 33,
k_32_32 = 34,
k_32_32_32_32 = 35,
k_32_FLOAT = 36,
k_32_32_FLOAT = 37,
k_32_32_32_32_FLOAT = 38,
k_32_32_32_FLOAT = 57,
};
inline int GetVertexFormatComponentCount(VertexFormat format) {
switch (format) {
case VertexFormat::k_32:
case VertexFormat::k_32_FLOAT:
return 1;
case VertexFormat::k_16_16:
case VertexFormat::k_16_16_FLOAT:
case VertexFormat::k_32_32:
case VertexFormat::k_32_32_FLOAT:
return 2;
case VertexFormat::k_10_11_11:
case VertexFormat::k_11_11_10:
case VertexFormat::k_32_32_32_FLOAT:
return 3;
case VertexFormat::k_8_8_8_8:
case VertexFormat::k_2_10_10_10:
case VertexFormat::k_16_16_16_16:
case VertexFormat::k_16_16_16_16_FLOAT:
case VertexFormat::k_32_32_32_32:
case VertexFormat::k_32_32_32_32_FLOAT:
return 4;
default:
assert_unhandled_case(format);
return 0;
}
}
inline uint32_t GetVertexFormatNeededWords(VertexFormat format,
uint32_t used_components) {
assert_zero(used_components & ~uint32_t(0b1111));
if (!used_components) {
return 0;
}
switch (format) {
case VertexFormat::k_8_8_8_8:
case VertexFormat::k_2_10_10_10:
return 0b0001;
case VertexFormat::k_10_11_11:
case VertexFormat::k_11_11_10:
return (used_components & 0b0111) ? 0b0001 : 0b0000;
case VertexFormat::k_16_16:
case VertexFormat::k_16_16_FLOAT:
return (used_components & 0b0011) ? 0b0001 : 0b0000;
case VertexFormat::k_16_16_16_16:
case VertexFormat::k_16_16_16_16_FLOAT:
return ((used_components & 0b0011) ? 0b0001 : 0b0000) |
((used_components & 0b1100) ? 0b0010 : 0b0000);
case VertexFormat::k_32:
case VertexFormat::k_32_FLOAT:
return used_components & 0b0001;
case VertexFormat::k_32_32:
case VertexFormat::k_32_32_FLOAT:
return used_components & 0b0011;
case VertexFormat::k_32_32_32_32:
case VertexFormat::k_32_32_32_32_FLOAT:
return used_components;
case VertexFormat::k_32_32_32_FLOAT:
return used_components & 0b0111;
default:
assert_unhandled_case(format);
return 0b0000;
}
}
enum class CompareFunction : uint32_t {
kNever = 0b000,
kLess = 0b001,
kEqual = 0b010,
kLessEqual = 0b011,
kGreater = 0b100,
kNotEqual = 0b101,
kGreaterEqual = 0b110,
kAlways = 0b111,
};
enum class StencilOp : uint32_t {
kKeep = 0,
kZero = 1,
kReplace = 2,
kIncrementClamp = 3,
kDecrementClamp = 4,
kInvert = 5,
kIncrementWrap = 6,
kDecrementWrap = 7,
};
// adreno_rb_blend_factor
enum class BlendFactor : uint32_t {
kZero = 0,
kOne = 1,
kSrcColor = 4,
kOneMinusSrcColor = 5,
kSrcAlpha = 6,
kOneMinusSrcAlpha = 7,
kDstColor = 8,
kOneMinusDstColor = 9,
kDstAlpha = 10,
kOneMinusDstAlpha = 11,
kConstantColor = 12,
kOneMinusConstantColor = 13,
kConstantAlpha = 14,
kOneMinusConstantAlpha = 15,
kSrcAlphaSaturate = 16,
// SRC1 added on Adreno.
};
enum class BlendOp : uint32_t {
kAdd = 0,
kSubtract = 1,
kMin = 2,
kMax = 3,
kRevSubtract = 4,
};
typedef enum {
XE_GPU_INVALIDATE_MASK_VERTEX_SHADER = 1 << 8,
XE_GPU_INVALIDATE_MASK_PIXEL_SHADER = 1 << 9,
XE_GPU_INVALIDATE_MASK_ALL = 0x7FFF,
} XE_GPU_INVALIDATE_MASK;
// VGT_DRAW_INITIATOR::DI_SRC_SEL_*
enum class SourceSelect : uint32_t {
kDMA,
kImmediate,
kAutoIndex,
};
// VGT_DRAW_INITIATOR::DI_MAJOR_MODE_*
enum class MajorMode : uint32_t {
kImplicit,
kExplicit,
};
inline bool IsMajorModeExplicit(MajorMode major_mode,
PrimitiveType primitive_type) {
return major_mode != MajorMode::kImplicit ||
primitive_type >= PrimitiveType::kExplicitMajorModeForceStart;
}
enum class SignedRepeatingFractionMode : uint32_t {
// Microsoft-style representation with two -1 representations (one is slightly
// past -1 but clamped).
kZeroClampMinusOne,
// OpenGL "alternate mapping" format lacking representation for zero.
kNoZero,
};
// instr_arbitrary_filter_t
enum class ArbitraryFilter : uint32_t {
k2x4Sym = 0,
k2x4Asym = 1,
k4x2Sym = 2,
k4x2Asym = 3,
k4x4Sym = 4,
k4x4Asym = 5,
kUseFetchConst = 7,
};
// a2xx_sq_ps_vtx_mode
enum class VertexShaderExportMode : uint32_t {
kPosition1Vector = 0,
kPosition2VectorsSprite = 2,
kPosition2VectorsEdge = 3,
kPosition2VectorsKill = 4,
kPosition2VectorsSpriteKill = 5,
kPosition2VectorsEdgeKill = 6,
// Vertex shader outputs are ignored (kill all primitives) - see
// SX_MISC::MULTIPASS on R6xx/R7xx.
kMultipass = 7,
};
constexpr uint32_t kMaxInterpolators = 16;
enum class SampleControl : uint32_t {
kCentroidsOnly = 0,
kCentersOnly = 1,
kCentroidsAndCenters = 2,
};
// - msaa_samples is RB_SURFACE_INFO::msaa_samples.
// - sample_control is SQ_CONTEXT_MISC::sc_sample_cntl.
// - interpolator_control_sampling_pattern is
// SQ_INTERPOLATOR_CNTL::sampling_pattern.
// Centroid interpolation can be tested in 5454082B. If the GPU host backend
// implements guest MSAA properly, using host MSAA, with everything interpolated
// at centers, the Monument Valley start screen background may have a few
// distinctly bright pixels on the mesas/buttes, where extrapolation happens.
// Interpolating certain values (ones that aren't used for gradient calculation,
// not texture coordinates) at centroids fixes this issue.
inline uint32_t GetInterpolatorSamplingPattern(
MsaaSamples msaa_samples, SampleControl sample_control,
uint32_t interpolator_control_sampling_pattern) {
if (msaa_samples == MsaaSamples::k1X ||
sample_control == SampleControl::kCentersOnly) {
return ((1 << kMaxInterpolators) - 1) * uint32_t(SampleLocation::kCenter);
}
if (sample_control == SampleControl::kCentroidsOnly) {
return ((1 << kMaxInterpolators) - 1) * uint32_t(SampleLocation::kCentroid);
}
assert_true(sample_control == SampleControl::kCentroidsAndCenters);
return interpolator_control_sampling_pattern;
}
enum class VGTOutputPath : uint32_t {
kVertexReuse = 0,
kTessellationEnable = 1,
kPassthru = 2,
};
enum class TessellationMode : uint32_t {
kDiscrete = 0,
kContinuous = 1,
kAdaptive = 2,
};
enum class PolygonModeEnable : uint32_t {
kDisabled = 0, // Render triangles.
kDualMode = 1, // Send 2 sets of 3 polygons with the specified polygon type.
// 4541096E uses 2 for triangles, which is "reserved" on R6xx and not defined
// on Adreno 2xx, but polymode_front/back_ptype are 0 (points) in this case in
// 4541096E, which should not be respected for non-kDualMode as the title
// wants to draw filled triangles.
};
enum class PolygonType : uint32_t {
kPoints = 0,
kLines = 1,
kTriangles = 2,
};
enum class ModeControl : uint32_t {
kIgnore = 0,
kColorDepth = 4,
// TODO(Triang3l): Verify whether kDepth means the pixel shader is ignored
// completely even if it writes depth, exports to memory or kills pixels.
// Hints suggesting that it should be completely ignored (which is desirable
// on real hardware to avoid scheduling the pixel shader at all and waiting
// for it especially since the Xbox 360 doesn't have early per-sample depth /
// stencil, only early hi-Z / hi-stencil, and other registers possibly
// toggling pixel shader execution are yet to be found):
// - Most of depth pre-pass draws in 415607E6 use the kDepth more with a
// `oC0 = tfetch2D(tf0, r0.xy) * r1` shader, some use `oC0 = r0` though.
// However, when alphatested surfaces are drawn, kColorDepth is explicitly
// used with the same shader performing the texture fetch.
// - 5454082B has some kDepth draws with alphatest enabled, but the shader is
// `oC0 = r0`, which makes no sense (alphatest based on an interpolant from
// the vertex shader) as no texture alpha cutout is involved.
// - 5454082B also has kDepth draws with pretty complex shaders clearly for
// use only in the color pass - even fetching and filtering a shadowmap.
// For now, based on these, let's assume the pixel shader is never used with
// kDepth.
kDepth = 5,
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 4E4D07E9.
//
// Direct3D 9 gives the rectangle in source render target coordinates (for
// example, in 4D5307E6, 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 4D5307E6 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 58410955, 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
// 565507D9, 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, 4D5307E6 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,
kConstantOne = 2,
kNull = 3, // ?
};
// a2xx_rb_copy_sample_select
enum class CopySampleSelect : uint32_t {
k0,
k1,
k2,
k3,
k01,
k23,
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))
typedef enum {
XE_GPU_SWIZZLE_X = 0,
XE_GPU_SWIZZLE_R = 0,
XE_GPU_SWIZZLE_Y = 1,
XE_GPU_SWIZZLE_G = 1,
XE_GPU_SWIZZLE_Z = 2,
XE_GPU_SWIZZLE_B = 2,
XE_GPU_SWIZZLE_W = 3,
XE_GPU_SWIZZLE_A = 3,
XE_GPU_SWIZZLE_0 = 4,
XE_GPU_SWIZZLE_1 = 5,
XE_GPU_SWIZZLE_RGBA = XE_GPU_MAKE_SWIZZLE(R, G, B, A),
XE_GPU_SWIZZLE_BGRA = XE_GPU_MAKE_SWIZZLE(B, G, R, A),
XE_GPU_SWIZZLE_RGB1 = XE_GPU_MAKE_SWIZZLE(R, G, B, 1),
XE_GPU_SWIZZLE_BGR1 = XE_GPU_MAKE_SWIZZLE(B, G, R, 1),
XE_GPU_SWIZZLE_000R = XE_GPU_MAKE_SWIZZLE(0, 0, 0, R),
XE_GPU_SWIZZLE_RRR1 = XE_GPU_MAKE_SWIZZLE(R, R, R, 1),
XE_GPU_SWIZZLE_R111 = XE_GPU_MAKE_SWIZZLE(R, 1, 1, 1),
XE_GPU_SWIZZLE_R000 = XE_GPU_MAKE_SWIZZLE(R, 0, 0, 0),
} XE_GPU_SWIZZLE;
inline uint16_t GpuSwap(uint16_t value, Endian endianness) {
switch (endianness) {
case Endian::kNone:
// No swap.
return value;
case Endian::k8in16:
// Swap bytes in half words.
return ((value << 8) & 0xFF00FF00) | ((value >> 8) & 0x00FF00FF);
default:
assert_unhandled_case(endianness);
return value;
}
}
inline uint32_t GpuSwap(uint32_t value, Endian endianness) {
switch (endianness) {
default:
case Endian::kNone:
// No swap.
return value;
case Endian::k8in16:
// Swap bytes in half words.
return ((value << 8) & 0xFF00FF00) | ((value >> 8) & 0x00FF00FF);
case Endian::k8in32:
// Swap bytes.
// NOTE: we are likely doing two swaps here. Wasteful. Oh well.
return xe::byte_swap(value);
case Endian::k16in32:
// Swap half words.
return ((value >> 16) & 0xFFFF) | (value << 16);
}
}
inline float GpuSwap(float value, Endian endianness) {
union {
uint32_t i;
float f;
} v;
v.f = value;
v.i = GpuSwap(v.i, endianness);
return v.f;
}
inline uint32_t GpuToCpu(uint32_t p) { return p; }
inline uint32_t CpuToGpu(uint32_t p) { return p & 0x1FFFFFFF; }
// SQ_TEX_VTX_INVALID/VALID_TEXTURE/BUFFER
enum class FetchConstantType : uint32_t {
kInvalidTexture,
kInvalidVertex,
kTexture,
kVertex,
};
// XE_GPU_REG_SHADER_CONSTANT_FETCH_*
union alignas(uint32_t) xe_gpu_vertex_fetch_t {
struct {
uint32_t dword_0;
uint32_t dword_1;
};
struct {
FetchConstantType type : 2; // +0
uint32_t address : 30; // +2 address in dwords
Endian endian : 2; // +0
uint32_t size : 24; // +2 size in words
uint32_t unk1 : 6; // +26
};
};
static_assert_size(xe_gpu_vertex_fetch_t, sizeof(uint32_t) * 2);
// 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 = 1 << kTexture1DMaxWidthLog2;
constexpr uint32_t kTexture2DCubeMaxWidthHeightLog2 = 13;
constexpr uint32_t kTexture2DCubeMaxWidthHeight =
1 << kTexture2DCubeMaxWidthHeightLog2;
constexpr uint32_t kTexture2DMaxStackDepthLog2 = 6;
constexpr uint32_t kTexture2DMaxStackDepth = 1 << kTexture2DMaxStackDepthLog2;
constexpr uint32_t kTexture3DMaxWidthHeightLog2 = 11;
constexpr uint32_t kTexture3DMaxWidthHeight = 1 << kTexture3DMaxWidthHeightLog2;
constexpr uint32_t kTexture3DMaxDepthLog2 = 10;
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_*
union alignas(uint32_t) xe_gpu_texture_fetch_t {
struct {
uint32_t dword_0;
uint32_t dword_1;
uint32_t dword_2;
uint32_t dword_3;
uint32_t dword_4;
uint32_t dword_5;
};
struct {
FetchConstantType type : 2; // +0 dword_0
// Likely before the swizzle, seems logical from R5xx (SIGNED_COMP0/1/2/3
// set the signedness of components 0/1/2/3, while SEL_ALPHA/RED/GREEN/BLUE
// specify "swizzling for each channel at the input of the pixel shader",
// which can be texture components 0/1/2/3 or constant 0/1) and R6xx
// (signedness is FORMAT_COMP_X/Y/Z/W, while the swizzle is DST_SEL_X/Y/Z/W,
// which is named in resources the same as DST_SEL in fetch clauses).
TextureSign sign_x : 2; // +2
TextureSign sign_y : 2; // +4
TextureSign sign_z : 2; // +6
TextureSign sign_w : 2; // +8
ClampMode clamp_x : 3; // +10
ClampMode clamp_y : 3; // +13
ClampMode clamp_z : 3; // +16
SignedRepeatingFractionMode signed_rf_mode_all : 1; // +19
uint32_t dim_tbd : 2; // +20
// Base row pitch in pixels (not blocks) >> 5. For linear textures, this is
// provided by Direct3D 9 in a way that every row of blocks ends up aligned
// to kTextureLinearRowAlignmentBytes (the GPU requires 256-byte alignment
// of linear texture block rows for all textures).
// Mips are always stored with padding to the `max(next_pow2(base width or
// height) >> level, 1)` or a 32x32x4 tile (whichever is larger), so this
// pitch is irrelevant to them (but the 256-byte alignment requirement still
// applies to linear textures).
// Examples of pitch > aligned width:
// - 584109FF (loading screen and menu backgrounds, 1408 for a 1280x linear
// k_DXT4_5 texture, which corresponds to 22 * 256 bytes rather than
// 20 * 256 for just 1280x).
uint32_t pitch : 9; // +22
uint32_t tiled : 1; // +31
TextureFormat format : 6; // +0 dword_1
Endian endianness : 2; // +6
uint32_t request_size : 2; // +8
uint32_t stacked : 1; // +10
uint32_t nearest_clamp_policy : 1; // +11 d3d/opengl
uint32_t base_address : 20; // +12 base address >> 12
// Size is stored with 1 subtracted from each component.
union { // dword_2
struct {
uint32_t width : 24;
uint32_t : 8;
} size_1d;
struct {
uint32_t width : 13;
uint32_t height : 13;
// Should be 0 for k2D and 5 for kCube if not stacked, but not very
// meaningful in this case, likely should be ignored for non-stacked.
uint32_t stack_depth : 6;
} size_2d;
struct {
uint32_t width : 11;
uint32_t height : 11;
uint32_t depth : 10;
} size_3d;
};
uint32_t num_format : 1; // +0 dword_3 frac/int
// xyzw, 3b each (XE_GPU_SWIZZLE)
uint32_t swizzle : 12; // +1
int32_t exp_adjust : 6; // +13
TextureFilter mag_filter : 2; // +19
TextureFilter min_filter : 2; // +21
TextureFilter mip_filter : 2; // +23
AnisoFilter aniso_filter : 3; // +25
ArbitraryFilter arbitrary_filter : 3; // +28
uint32_t border_size : 1; // +31
uint32_t vol_mag_filter : 1; // +0 dword_4
uint32_t vol_min_filter : 1; // +1
uint32_t mip_min_level : 4; // +2
uint32_t mip_max_level : 4; // +6
uint32_t mag_aniso_walk : 1; // +10
uint32_t min_aniso_walk : 1; // +11
// 5 fractional bits (A2XX_SQ_TEX_4_LOD_BIAS).
int32_t lod_bias : 10; // +12
// Also known as LodBiasH/V in sys2gmem.
int32_t grad_exp_adjust_h : 5; // +22
int32_t grad_exp_adjust_v : 5; // +27
BorderColor border_color : 2; // +0 dword_5
uint32_t force_bc_w_to_max : 1; // +2
// Also known as TriJuice.
uint32_t tri_clamp : 2; // +3
int32_t aniso_bias : 4; // +5
DataDimension dimension : 2; // +9
uint32_t packed_mips : 1; // +11
uint32_t mip_address : 20; // +12 mip address >> 12
};
};
static_assert_size(xe_gpu_texture_fetch_t, sizeof(uint32_t) * 6);
// XE_GPU_REG_SHADER_CONSTANT_FETCH_*
union alignas(uint32_t) xe_gpu_fetch_group_t {
struct {
uint32_t dword_0;
uint32_t dword_1;
uint32_t dword_2;
uint32_t dword_3;
uint32_t dword_4;
uint32_t dword_5;
};
xe_gpu_texture_fetch_t texture_fetch;
struct {
xe_gpu_vertex_fetch_t vertex_fetch_0;
xe_gpu_vertex_fetch_t vertex_fetch_1;
xe_gpu_vertex_fetch_t vertex_fetch_2;
};
struct {
uint32_t type_0 : 2;
uint32_t data_0_a : 30;
uint32_t data_0_b : 32;
uint32_t type_1 : 2;
uint32_t data_1_a : 30;
uint32_t data_1_b : 32;
uint32_t type_2 : 2;
uint32_t data_2_a : 30;
uint32_t data_2_b : 32;
};
};
static_assert_size(xe_gpu_fetch_group_t, sizeof(uint32_t) * 6);
// Shader memory export (memexport) allows for writing of arbitrary formatted
// data with random access / scatter capabilities. It provides functionality
// largely similar to resolving - format packing, supporting arbitrary color
// formats, from sub-dword ones such as k_8 in 58410B86, to 128-bit ones, with
// endian swap similar to how it's performed in resolves (up to 128-bit);
// specifying the number format, swapping red and blue channels - though with no
// exponent biasing. Unlike resolving, however, instead of writing to tiled
// textures, it exports the data to up to 5 elements (the eM# shader registers,
// each corresponding to `base address + element size * (offset + 0...4)`) in a
// stream defined by a stream constant and an offset in elements written to eA -
// a shader, however, can write to multiple streams with different or the same
// stream constants, by performing `alloc export` multiple times. It's used
// mostly in vertex shaders (most commonly in improvised "compute shaders" done
// by executing a vertex shader for a number of point-type primitives covering
// nothing), though usage in pixel shaders is also possible - an example is
// provided in the "Advanced Screenspace Antialiasing" presentation by Arne
// Schober.
// https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdceurope2010/slides/A_Schober_Advanced_Screenspace_Antialiasing.pdf
//
// Unlike fetch constants, which are passed via special registers, a memory
// export stream is configured by writing the stream constant and the offset to
// a shader export register (eA) allocated by the shader - similar to more
// conventional exports like oPos, o#, oC#. Therefore, in general, it's not
// possible to know what its value will be without running the shader. For
// emulation, this means that the memory range referenced by an export - that
// needs to be validated - requires running the shader on the CPU in general.
// Thankfully, however, the usual way of setting up eA is by executing:
// `mad eA, r#, const0100, c#`
// where c# is the stream float4 constant from the float constant registers, and
// const0100 is a literal (0.0f, 1.0f, 0.0f, 0.0f) constant, also from the float
// constant registers, used for placing the element index (r#) in the correct
// component of eA. This allows for easy gathering of memexport stream
// constants, which contain both the base address and the size of the
// destination buffer for bounds checking, from the shader code and the float
// constant registers, as long as the guest uses this instruction pattern to
// write to eA.
//
// The Xenos doesn't have an integer ALU, and denormals are treated as zero and
// are flushed. However, eA contains integers and bit fields. A stream constant
// is thus structured in a way that allows for packing integers in normalized
// floating-point numbers.
//
// X contains the base address of the stream in dwords as integer bits in the
// lower 30 bits, and bits 0b01 in the top. The 0b01 bits make the exponent
// nonzero, so the number is considered normalized, and therefore isn't flushed
// to zero. With only 512 MB of the physical memory on the Xbox 360, the
// exponent can't become 0b11111111, so X also won't be NaN for any valid Xbox
// 360 physical address (though in general the GPU supports 32-bit addresses,
// but this is originally an Xbox 360-specific feature, that was later, however,
// likely reused for GL_QCOM_writeonly_rendering).
//
// TODO(Triang3l): Verify whether GL_QCOM_writeonly_rendering is actually
// memexport on the Adreno 2xx using GL_OES_get_program_binary - it's also
// interesting to see how alphatest interacts with it, whether it's still true
// fixed-function alphatest, as it's claimed to be supported as usual by the
// extension specification - it's likely, however, that memory exports are
// discarded alongside other exports such as oC# and oDepth this way.
//
// Y of eA contains the offset in elements - this is what shaders are supposed
// to calculate from something like the vertex index. Again, it's specified as
// an integer in the low bits, not as a truly floating-point number. For this
// purpose, stream constants contain the value 2^23 - when a whole
// floating-point number smaller than 2^23 is added as floating-point to 2^23,
// its integer representation becomes the mantissa bits of a number with an
// exponent of 23. Via multiply-add, `offset * 1.0f + exp2f(23)` is written here
// by the shader, allowing for element offsets of up to 2^23 - 1.
//
// Z is a bit field with the information about the formatting of the data. It's
// also packed as a normalized floating-point number, but in a cleaner way than
// X because not as many bits are required - just like Y, it has an exponent of
// 23 (possibly to let shaders build these values manually using floating-point
// multiply-add like integer shift-or, and finally to add 2^23, though that's
// not a case easy to handle in emulation, unlike prebuilt stream constants).
//
// W contains the number of elements in the stream. It's also packed with the
// full 23 exponent just like Y and Z, there's no way to index more than 2^23
// elements using packing via addition to 2^23, so this field also doesn't need
// more bits than that.
//
// Examples of setup in titles (Z from MSB to LSB):
//
// 4D5307E6 particles (different VS invocation counts, like 1, 2, 4):
// There is a passthrough shader - useful for verification as it simply writes
// directly what it reads via vfetch of various formats. Another shader (with
// different c# numbers, but same formats) does complicated math to process the
// particles.
// c152: Z = 010010110000|0|111|00|100110|00000|010, count = 35840
// 8in32, 32_32_32_32_FLOAT, float, RGBA - from 32_32_32_32_FLOAT vfetch
// c154, 162: Z = 010010110000|0|111|00|100000|00000|001, count = 71680
// 8in16, 16_16_16_16_FLOAT, float, RGBA - from 16_16_16_16_FLOAT vfetch
// c156, 158, 160: Z = 010010110000|0|000|00|011010|00000|001, count = 71680
// 8in16, 16_16_16_16, unorm, RGBA - from 16_16_16_16 unorm vfetch
// c164: Z = 010010110000|0|111|00|011111|00000|001, count = 143360
// 8in16, 16_16_FLOAT, float, RGBA - from 16_16_FLOAT vfetch
// c166: Z = 010010110000|0|000|00|011001|00000|001, count = 143360
// 8in16, 16_16, unorm, RGBA - from 16_16 unorm vfetch
// c168: Z = 010010110000|0|001|00|000111|00000|010, count = 143360
// 8in32, 2_10_10_10, snorm, RGBA - from 2_10_10_10 snorm vfetch
// c170, c172: Z = 010010110000|1|000|00|000110|00000|010, count = 143360
// 8in32, 8_8_8_8, unorm, BGRA - from 8_8_8_8 unorm vfetch with .zyxw swizzle
//
// 4D5307E6 water simulation (2048 VS invocations):
// c130: Z = 010010110000|0|111|00|100110|00000|010, count = 16384
// 8in32, 32_32_32_32_FLOAT, float, RGBA
// The shader has 5 memexports of this kind and 6 32_32_32_32_FLOAT vfetches.
//
// 4D5307E6 water tessellation factors (1 VS invocation per triangle patch):
// c130: Z = 010010110000|0|111|11|100100|11111|010, count = patch count * 3
// 8in32, 32_FLOAT, float, RGBA
//
// 41560817 texture memory copying (64 bytes per invocation, two eA, eight eM#):
// c0: Z = 010010110000|0|010|11|011010|00011|001
// 8in16, 16_16_16_16, uint, RGBA - from 16_16_16_16 uint vfetch
// (16_16_16_16 is the largest color format without special values)
union alignas(uint32_t) xe_gpu_memexport_stream_t {
struct {
uint32_t dword_0;
uint32_t dword_1;
uint32_t dword_2;
uint32_t dword_3;
};
struct {
uint32_t base_address : 30; // +0 dword_0 physical address >> 2
uint32_t const_0x1 : 2; // +30
uint32_t const_0x4b000000; // +0 dword_1
Endian128 endianness : 3; // +0 dword_2
uint32_t unused_0 : 5; // +3
ColorFormat format : 6; // +8
uint32_t unused_1 : 2; // +14
SurfaceNumberFormat num_format : 3; // +16
uint32_t red_blue_swap : 1; // +19
uint32_t const_0x4b0 : 12; // +20
uint32_t index_count : 23; // +0 dword_3
uint32_t const_0x96 : 9; // +23
};
};
static_assert_size(xe_gpu_memexport_stream_t, sizeof(uint32_t) * 4);
struct alignas(uint32_t) xe_gpu_depth_sample_counts {
// This is little endian as it is swapped in D3D code.
// Corresponding A and B values are summed up by D3D.
// Occlusion there is calculated by substracting begin from end struct.
le<uint32_t> Total_A;
le<uint32_t> Total_B;
le<uint32_t> ZFail_A;
le<uint32_t> ZFail_B;
le<uint32_t> ZPass_A;
le<uint32_t> ZPass_B;
le<uint32_t> StencilFail_A;
le<uint32_t> StencilFail_B;
};
static_assert_size(xe_gpu_depth_sample_counts, sizeof(uint32_t) * 8);
// Enum of event values used for VGT_EVENT_INITIATOR
enum Event {
VS_DEALLOC = 0,
PS_DEALLOC = 1,
VS_DONE_TS = 2,
PS_DONE_TS = 3,
CACHE_FLUSH_TS = 4,
CONTEXT_DONE = 5,
CACHE_FLUSH = 6,
VIZQUERY_START = 7,
VIZQUERY_END = 8,
SC_WAIT_WC = 9,
MPASS_PS_CP_REFETCH = 10,
MPASS_PS_RST_START = 11,
MPASS_PS_INCR_START = 12,
RST_PIX_CNT = 13,
RST_VTX_CNT = 14,
TILE_FLUSH = 15,
CACHE_FLUSH_AND_INV_TS_EVENT = 20,
ZPASS_DONE = 21,
CACHE_FLUSH_AND_INV_EVENT = 22,
PERFCOUNTER_START = 23,
PERFCOUNTER_STOP = 24,
SCREEN_EXT_INIT = 25,
SCREEN_EXT_RPT = 26,
VS_FETCH_DONE_TS = 27,
};
// Opcodes (IT_OPCODE) for Type-3 commands in the ringbuffer.
// https://github.com/freedreno/amd-gpu/blob/master/include/api/gsl_pm4types.h
// Not sure if all of these are used.
// clang-format off
enum Type3Opcode {
PM4_ME_INIT = 0x48, // initialize CP's micro-engine
PM4_NOP = 0x10, // skip N 32-bit words to get to the next packet
PM4_INDIRECT_BUFFER = 0x3f, // indirect buffer dispatch. prefetch parser uses this packet type to determine whether to pre-fetch the IB
PM4_INDIRECT_BUFFER_PFD = 0x37, // indirect buffer dispatch. same as IB, but init is pipelined
PM4_WAIT_FOR_IDLE = 0x26, // wait for the IDLE state of the engine
PM4_WAIT_REG_MEM = 0x3c, // wait until a register or memory location is a specific value
PM4_WAIT_REG_EQ = 0x52, // wait until a register location is equal to a specific value
PM4_WAIT_REG_GTE = 0x53, // wait until a register location is >= a specific value
PM4_WAIT_UNTIL_READ = 0x5c, // wait until a read completes
PM4_WAIT_IB_PFD_COMPLETE = 0x5d, // wait until all base/size writes from an IB_PFD packet have completed
PM4_REG_RMW = 0x21, // register read/modify/write
PM4_REG_TO_MEM = 0x3e, // reads register in chip and writes to memory
PM4_MEM_WRITE = 0x3d, // write N 32-bit words to memory
PM4_MEM_WRITE_CNTR = 0x4f, // write CP_PROG_COUNTER value to memory
PM4_COND_EXEC = 0x44, // conditional execution of a sequence of packets
PM4_COND_WRITE = 0x45, // conditional write to memory or register
PM4_EVENT_WRITE = 0x46, // generate an event that creates a write to memory when completed
PM4_EVENT_WRITE_SHD = 0x58, // generate a VS|PS_done event
PM4_EVENT_WRITE_CFL = 0x59, // generate a cache flush done event
PM4_EVENT_WRITE_EXT = 0x5a, // generate a screen extent event
PM4_EVENT_WRITE_ZPD = 0x5b, // generate a z_pass done event
PM4_DRAW_INDX = 0x22, // initiate fetch of index buffer and draw
PM4_DRAW_INDX_2 = 0x36, // draw using supplied indices in packet
PM4_DRAW_INDX_BIN = 0x34, // initiate fetch of index buffer and binIDs and draw
PM4_DRAW_INDX_2_BIN = 0x35, // initiate fetch of bin IDs and draw using supplied indices
PM4_VIZ_QUERY = 0x23, // begin/end initiator for viz query extent processing
PM4_SET_STATE = 0x25, // fetch state sub-blocks and initiate shader code DMAs
PM4_SET_CONSTANT = 0x2d, // load constant into chip and to memory
PM4_SET_CONSTANT2 = 0x55, // INCR_UPDATE_STATE
PM4_SET_SHADER_CONSTANTS = 0x56, // INCR_UPDT_CONST
PM4_LOAD_ALU_CONSTANT = 0x2f, // load constants from memory
PM4_IM_LOAD = 0x27, // load sequencer instruction memory (pointer-based)
PM4_IM_LOAD_IMMEDIATE = 0x2b, // load sequencer instruction memory (code embedded in packet)
PM4_LOAD_CONSTANT_CONTEXT = 0x2e, // load constants from a location in memory
PM4_INVALIDATE_STATE = 0x3b, // selective invalidation of state pointers
PM4_SET_SHADER_BASES = 0x4A, // dynamically changes shader instruction memory partition
PM4_SET_BIN_BASE_OFFSET = 0x4B, // program an offset that will added to the BIN_BASE value of the 3D_DRAW_INDX_BIN packet
PM4_SET_BIN_MASK = 0x50, // sets the 64-bit BIN_MASK register in the PFP
PM4_SET_BIN_SELECT = 0x51, // sets the 64-bit BIN_SELECT register in the PFP
PM4_CONTEXT_UPDATE = 0x5e, // updates the current context, if needed
PM4_INTERRUPT = 0x54, // generate interrupt from the command stream
PM4_XE_SWAP = 0x64, // Xenia only: VdSwap uses this to trigger a swap.
PM4_IM_STORE = 0x2c, // copy sequencer instruction memory to system memory
// Tiled rendering:
// https://www.google.com/patents/US20060055701
PM4_SET_BIN_MASK_LO = 0x60,
PM4_SET_BIN_MASK_HI = 0x61,
PM4_SET_BIN_SELECT_LO = 0x62,
PM4_SET_BIN_SELECT_HI = 0x63,
};
// clang-format on
inline uint32_t MakePacketType0(uint16_t index, uint16_t count,
bool one_reg = false) {
// ttcccccc cccccccc oiiiiiii iiiiiiii
assert(index <= 0x7FFF);
assert(count >= 1 && count <= 0x4000);
return (0u << 30) | (((count - 1) & 0x3FFF) << 16) | (index & 0x7FFF);
}
inline uint32_t MakePacketType1(uint16_t index_1, uint16_t index_2) {
// tt?????? ??222222 22222111 11111111
assert(index_1 <= 0x7FF);
assert(index_2 <= 0x7FF);
return (1u << 30) | ((index_2 & 0x7FF) << 11) | (index_1 & 0x7FF);
}
constexpr inline uint32_t MakePacketType2() {
// tt?????? ???????? ???????? ????????
return (2u << 30);
}
inline uint32_t MakePacketType3(Type3Opcode opcode, uint16_t count,
bool predicate = false) {
// ttcccccc cccccccc ?ooooooo ???????p
assert(opcode <= 0x7F);
assert(count >= 1 && count <= 0x4000);
return (3u << 30) | (((count - 1) & 0x3FFF) << 16) | ((opcode & 0x7F) << 8) |
(predicate ? 1 : 0);
}
} // namespace xenos
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_XENOS_H_