668 lines
30 KiB
C++
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_
|