Files
Xenia-Canary/src/xenia/gpu/render_target_cache.h
2021-09-27 13:43:57 +03:00

668 lines
30 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_RENDER_TARGET_CACHE_H_
#define XENIA_GPU_RENDER_TARGET_CACHE_H_
#include <cmath>
#include <cstdint>
#include <functional>
#include <map>
#include <unordered_map>
#include <utility>
#include <vector>
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/gpu/draw_util.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/xenos.h"
DECLARE_bool(depth_transfer_not_equal_test);
DECLARE_int32(draw_resolution_scale);
DECLARE_bool(draw_resolution_scaled_texture_offsets);
DECLARE_bool(gamma_render_target_as_srgb);
DECLARE_bool(native_2x_msaa);
DECLARE_bool(native_stencil_value_output);
DECLARE_bool(snorm16_render_target_full_range);
namespace xe {
namespace gpu {
class RenderTargetCache {
public:
// High-level emulation logic implementation path.
enum class Path {
// Approximate method using conventional host render targets and copying
// ("transferring ownership" of tiles) between render targets to support
// aliasing.
//
// May be irreparably inaccurate, completely at the mercy of the host API's
// fixed-function output-merger, primarily because it has to perform
// blending - and when using a different pixel format, it will behave
// differently (the most important factor here is the range - it's clamped
// for normalized formats, but not for floating-point ones).
//
// On a Direct3D 11-level device, formats which can be mapped directly
// (disregarding things like blending internal precision details):
// - 8_8_8_8
// - 2_10_10_10
// - 32_FLOAT
// - 32_32_FLOAT
// - D24S8
// Can be mapped directly, but require handling in shaders:
// - D24FS8 with truncated SV_DepthLessEqual output (or SV_Depth, which is
// suboptimal, as it prevents early depth / stencil from working). To
// support bit-exact reinterpretation to and from D24F for unmodified
// areas using pixel shader depth output without unrestricted depth range,
// 0...1 of the guest depth should be mapped to 0...0.5 on the host in the
// viewport and conversion.
// Can be mapped directly, but not supporting rare edge cases:
// - 16_16_FLOAT, k_16_16_16_16_FLOAT - the Xenos float16 doesn't have
// special values.
// Significant differences:
// - 8_8_8_8_GAMMA - the piecewise linear gamma curve is very different than
// sRGB, one possible path is conversion in shaders (resulting in
// incorrect blending, especially visible on decals in 4D5307E6), another
// is using sRGB render targets and either conversion on resolve or
// reading the resolved data as a true sRGB texture (incorrect when the
// game accesses the data directly, like 4541080F).
// - 2_10_10_10_FLOAT - ranges significantly different than in float16, much
// smaller RGB range, and alpha is fixed-point and has only 2 bits.
// - 16_16, 16_16_16_16 - has -32 to 32 range, not -1 to 1 - need either to
// truncate the range for blending to work correctly, or divide by 32 in
// shaders breaking multiplication in blending.
kHostRenderTargets,
// Custom output-merger implementation, with full per-pixel and per-sample
// control, however, only available on hosts with raster-ordered writes from
// pixel shaders.
kPixelShaderInterlock,
};
enum class DepthFloat24Conversion {
// Doing depth test at the host precision, converting to 20e4 to support
// reinterpretation, but keeping track of both the last color (or non-20e4
// depth) value (let's call it stored_f24) and the last host depth value
// (stored_host) for each EDRAM pixel, reloading the last host depth value
// if stored_f24 == to_f24(stored_host) (otherwise it was overwritten by
// something else, like clearing, or an actually used color buffer; this is
// inexact though, and will incorrectly load pixels that were overwritten by
// something else in the EDRAM, but turned out to have the same value on the
// guest as before - an outdated host-precision value will be loaded in
// these cases instead).
//
// EDRAM > RAM, then reusing the EDRAM region for something else > EDRAM
// round trip destroys precision beyond repair.
//
// Full host early Z and MSAA with pixel-rate shading are supported.
kOnCopy,
// Converting the depth to the closest host value representable exactly as a
// 20e4 float in pixel shaders, to support invariance in cases when the
// guest reuploads a previously resolved depth buffer to the EDRAM, rounding
// towards zero (which contradicts the rounding used by the Direct3D 9
// reference rasterizer, but allows less-than-or-equal pixel shader depth
// output to be used to preserve most of early Z culling when the game is
// using reversed depth, which is the usual way of doing depth testing on
// the Xbox 360 and of utilizing the advantages of a floating-point
// encoding).
//
// With MSAA, pixel shaders must run at sample frequency - otherwise, if the
// depth is the same for the entire pixel, intersections of polygons cannot
// be antialiased.
//
// Important usage note: When using this mode, bounds of the fixed-function
// viewport must be converted to and back from float24 too (preferably using
// correct rounding to the nearest even, to reduce the error already caused
// by truncation rather than to amplify it). This ensures that clamping to
// the viewport bounds, which happens after the pixel shader even if it
// overwrites the resulting depth, is never done to a value not
// representable as float24 (for example, if the minimum Z is a number too
// small to be represented as float24, but not zero, it won't be possible to
// write what should become 0x000000 to the depth buffer). Note that this
// may add some error to the depth values from the rasterizer; however,
// modifying Z in the vertex shader to make interpolated depth values would
// cause clipping to be done to different bounds, which may be more
// undesirable, especially in cases when Z is explicitly set to a value like
// 0 or W (in such cases, the adjusted polygon may go outside 0...W in clip
// space and disappear).
kOnOutputTruncating,
// Similar to kOnOutputTruncating, but rounding to the nearest even, more
// correctly, however, because the resulting depth can be bigger than the
// original host value, early depth testing can't be used at all. Same
// viewport usage rules apply.
kOnOutputRounding,
};
// Useful host-specific values.
// sRGB conversion from the Direct3D 11.3 functional specification.
static constexpr float kSrgbToLinearDenominator1 = 12.92f;
static constexpr float kSrgbToLinearDenominator2 = 1.055f;
static constexpr float kSrgbToLinearExponent = 2.4f;
static constexpr float kSrgbToLinearOffset = 0.055f;
static constexpr float kSrgbToLinearThreshold = 0.04045f;
static constexpr float SrgbToLinear(float srgb) {
// 0 and 1 must be exactly achievable, also convert NaN to 0.
if (!(srgb > 0.0f)) {
return 0.0f;
}
if (!(srgb < 1.0f)) {
return 1.0f;
}
if (srgb <= kSrgbToLinearThreshold) {
return srgb / kSrgbToLinearDenominator1;
}
return std::pow((srgb + kSrgbToLinearOffset) / kSrgbToLinearDenominator2,
kSrgbToLinearExponent);
}
virtual ~RenderTargetCache();
virtual Path GetPath() const = 0;
virtual uint32_t GetResolutionScale() const = 0;
// Virtual (both the common code and the implementation may do something
// here), don't call from destructors (does work not needed for shutdown
// also).
virtual void ClearCache();
virtual void BeginFrame();
virtual bool Update(bool is_rasterization_done,
uint32_t shader_writes_color_targets);
// Returns bits where 0 is whether a depth render target is currently bound on
// the host and 1... are whether the same applies to color render targets, and
// formats (resource formats, but if needed, with gamma taken into account) of
// each.
uint32_t GetLastUpdateBoundRenderTargets(
bool distinguish_gamma_formats,
uint32_t* depth_and_color_formats_out = nullptr) const;
protected:
RenderTargetCache(const RegisterFile& register_file)
: register_file_(register_file) {}
const RegisterFile& register_file() const { return register_file_; }
// Call last in implementation-specific initialization (when things like path
// are initialized by the implementation).
void InitializeCommon();
// Call last in implementation-specific shutdown, also callable from the
// destructor.
void ShutdownCommon();
// For host render targets, implemented via transfer of ownership of EDRAM
// 80x16-sample tiles between host render targets. When a range is
// transferred, its data is copied, bit-exactly from the guest's perspective
// (when dangerous, such as because of non-propagated NaN, primarily in the
// float16 case, by drawing to an integer view of the render target texture),
// from the previous host render target to the new one, by drawing rectangles
// with a pixel shader converting the previous host render target to a guest
// bit pattern, reinterpreting it in the new format. If depth is emulated with
// float32, this may lead to loss of data - specifically for depth, both guest
// format ownership and float32 ownership are tracked, and to let color data
// overwrite depth data, loading during ownership transfer is done from
// intersections of the current guest ownership ranges and float32 ownership
// ranges. Ownership transfer happens when a render target is needed - based
// on the current viewport; or, if no viewport is available, ownership of the
// rest of the EDRAM is transferred.
union RenderTargetKey {
struct {
// [0, 2047].
uint32_t base_tiles : xenos::kEdramBaseTilesBits - 1; // 11
// At 4x MSAA (2 horizontal samples), max. align(8192 * 2, 80) / 80 = 205.
// For pitch at 64bpp, multiply by 2 (or use GetPitchTiles).
uint32_t pitch_tiles_at_32bpp : 8; // 19
xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits; // 21
uint32_t is_depth : 1; // 22
// Ignoring the blending precision and sRGB.
uint32_t resource_format : xenos::kRenderTargetFormatBits; // 26
};
uint32_t key = 0;
struct Hasher {
size_t operator()(const RenderTargetKey& render_target_key) const {
return std::hash<uint32_t>{}(render_target_key.key);
}
};
bool operator==(const RenderTargetKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const RenderTargetKey& other_key) const {
return !(*this == other_key);
}
bool IsEmpty() const {
// Meaningless when pitch_tiles_at_32bpp == 0, but for comparison
// purposes, only treat everything being 0 as a special case.
return key == 0;
}
xenos::ColorRenderTargetFormat GetColorFormat() const {
assert_false(is_depth);
return xenos::ColorRenderTargetFormat(resource_format);
}
xenos::DepthRenderTargetFormat GetDepthFormat() const {
assert_true(is_depth);
return xenos::DepthRenderTargetFormat(resource_format);
}
bool Is64bpp() const {
if (is_depth) {
return false;
}
return xenos::IsColorRenderTargetFormat64bpp(GetColorFormat());
}
uint32_t GetPitchTiles() const {
return pitch_tiles_at_32bpp << uint32_t(Is64bpp());
}
static constexpr uint32_t GetWidth(uint32_t pitch_tiles_at_32bpp,
xenos::MsaaSamples msaa_samples) {
return pitch_tiles_at_32bpp *
(xenos::kEdramTileWidthSamples >>
uint32_t(msaa_samples >= xenos::MsaaSamples::k4X));
}
uint32_t GetWidth() const {
return GetWidth(pitch_tiles_at_32bpp, msaa_samples);
}
std::string GetDebugName() const {
return fmt::format(
"RT @ {}t, <{}t>, {}xMSAA, {}", base_tiles, GetPitchTiles(),
uint32_t(1) << uint32_t(msaa_samples),
is_depth ? xenos::GetDepthRenderTargetFormatName(GetDepthFormat())
: xenos::GetColorRenderTargetFormatName(GetColorFormat()));
}
};
class RenderTarget {
public:
virtual ~RenderTarget() = default;
// Exclusive ownership, plus no point in moving (only allocated via new).
RenderTarget(const RenderTarget& render_target) = delete;
RenderTarget& operator=(const RenderTarget& render_target) = delete;
RenderTarget(RenderTarget&& render_target) = delete;
RenderTarget& operator=(RenderTarget&& render_target) = delete;
RenderTargetKey key() const { return key_; }
protected:
RenderTarget(RenderTargetKey key) : key_(key) {}
private:
RenderTargetKey key_;
};
struct Transfer {
uint32_t start_tiles;
uint32_t end_tiles;
RenderTarget* source;
RenderTarget* host_depth_source;
Transfer(uint32_t start_tiles, uint32_t end_tiles, RenderTarget* source,
RenderTarget* host_depth_source)
: start_tiles(start_tiles),
end_tiles(end_tiles),
source(source),
host_depth_source(host_depth_source) {
assert_true(start_tiles < end_tiles);
}
struct Rectangle {
uint32_t x_pixels;
uint32_t y_pixels;
uint32_t width_pixels;
uint32_t height_pixels;
};
static constexpr uint32_t kMaxRectanglesWithoutCutout = 3;
static constexpr uint32_t kMaxCutoutBorderRectangles = 4;
static constexpr uint32_t kMaxRectanglesWithCutout =
kMaxRectanglesWithoutCutout * kMaxCutoutBorderRectangles;
// Cutout can be specified for resolve clears - not to transfer areas that
// will be cleared to a single value anyway.
static uint32_t GetRangeRectangles(uint32_t start_tiles, uint32_t end_tiles,
uint32_t base_tiles,
uint32_t pitch_tiles,
xenos::MsaaSamples msaa_samples,
bool is_64bpp, Rectangle* rectangles_out,
const Rectangle* cutout = nullptr);
uint32_t GetRectangles(uint32_t base_tiles, uint32_t pitch_tiles,
xenos::MsaaSamples msaa_samples, bool is_64bpp,
Rectangle* rectangles_out,
const Rectangle* cutout = nullptr) const {
return GetRangeRectangles(start_tiles, end_tiles, base_tiles, pitch_tiles,
msaa_samples, is_64bpp, rectangles_out, cutout);
}
bool AreSourcesSame(const Transfer& other_transfer) const {
return source == other_transfer.source &&
host_depth_source == other_transfer.host_depth_source;
}
private:
static uint32_t AddRectangle(const Rectangle& rectangle,
Rectangle* rectangles_out,
const Rectangle* cutout = nullptr);
};
struct ResolveCopyDumpRectangle {
RenderTarget* render_target;
// If rows == 1:
// Row row_first span:
// [row_first_start, row_last_end)
// If rows > 1:
// Row row_first + row span:
// [row_first_start, row_length_used)
// Rows [row_first + 1, row_first + rows - 1) span:
// [row * pitch, row * pitch + row_length_used)
// Row row_first + rows - 1 span:
// [row * pitch, row * pitch + row_last_end)
uint32_t row_first;
uint32_t rows;
uint32_t row_first_start;
uint32_t row_last_end;
ResolveCopyDumpRectangle(RenderTarget* render_target, uint32_t row_first,
uint32_t rows, uint32_t row_first_start,
uint32_t row_last_end)
: render_target(render_target),
row_first(row_first),
rows(rows),
row_first_start(row_first_start),
row_last_end(row_last_end) {}
struct Dispatch {
uint32_t offset;
uint32_t width_tiles;
uint32_t height_tiles;
};
static constexpr uint32_t kMaxDispatches = 3;
uint32_t GetDispatches(uint32_t pitch_tiles, uint32_t row_length_used,
Dispatch* dispatches_out) const {
if (!rows) {
return 0;
}
// If the first and / or the last rows have the same X spans as the middle
// part, merge them with it.
uint32_t dispatch_count = 0;
if (rows == 1 || row_first_start) {
Dispatch& dispatch_first = dispatches_out[dispatch_count++];
dispatch_first.offset = row_first * pitch_tiles + row_first_start;
dispatch_first.width_tiles =
(rows == 1 ? row_last_end : row_length_used) - row_first_start;
dispatch_first.height_tiles = 1;
if (rows == 1) {
return dispatch_count;
}
}
uint32_t mid_row_first = row_first + 1;
uint32_t mid_rows = rows - 2;
if (!row_first_start) {
--mid_row_first;
++mid_rows;
}
if (row_last_end == row_length_used) {
++mid_rows;
}
if (mid_rows) {
Dispatch& dispatch_mid = dispatches_out[dispatch_count++];
dispatch_mid.offset = mid_row_first * pitch_tiles;
dispatch_mid.width_tiles = row_length_used;
dispatch_mid.height_tiles = mid_rows;
}
if (row_last_end != row_length_used) {
Dispatch& dispatch_last = dispatches_out[dispatch_count++];
dispatch_last.offset = (row_first + rows - 1) * pitch_tiles;
dispatch_last.width_tiles = row_last_end;
dispatch_last.height_tiles = 1;
}
return dispatch_count;
}
};
static DepthFloat24Conversion GetConfigDepthFloat24Conversion();
virtual uint32_t GetMaxRenderTargetWidth() const = 0;
virtual uint32_t GetMaxRenderTargetHeight() const = 0;
// Returns the height of a render target that's needed and can be created,
// taking guest and host limits into account. EDRAM base and 32bpp/64bpp are
// not taken into account, the same height is used for all render targets even
// if the implementation supports mixed-size render targets, so the
// implementation can freely disable individual render targets and let the
// other ones use the newly available space without restarting the whole
// render pass (on Vulkan, the actually used height is specified in
// VkFramebuffer).
uint32_t GetRenderTargetHeight(uint32_t pitch_tiles_at_32bpp,
xenos::MsaaSamples msaa_samples) const;
virtual RenderTarget* CreateRenderTarget(RenderTargetKey key) = 0;
// Whether depth buffer is encoded differently on the host, thus after
// aliasing naively, precision may be lost - host depth must only be
// overwritten if the new guest value is different than the current host depth
// when converted to the guest format (this catches the usual case of
// overwriting the depth buffer for clearing it mostly). 534507D6 intro
// cutscene, for example, has a good example of corruption that happens if
// this is not handled - the upper 1280x384 pixels are rendered in a very
// "striped" way if the depth precision is lost (if this is made always return
// false).
virtual bool IsHostDepthEncodingDifferent(
xenos::DepthRenderTargetFormat format) const = 0;
void ResetAccumulatedRenderTargets() {
are_accumulated_render_targets_valid_ = false;
}
RenderTarget* const* last_update_accumulated_render_targets() const {
assert_true(GetPath() == Path::kHostRenderTargets);
return last_update_accumulated_render_targets_;
}
uint32_t last_update_accumulated_color_targets_are_gamma() const {
assert_true(GetPath() == Path::kHostRenderTargets);
return last_update_accumulated_color_targets_are_gamma_;
}
const std::vector<Transfer>* last_update_transfers() const {
assert_true(GetPath() == Path::kHostRenderTargets);
return last_update_transfers_;
}
// Returns mappings between ranges within the specified tile rectangle (not
// render target texture rectangle - textures may have any pitch they need)
// from ResolveInfo::GetCopyEdramTileSpan and render targets owning them to
// rectangles_out.
void GetResolveCopyRectanglesToDump(
uint32_t base, uint32_t row_length, uint32_t rows, uint32_t pitch,
std::vector<ResolveCopyDumpRectangle>& rectangles_out) const;
// Sets up the needed render targets and transfers to perform a clear in a
// resolve operation via a host render target clear. resolve_info is expected
// to be obtained via draw_util::GetResolveInfo. Returns whether any clears
// need to be done (false in both empty and error cases).
// TODO(Triang3l): Try to defer clears until the first draw in the next pass
// (if it uses one or both render targets being cleared) for tile-based GPUs.
bool PrepareHostRenderTargetsResolveClear(
const draw_util::ResolveInfo& resolve_info,
Transfer::Rectangle& clear_rectangle_out,
RenderTarget*& depth_render_target_out,
std::vector<Transfer>& depth_transfers_out,
RenderTarget*& color_render_target_out,
std::vector<Transfer>& color_transfers_out);
// For restoring EDRAM contents from frame traces, obtains or creates a render
// target at base 0 with of 1280 (only 1 sample and color because copying
// between MSAA render targets and buffers is not possible in Direct3D 12, and
// depth may require additional format conversions, not needed really) and
// transfers ownership of the entire EDRAM to that render target. If a
// full-EDRAM render target can't be created (for instance, due to size
// limitations on the host), nullptr is returned.
RenderTarget* PrepareFullEdram1280xRenderTargetForSnapshotRestoration(
xenos::ColorRenderTargetFormat color_format);
// For pixel shader interlock.
virtual void RequestPixelShaderInterlockBarrier() {}
// To be called by the implementation when interlocked writes to all of the
// EDRAM memory are committed with a memory barrier.
void PixelShaderInterlockFullEdramBarrierPlaced();
private:
const RegisterFile& register_file_;
// For host render targets.
struct OwnershipRange {
uint32_t end_tiles;
// Need to store keys, not pointers to render targets themselves, because
// ownership transfer is also what's used to determine when to place
// barriers with pixel shader interlock, and in this case there are no host
// render targets.
// Render target this range is last used by.
RenderTargetKey render_target;
// Last host-side depth render targets that used this range even if it has
// been used by a different render target since then, only used if the
// respective format has a different encoding on the host. They are tracked
// separately, overwritten if the host value converted to the guest format
// becomes out of sync with the guest value. Even if the host uses float32
// to emulate both unorm24 and float24 (Vulkan on AMD), the unorm24 and
// float24 render targets are tracked separately from each other, so
// switching between unorm24 and float24 for the same depth data (clearing
// of most render targets is done through unorm24 without a viewport - very
// common) is not destructive as well (f32tof24(host_f32) == guest_f24 does
// not imply f32tou24(host_f32) == guest_u24, thus aliasing float24 with
// unorm24 through the same float32 buffer will drop the precision of the
// float32 value to that of an unorm24 with a totally wrong value). If the
// range hasn't been used yet (render_target.IsEmpty() == true), these are
// empty too.
RenderTargetKey host_depth_render_target_unorm24;
RenderTargetKey host_depth_render_target_float24;
OwnershipRange(uint32_t end_tiles, RenderTargetKey render_target,
RenderTargetKey host_depth_render_target_unorm24,
RenderTargetKey host_depth_render_target_float24)
: end_tiles(end_tiles),
render_target(render_target),
host_depth_render_target_unorm24(host_depth_render_target_unorm24),
host_depth_render_target_float24(host_depth_render_target_float24) {}
const RenderTargetKey& GetHostDepthRenderTarget(
xenos::DepthRenderTargetFormat resource_format) const {
assert_true(
resource_format == xenos::DepthRenderTargetFormat::kD24S8 ||
resource_format == xenos::DepthRenderTargetFormat::kD24FS8,
"Illegal resource format");
return resource_format == xenos::DepthRenderTargetFormat::kD24S8
? host_depth_render_target_unorm24
: host_depth_render_target_float24;
}
RenderTargetKey& GetHostDepthRenderTarget(
xenos::DepthRenderTargetFormat resource_format) {
return const_cast<RenderTargetKey&>(
const_cast<const OwnershipRange*>(this)->GetHostDepthRenderTarget(
resource_format));
}
bool IsOwnedBy(RenderTargetKey key,
bool host_depth_encoding_different) const {
if (render_target != key) {
// Last time used for something else. If it's a depth render target with
// different host depth encoding, might have been overwritten by color,
// or by a depth render target of a different format.
return false;
}
if (host_depth_encoding_different && !key.is_depth &&
GetHostDepthRenderTarget(key.GetDepthFormat()) != key) {
// Depth encoding is the same, but different addressing is needed.
return false;
}
return true;
}
bool AreOwnersSame(const OwnershipRange& other_range) const {
return render_target == other_range.render_target &&
host_depth_render_target_unorm24 ==
other_range.host_depth_render_target_unorm24 &&
host_depth_render_target_float24 ==
other_range.host_depth_render_target_float24;
}
};
static constexpr xenos::ColorRenderTargetFormat GetColorResourceFormat(
xenos::ColorRenderTargetFormat format) {
// sRGB, if used on the host, is a view property or global state - linear
// and sRGB host render targets can share data directly without transfers.
if (format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) {
return xenos::ColorRenderTargetFormat::k_8_8_8_8;
}
return xenos::GetStorageColorFormat(format);
}
RenderTarget* GetOrCreateRenderTarget(RenderTargetKey key);
// Checks if changing ownership of the range to the specified render target
// would require transferring data - primarily for barrier placement on the
// pixel shader interlock path (where transfers do not involve copying, but
// barriers are still needed before accessing ranges written before the
// barrier and addressed by different target-independent rasterization pixel
// positions.
bool WouldOwnershipChangeRequireTransfers(RenderTargetKey dest,
uint32_t start_tiles,
uint32_t length_tiles) const;
// Updates ownership_ranges_, adds the transfers needed for the ownership
// change to transfers_append_out if it's not null.
void ChangeOwnership(
RenderTargetKey dest, uint32_t start_tiles, uint32_t length_tiles,
std::vector<Transfer>* transfers_append_out,
const Transfer::Rectangle* resolve_clear_cutout = nullptr);
// If failed to create, may contain nullptr to prevent attempting to create a
// render target twice.
std::unordered_map<RenderTargetKey, RenderTarget*, RenderTargetKey::Hasher>
render_targets_;
// Map of host render targets currently containing the most up-to-date version
// of the tile. Has no gaps, unused parts are represented by empty render
// target keys.
// TODO(Triang3l): Pool allocator (or a custom red-black tree with one even),
// since standard containers use dynamic allocation for elements, though
// changes to this throughout a frame are pretty rare.
std::map<uint32_t, OwnershipRange> ownership_ranges_;
// Render targets actually used by the draw call with the last successful
// update. 0 is depth, color starting from 1, nullptr if not bound.
// Only valid for non-pixel-shader-interlock paths.
RenderTarget*
last_update_used_render_targets_[1 + xenos::kMaxColorRenderTargets];
// Render targets used by the draw call with the last successful update or
// previous updates, unless a different or a totally new one was bound (or
// surface info was changed), to avoid unneeded render target switching (which
// is especially undesirable on tile-based GPUs) in the implementation if
// simply disabling depth / stencil test or color writes and then re-enabling
// (58410954 does this often with color). Must also be used to determine
// whether it's safe to enable depth / stencil or writing to a specific color
// render target in the pipeline for this draw call.
// Only valid for non-pixel-shader-interlock paths.
RenderTarget*
last_update_accumulated_render_targets_[1 +
xenos::kMaxColorRenderTargets];
// Whether the color render targets (in bits 0...3) from the last successful
// update have k_8_8_8_8_GAMMA format, for sRGB emulation on the host if
// needed.
uint32_t last_update_accumulated_color_targets_are_gamma_;
// If false, the next update must copy last_update_used_render_targets_ to
// last_update_accumulated_render_targets_ - it's not beneficial or even
// incorrect to keep the previously bound render targets.
bool are_accumulated_render_targets_valid_ = false;
// After an update (for simplicity, even an unsuccessful update invalidates
// this), contains needed ownership transfer sources for each of the current
// render targets. They are reordered so for one source, all transfers are
// consecutive in the array.
std::vector<Transfer>
last_update_transfers_[1 + xenos::kMaxColorRenderTargets];
};
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_RENDER_TARGET_CACHE_H_