1473 lines
65 KiB
C++
1473 lines
65 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/render_target_cache.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <tuple>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/gpu/draw_util.h"
|
|
#include "xenia/gpu/register_file.h"
|
|
#include "xenia/gpu/registers.h"
|
|
#include "xenia/gpu/xenos.h"
|
|
|
|
DEFINE_bool(
|
|
depth_transfer_not_equal_test, true,
|
|
"When transferring data between depth render targets, use the \"not "
|
|
"equal\" test to avoid writing rewriting depth via shader depth output if "
|
|
"it's the same as the one currently in the depth buffer in case of round "
|
|
"trips of the data.\n"
|
|
"Settings this to true may make transfer round trips more friendly to "
|
|
"depth compression depending on how the GPU implements it (as arbitrary "
|
|
"depth output may result in it being disabled completely), which is "
|
|
"beneficial to subsequent rendering, while setting this to false may "
|
|
"reduce bandwidth usage during transfers as the previous depth won't need "
|
|
"to be read.",
|
|
"GPU");
|
|
// Lossless round trip: 545407F2.
|
|
// Lossy round trip with the "greater or equal" test afterwards: 4D530919.
|
|
// Lossy round trip with the "equal" test afterwards: 535107F5, 565507EF.
|
|
DEFINE_bool(
|
|
depth_float24_round, false,
|
|
"Whether to round to the nearest even, rather than truncating (rounding "
|
|
"towards zero), the depth when converting it to 24-bit floating-point "
|
|
"(20e4) from the host precision (32-bit floating point) when using a host "
|
|
"depth buffer.\n"
|
|
"false:\n"
|
|
" Recommended.\n"
|
|
" The conversion may move the depth values farther away from the camera.\n"
|
|
" Without depth_float24_convert_in_pixel_shader:\n"
|
|
" The \"greater or equal\" depth test function continues to work fine if "
|
|
"the full host precision depth data is lost, it's still possible to draw "
|
|
"another pass of the same geometry with it.\n"
|
|
" (See the description of depth_float24_convert_in_pixel_shader for more "
|
|
"information about full precision depth data loss.)\n"
|
|
" With depth_float24_convert_in_pixel_shader:\n"
|
|
" Faster - the pixel shader for hidden surfaces may still be skipped "
|
|
"(using conservative depth output).\n"
|
|
"true:\n"
|
|
" Only for special cases of issues caused by minor 32-bit floating-point "
|
|
"rounding errors, for instance, when the game tries to draw something at "
|
|
"the camera plane by setting Z of the vertex position to W.\n"
|
|
" The conversion may move the depth values closer or farther.\n"
|
|
" Using the same rounding mode as in the Direct3D 9 reference rasterizer.\n"
|
|
" Without depth_float24_convert_in_pixel_shader:\n"
|
|
" Not possible to recover from a full host precision depth data loss - in "
|
|
"subsequent passes of rendering the same geometry, half of the samples "
|
|
"will be failing the depth test with the \"greater or equal\" depth test "
|
|
"function.\n"
|
|
" With depth_float24_convert_in_pixel_shader:\n"
|
|
" Slower - depth rejection before the pixel shader is not possible.\n"
|
|
"When the depth buffer is emulated in software (via the fragment shader "
|
|
"interlock / rasterizer-ordered view), this is ignored, and rounding to "
|
|
"the nearest even is always done.",
|
|
"GPU");
|
|
// With MSAA, when converting the depth in pixel shaders, they 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
|
|
// rounding to the nearest even regardless of whether truncation was requested
|
|
// for the values, 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).
|
|
//
|
|
// If false, doing the 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).
|
|
DEFINE_bool(
|
|
depth_float24_convert_in_pixel_shader, false,
|
|
"Whether to convert the depth values to 24-bit floating-point (20e4) from "
|
|
"the host precision (32-bit floating point) directly in the pixel shaders "
|
|
"of guest draws when using a host depth buffer.\n"
|
|
"This prevents visual artifacts (interleaved stripes of parts of surfaces "
|
|
"rendered and parts not rendered, having either the same width in case of "
|
|
"the \"greater or equal\" depth test function, or the former being much "
|
|
"thinner than the latter with the \"equal\" function) if the full host "
|
|
"precision depth data is lost.\n"
|
|
"This issue may happen if the game reloads the depth data previously "
|
|
"evicted from the EDRAM to the RAM back to the EDRAM, but the EDRAM region "
|
|
"that previously contained that depth buffer was overwritten by another "
|
|
"depth buffer, or the game loads it to a different location in the EDRAM "
|
|
"than it was previously placed at, thus Xenia is unable to restore the "
|
|
"depth data with the original precision, and instead falls back to "
|
|
"converting the lower-precision values, so in subsequent rendering passes "
|
|
"for the same geometry, the actual depth values of the surfaces don't "
|
|
"match those stored in the depth buffer anymore.\n"
|
|
"This is a costly option because it makes the GPU unable to use depth "
|
|
"buffer compression, and also with MSAA, forces the pixel shader to run "
|
|
"for every subpixel sample rather than for the entire pixel, making pixel "
|
|
"shading 2 or 4 times heavier depending on the MSAA sample count.\n"
|
|
"The rounding direction is controlled by the depth_float24_round "
|
|
"configuration variable.\n"
|
|
"Note that with depth_float24_round = true, this becomes even more costly "
|
|
"because pixel shaders must be executed regardless of whether the surface "
|
|
"is behind the previously drawn surfaces. With depth_float24_round = "
|
|
"false, conservative depth output is used, however, so depth rejection "
|
|
"before the pixel shader may still work.\n"
|
|
"If sample-rate shading is not supported by the host GPU, the conversion "
|
|
"in the pixel shader is done only when MSAA is not used.\n"
|
|
"When the depth buffer is emulated in software (via the fragment shader "
|
|
"interlock / rasterizer-ordered view), this is ignored because 24-bit "
|
|
"depth is always used directly.",
|
|
"GPU");
|
|
DEFINE_bool(
|
|
draw_resolution_scaled_texture_offsets, true,
|
|
"Apply offsets from texture fetch instructions taking resolution scale "
|
|
"into account for render-to-texture, for more correct shadow filtering, "
|
|
"bloom, etc., in some cases.",
|
|
"GPU");
|
|
// Disabled by default because of full-screen effects that occur when game
|
|
// shaders assume piecewise linear (4541080F), much more severe than
|
|
// blending-related issues.
|
|
DEFINE_bool(
|
|
gamma_render_target_as_srgb, false,
|
|
"When the host can't write piecewise linear gamma directly with correct "
|
|
"blending, use sRGB output on the host for conceptually correct blending "
|
|
"in linear color space while having slightly different precision "
|
|
"distribution in the render target and severely incorrect values if the "
|
|
"game accesses the resulting colors directly as raw data.",
|
|
"GPU");
|
|
DEFINE_bool(
|
|
mrt_edram_used_range_clamp_to_min, true,
|
|
"With host render targets, if multiple render targets are bound, estimate "
|
|
"the EDRAM range modified in any of them to be not bigger than the "
|
|
"distance between any two render targets in the EDRAM, rather than "
|
|
"allowing the last one claim the rest of the EDRAM.\n"
|
|
"Has effect primarily on draws without viewport clipping.\n"
|
|
"Setting this to false results in higher accuracy in rare cases, but may "
|
|
"increase the amount of copying that needs to be done sometimes.",
|
|
"GPU");
|
|
DEFINE_bool(
|
|
native_2x_msaa, true,
|
|
"Use host 2x MSAA when available. Can be disabled for scalability testing "
|
|
"on host GPU APIs where 2x is not mandatory, in this case, 2 samples of 4x "
|
|
"MSAA will be used instead (with similar or worse quality and higher "
|
|
"memory usage).",
|
|
"GPU");
|
|
DEFINE_bool(
|
|
native_stencil_value_output, true,
|
|
"Use pixel shader stencil reference output where available for purposes "
|
|
"like copying between render targets. Can be disabled for scalability "
|
|
"testing, in this case, much more expensive drawing of 8 quads will be "
|
|
"done.",
|
|
"GPU");
|
|
DEFINE_bool(
|
|
snorm16_render_target_full_range, true,
|
|
"When the host can only support 16_16 and 16_16_16_16 render targets as "
|
|
"-1...1, remap -32...32 to -1...1 to use the full possible range of "
|
|
"values, at the expense of multiplicative blending correctness.",
|
|
"GPU");
|
|
// Enabled by default as the GPU is overall usually the bottleneck when the
|
|
// pixel shader interlock render backend implementation is used, anything that
|
|
// may improve GPU performance is favorable.
|
|
DEFINE_bool(
|
|
execute_unclipped_draw_vs_on_cpu_for_psi_render_backend, true,
|
|
"If execute_unclipped_draw_vs_on_cpu is enabled, execute the vertex shader "
|
|
"for unclipped draws on the CPU even when using the pixel shader interlock "
|
|
"(rasterizer-ordered view) implementation of the render backend on the "
|
|
"host, for which no expensive copying between host render targets is "
|
|
"needed when the ownership of a EDRAM range is changed.\n"
|
|
"If this is enabled, excessive barriers may be eliminated when switching "
|
|
"between different render targets in separate EDRAM locations.",
|
|
"GPU");
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
|
|
uint32_t RenderTargetCache::Transfer::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) {
|
|
assert_true(start_tiles <= end_tiles);
|
|
assert_true(base_tiles <= start_tiles);
|
|
assert_not_zero(pitch_tiles);
|
|
if (start_tiles == end_tiles) {
|
|
return 0;
|
|
}
|
|
uint32_t tile_width =
|
|
xenos::kEdramTileWidthSamples >>
|
|
(uint32_t(msaa_samples >= xenos::MsaaSamples::k4X) + uint32_t(is_64bpp));
|
|
uint32_t tile_height = xenos::kEdramTileHeightSamples >>
|
|
uint32_t(msaa_samples >= xenos::MsaaSamples::k2X);
|
|
// If the first and / or the last rows have the same X spans as the middle
|
|
// part, merge them with it.
|
|
uint32_t rectangle_count = 0;
|
|
uint32_t local_start = start_tiles - base_tiles;
|
|
uint32_t local_end = end_tiles - base_tiles;
|
|
// Inclusive.
|
|
uint32_t rows_start = local_start / pitch_tiles;
|
|
// Exclusive.
|
|
uint32_t rows_end = (local_end + (pitch_tiles - 1)) / pitch_tiles;
|
|
uint32_t row_first_start = local_start - rows_start * pitch_tiles;
|
|
uint32_t row_last_end = pitch_tiles - (rows_end * pitch_tiles - local_end);
|
|
uint32_t rows = rows_end - rows_start;
|
|
if (rows == 1 || row_first_start) {
|
|
Rectangle rectangle_first;
|
|
rectangle_first.x_pixels = row_first_start * tile_width;
|
|
rectangle_first.y_pixels = rows_start * tile_height;
|
|
rectangle_first.width_pixels =
|
|
((rows == 1 ? row_last_end : pitch_tiles) - row_first_start) *
|
|
tile_width;
|
|
rectangle_first.height_pixels = tile_height;
|
|
rectangle_count += AddRectangle(
|
|
rectangle_first,
|
|
rectangles_out ? rectangles_out + rectangle_count : nullptr, cutout);
|
|
if (rows == 1) {
|
|
return rectangle_count;
|
|
}
|
|
}
|
|
uint32_t mid_rows_start = rows_start + 1;
|
|
uint32_t mid_rows = rows - 2;
|
|
if (!row_first_start) {
|
|
--mid_rows_start;
|
|
++mid_rows;
|
|
}
|
|
if (row_last_end == pitch_tiles) {
|
|
++mid_rows;
|
|
}
|
|
if (mid_rows) {
|
|
Rectangle rectangle_mid;
|
|
rectangle_mid.x_pixels = 0;
|
|
rectangle_mid.y_pixels = mid_rows_start * tile_height;
|
|
rectangle_mid.width_pixels = pitch_tiles * tile_width;
|
|
rectangle_mid.height_pixels = mid_rows * tile_height;
|
|
rectangle_count += AddRectangle(
|
|
rectangle_mid,
|
|
rectangles_out ? rectangles_out + rectangle_count : nullptr, cutout);
|
|
}
|
|
if (row_last_end != pitch_tiles) {
|
|
Rectangle rectangle_last;
|
|
rectangle_last.x_pixels = 0;
|
|
rectangle_last.y_pixels = (rows_end - 1) * tile_height;
|
|
rectangle_last.width_pixels = row_last_end * tile_width;
|
|
rectangle_last.height_pixels = tile_height;
|
|
rectangle_count += AddRectangle(
|
|
rectangle_last,
|
|
rectangles_out ? rectangles_out + rectangle_count : nullptr, cutout);
|
|
}
|
|
assert_true(rectangle_count <= (cutout ? kMaxRectanglesWithCutout
|
|
: kMaxRectanglesWithoutCutout));
|
|
return rectangle_count;
|
|
}
|
|
|
|
uint32_t RenderTargetCache::Transfer::AddRectangle(const Rectangle& rectangle,
|
|
Rectangle* rectangles_out,
|
|
const Rectangle* cutout) {
|
|
uint32_t rectangle_right = rectangle.x_pixels + rectangle.width_pixels;
|
|
uint32_t rectangle_bottom = rectangle.y_pixels + rectangle.height_pixels;
|
|
// If nothing to cut out (no region specified, or no intersection - if the
|
|
// cutout region is in the middle on Y, but completely to the left / right on
|
|
// X, don't split), add the whole rectangle.
|
|
if (!cutout || !cutout->width_pixels || !cutout->height_pixels ||
|
|
cutout->x_pixels >= rectangle_right ||
|
|
cutout->x_pixels + cutout->width_pixels <= rectangle.x_pixels ||
|
|
cutout->y_pixels >= rectangle_bottom ||
|
|
cutout->y_pixels + cutout->height_pixels <= rectangle.y_pixels) {
|
|
if (rectangles_out) {
|
|
rectangles_out[0] = rectangle;
|
|
}
|
|
return 1;
|
|
}
|
|
uint32_t rectangle_count = 0;
|
|
uint32_t cutout_right = cutout->x_pixels + cutout->width_pixels;
|
|
uint32_t cutout_bottom = cutout->y_pixels + cutout->height_pixels;
|
|
// Upper part after cutout.
|
|
if (cutout->y_pixels > rectangle.y_pixels) {
|
|
// The completely outside case has already been checked.
|
|
assert_true(cutout->y_pixels < rectangle_bottom);
|
|
if (rectangles_out) {
|
|
Rectangle& rectangle_upper = rectangles_out[rectangle_count];
|
|
rectangle_upper.x_pixels = rectangle.x_pixels;
|
|
rectangle_upper.y_pixels = rectangle.y_pixels;
|
|
rectangle_upper.width_pixels = rectangle.width_pixels;
|
|
// cutout->y_pixels is already known to be < rectangle_bottom, no need for
|
|
// min(cutout->y_pixels - rectangle.y_pixels, rectangle.height_pixels).
|
|
rectangle_upper.height_pixels = cutout->y_pixels - rectangle.y_pixels;
|
|
}
|
|
++rectangle_count;
|
|
}
|
|
// Middle part after cutout.
|
|
uint32_t middle_top = std::max(cutout->y_pixels, rectangle.y_pixels);
|
|
uint32_t middle_height =
|
|
std::min(cutout_bottom, rectangle_bottom) - middle_top;
|
|
// Middle left.
|
|
if (cutout->x_pixels > rectangle.x_pixels) {
|
|
assert_true(cutout->x_pixels < rectangle_right);
|
|
if (rectangles_out) {
|
|
Rectangle& rectangle_middle_left = rectangles_out[rectangle_count];
|
|
rectangle_middle_left.x_pixels = rectangle.x_pixels;
|
|
rectangle_middle_left.y_pixels = middle_top;
|
|
rectangle_middle_left.width_pixels =
|
|
cutout->x_pixels - rectangle.x_pixels;
|
|
rectangle_middle_left.height_pixels = middle_height;
|
|
}
|
|
++rectangle_count;
|
|
}
|
|
// Middle right.
|
|
if (cutout_right < rectangle_right) {
|
|
assert_true(cutout_right > rectangle.x_pixels);
|
|
if (rectangles_out) {
|
|
Rectangle& rectangle_middle_right = rectangles_out[rectangle_count];
|
|
rectangle_middle_right.x_pixels = cutout_right;
|
|
rectangle_middle_right.y_pixels = middle_top;
|
|
rectangle_middle_right.width_pixels = rectangle_right - cutout_right;
|
|
rectangle_middle_right.height_pixels = middle_height;
|
|
}
|
|
++rectangle_count;
|
|
}
|
|
// Lower part after cutout.
|
|
if (cutout_bottom < rectangle_bottom) {
|
|
assert_true(cutout_bottom > rectangle.y_pixels);
|
|
if (rectangles_out) {
|
|
Rectangle& rectangle_upper = rectangles_out[rectangle_count];
|
|
rectangle_upper.x_pixels = rectangle.x_pixels;
|
|
rectangle_upper.y_pixels = cutout_bottom;
|
|
rectangle_upper.width_pixels = rectangle.width_pixels;
|
|
rectangle_upper.height_pixels = rectangle_bottom - cutout_bottom;
|
|
}
|
|
++rectangle_count;
|
|
}
|
|
assert_true(rectangle_count <= kMaxCutoutBorderRectangles);
|
|
return rectangle_count;
|
|
}
|
|
|
|
RenderTargetCache::~RenderTargetCache() { ShutdownCommon(); }
|
|
|
|
void RenderTargetCache::InitializeCommon() {
|
|
assert_true(ownership_ranges_.empty());
|
|
ownership_ranges_.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(uint32_t(0)),
|
|
std::forward_as_tuple(xenos::kEdramTileCount, RenderTargetKey(),
|
|
RenderTargetKey(), RenderTargetKey()));
|
|
}
|
|
|
|
void RenderTargetCache::DestroyAllRenderTargets(bool shutting_down) {
|
|
ownership_ranges_.clear();
|
|
if (!shutting_down) {
|
|
ownership_ranges_.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(uint32_t(0)),
|
|
std::forward_as_tuple(xenos::kEdramTileCount, RenderTargetKey(),
|
|
RenderTargetKey(), RenderTargetKey()));
|
|
}
|
|
|
|
for (const auto& render_target_pair : render_targets_) {
|
|
if (render_target_pair.second) {
|
|
delete render_target_pair.second;
|
|
}
|
|
}
|
|
render_targets_.clear();
|
|
}
|
|
|
|
void RenderTargetCache::ShutdownCommon() { DestroyAllRenderTargets(true); }
|
|
|
|
void RenderTargetCache::ClearCache() {
|
|
// Keep only render targets currently owning any EDRAM data.
|
|
if (!render_targets_.empty()) {
|
|
std::unordered_set<RenderTargetKey, RenderTargetKey::Hasher>
|
|
used_render_targets;
|
|
for (const auto& ownership_range_pair : ownership_ranges_) {
|
|
const OwnershipRange& ownership_range = ownership_range_pair.second;
|
|
if (!ownership_range.render_target.IsEmpty()) {
|
|
used_render_targets.emplace(ownership_range.render_target);
|
|
}
|
|
if (!ownership_range.host_depth_render_target_unorm24.IsEmpty()) {
|
|
used_render_targets.emplace(
|
|
ownership_range.host_depth_render_target_unorm24);
|
|
}
|
|
if (!ownership_range.host_depth_render_target_float24.IsEmpty()) {
|
|
used_render_targets.emplace(
|
|
ownership_range.host_depth_render_target_float24);
|
|
}
|
|
}
|
|
if (render_targets_.size() != used_render_targets.size()) {
|
|
typename decltype(render_targets_)::iterator it_next;
|
|
for (auto it = render_targets_.begin(); it != render_targets_.end();
|
|
it = it_next) {
|
|
it_next = std::next(it);
|
|
if (!it->second) {
|
|
render_targets_.erase(it);
|
|
continue;
|
|
}
|
|
if (used_render_targets.find(it->second->key()) ==
|
|
used_render_targets.end()) {
|
|
delete it->second;
|
|
render_targets_.erase(it);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderTargetCache::BeginFrame() { ResetAccumulatedRenderTargets(); }
|
|
|
|
bool RenderTargetCache::Update(bool is_rasterization_done,
|
|
reg::RB_DEPTHCONTROL normalized_depth_control,
|
|
uint32_t normalized_color_mask,
|
|
const Shader& vertex_shader) {
|
|
const RegisterFile& regs = register_file();
|
|
bool interlock_barrier_only = GetPath() == Path::kPixelShaderInterlock;
|
|
|
|
auto rb_surface_info = regs.Get<reg::RB_SURFACE_INFO>();
|
|
xenos::MsaaSamples msaa_samples = rb_surface_info.msaa_samples;
|
|
assert_true(msaa_samples <= xenos::MsaaSamples::k4X);
|
|
if (msaa_samples > xenos::MsaaSamples::k4X) {
|
|
// Safety check because a lot of code assumes up to 4x.
|
|
assert_always();
|
|
XELOGE("{}x MSAA requested by the guest, Xenos only supports up to 4x",
|
|
uint32_t(1) << uint32_t(msaa_samples));
|
|
return false;
|
|
}
|
|
uint32_t msaa_samples_x_log2 =
|
|
uint32_t(msaa_samples >= xenos::MsaaSamples::k4X);
|
|
uint32_t pitch_pixels = rb_surface_info.surface_pitch;
|
|
// surface_pitch 0 should be handled in disabling rasterization (hopefully
|
|
// it's safe to assume that).
|
|
assert_true(pitch_pixels || !is_rasterization_done);
|
|
if (!pitch_pixels) {
|
|
is_rasterization_done = false;
|
|
} else if (pitch_pixels > xenos::kTexture2DCubeMaxWidthHeight) {
|
|
XELOGE(
|
|
"Surface pitch {} larger than the maximum texture width {} specified "
|
|
"by the guest",
|
|
pitch_pixels, xenos::kTexture2DCubeMaxWidthHeight);
|
|
return false;
|
|
}
|
|
uint32_t pitch_tiles_at_32bpp = ((pitch_pixels << msaa_samples_x_log2) +
|
|
(xenos::kEdramTileWidthSamples - 1)) /
|
|
xenos::kEdramTileWidthSamples;
|
|
if (!interlock_barrier_only) {
|
|
uint32_t pitch_pixels_tile_aligned_scaled =
|
|
pitch_tiles_at_32bpp *
|
|
(xenos::kEdramTileWidthSamples >> msaa_samples_x_log2) *
|
|
draw_resolution_scale_x();
|
|
uint32_t max_render_target_width = GetMaxRenderTargetWidth();
|
|
if (pitch_pixels_tile_aligned_scaled > max_render_target_width) {
|
|
// TODO(Triang3l): If really needed for some game on some device, clamp
|
|
// the pitch and generate multiple ranges (each for every row of tiles)
|
|
// with gaps for padding. Very few PowerVR GPUs have 4096, not 8192, as
|
|
// the limit, though with 8192 (on Mali) the actual limit for Xenia is
|
|
// 8160 because tile padding is stored - but 8192 should be extremely rare
|
|
// anyway.
|
|
XELOGE(
|
|
"Surface pitch aligned to EDRAM tiles and resolution-scaled {} "
|
|
"larger than the maximum host render target width {}",
|
|
pitch_pixels_tile_aligned_scaled, max_render_target_width);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get used render targets.
|
|
// [0] is depth / stencil where relevant, [1...4] is color.
|
|
// Depth / stencil testing / writing is before color in the pipeline.
|
|
uint32_t depth_and_color_rts_used_bits = 0;
|
|
// depth_and_color_rts_used_bits -> EDRAM base.
|
|
uint32_t edram_bases[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t resource_formats[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t rts_are_64bpp = 0;
|
|
uint32_t color_rts_are_gamma = 0;
|
|
if (is_rasterization_done) {
|
|
if (normalized_depth_control.z_enable ||
|
|
normalized_depth_control.stencil_enable) {
|
|
depth_and_color_rts_used_bits |= 1;
|
|
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
|
// std::min for safety, to avoid negative numbers in case it's completely
|
|
// wrong.
|
|
edram_bases[0] =
|
|
std::min(uint32_t(rb_depth_info.depth_base), xenos::kEdramTileCount);
|
|
// With pixel shader interlock, always the same addressing disregarding
|
|
// the format.
|
|
resource_formats[0] =
|
|
interlock_barrier_only ? 0 : uint32_t(rb_depth_info.depth_format);
|
|
}
|
|
for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) {
|
|
if (!(normalized_color_mask & (uint32_t(0b1111) << (4 * i)))) {
|
|
continue;
|
|
}
|
|
auto color_info = regs.Get<reg::RB_COLOR_INFO>(
|
|
reg::RB_COLOR_INFO::rt_register_indices[i]);
|
|
uint32_t rt_bit_index = 1 + i;
|
|
depth_and_color_rts_used_bits |= uint32_t(1) << rt_bit_index;
|
|
edram_bases[rt_bit_index] =
|
|
std::min(uint32_t(color_info.color_base), xenos::kEdramTileCount);
|
|
xenos::ColorRenderTargetFormat color_format =
|
|
regs.Get<reg::RB_COLOR_INFO>(
|
|
reg::RB_COLOR_INFO::rt_register_indices[i])
|
|
.color_format;
|
|
bool is_64bpp = xenos::IsColorRenderTargetFormat64bpp(color_format);
|
|
if (is_64bpp) {
|
|
rts_are_64bpp |= uint32_t(1) << rt_bit_index;
|
|
}
|
|
if (color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) {
|
|
color_rts_are_gamma |= uint32_t(1) << i;
|
|
}
|
|
xenos::ColorRenderTargetFormat color_resource_format;
|
|
if (interlock_barrier_only) {
|
|
// Only changes in mapping between coordinates and addresses are
|
|
// interesting (along with access overlap between draw calls), thus only
|
|
// pixel size is relevant.
|
|
color_resource_format =
|
|
is_64bpp ? xenos::ColorRenderTargetFormat::k_16_16_16_16
|
|
: xenos::ColorRenderTargetFormat::k_8_8_8_8;
|
|
} else {
|
|
color_resource_format =
|
|
GetColorResourceFormat(xenos::GetStorageColorFormat(color_format));
|
|
}
|
|
resource_formats[rt_bit_index] = uint32_t(color_resource_format);
|
|
}
|
|
}
|
|
|
|
uint32_t rts_remaining;
|
|
uint32_t rt_index;
|
|
|
|
// Eliminate other bound render targets if their EDRAM base conflicts with
|
|
// another render target - it's an error in most host implementations to bind
|
|
// the same render target into multiple slots, also the behavior would be
|
|
// unpredictable if that happens.
|
|
// Depth is considered the least important as it's earlier in the pipeline
|
|
// (issues caused by color and depth render target collisions haven't been
|
|
// found yet), but render targets with smaller index are considered more
|
|
// important - specifically, because of the usage in the lighting pass of
|
|
// 4D5307E6, which can be checked in the vertical look calibration sequence in
|
|
// the beginning of the game: if render target 0 is removed in favor of 1, the
|
|
// characters and the world will be too dark, like fully in shadow -
|
|
// especially prominent on the helmet. This happens because the shader picks
|
|
// between two render targets to write dynamically (though with a static, bool
|
|
// constant condition), but all other state is set up in a way that implies
|
|
// the same render target being bound twice. On Direct3D 9, if you don't write
|
|
// to a color pixel shader output on the control flow that was taken, the
|
|
// render target will not be written to. However, this has been relaxed in
|
|
// Direct3D 10, where if the shader declares an output, it's assumed to be
|
|
// always written (or with an undefined value otherwise).
|
|
rts_remaining = depth_and_color_rts_used_bits & ~(uint32_t(1));
|
|
while (xe::bit_scan_forward(rts_remaining, &rt_index)) {
|
|
rts_remaining &= ~(uint32_t(1) << rt_index);
|
|
uint32_t edram_base = edram_bases[rt_index];
|
|
uint32_t rts_other_remaining =
|
|
depth_and_color_rts_used_bits &
|
|
(~((uint32_t(1) << (rt_index + 1)) - 1) | uint32_t(1));
|
|
uint32_t rt_other_index;
|
|
while (xe::bit_scan_forward(rts_other_remaining, &rt_other_index)) {
|
|
rts_other_remaining &= ~(uint32_t(1) << rt_other_index);
|
|
if (edram_bases[rt_other_index] == edram_base) {
|
|
depth_and_color_rts_used_bits &= ~(uint32_t(1) << rt_other_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear ownership transfers before adding any.
|
|
if (!interlock_barrier_only) {
|
|
for (size_t i = 0; i < xe::countof(last_update_transfers_); ++i) {
|
|
last_update_transfers_[i].clear();
|
|
}
|
|
}
|
|
|
|
if (!depth_and_color_rts_used_bits) {
|
|
// Nothing to bind, don't waste time on things like memexport-only draws -
|
|
// just check if old bindings can still be used.
|
|
std::memset(last_update_used_render_targets_, 0,
|
|
sizeof(last_update_used_render_targets_));
|
|
if (are_accumulated_render_targets_valid_) {
|
|
for (size_t i = 0;
|
|
i < xe::countof(last_update_accumulated_render_targets_); ++i) {
|
|
const RenderTarget* render_target =
|
|
last_update_accumulated_render_targets_[i];
|
|
if (!render_target) {
|
|
continue;
|
|
}
|
|
RenderTargetKey rt_key = render_target->key();
|
|
if (rt_key.pitch_tiles_at_32bpp != pitch_tiles_at_32bpp ||
|
|
rt_key.msaa_samples != msaa_samples) {
|
|
are_accumulated_render_targets_valid_ = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!are_accumulated_render_targets_valid_) {
|
|
std::memset(last_update_accumulated_render_targets_, 0,
|
|
sizeof(last_update_accumulated_render_targets_));
|
|
last_update_accumulated_color_targets_are_gamma_ = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Estimate height used by render targets (for color for writes, for depth /
|
|
// stencil for both reads and writes) from various sources.
|
|
uint32_t height_used = std::min(
|
|
GetRenderTargetHeight(pitch_tiles_at_32bpp, msaa_samples),
|
|
draw_extent_estimator_.EstimateMaxY(
|
|
interlock_barrier_only
|
|
? cvars::execute_unclipped_draw_vs_on_cpu_for_psi_render_backend
|
|
: true,
|
|
vertex_shader));
|
|
|
|
// Sorted by EDRAM base and then by index in the pipeline - for simplicity,
|
|
// treat render targets placed closer to the end of the EDRAM as truncating
|
|
// the previous one (and in case multiple render targets are placed at the
|
|
// same EDRAM base, though normally this shouldn't happen, treat the color
|
|
// ones as more important than the depth one, which may be not needed and just
|
|
// a leftover if the draw, for instance, has depth / stencil happening to be
|
|
// always passing and never writing with the current state, and also because
|
|
// depth testing has to happen before the color is written). Overall it's
|
|
// normal for estimated EDRAM ranges of render targets to intersect if drawing
|
|
// without a viewport (as there's nothing to clamp the estimated height) and
|
|
// multiple render targets are bound.
|
|
std::pair<uint32_t, uint32_t>
|
|
edram_bases_sorted[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t edram_bases_sorted_count = 0;
|
|
rts_remaining = depth_and_color_rts_used_bits;
|
|
while (xe::bit_scan_forward(rts_remaining, &rt_index)) {
|
|
rts_remaining &= ~(uint32_t(1) << rt_index);
|
|
edram_bases_sorted[edram_bases_sorted_count++] =
|
|
std::make_pair(edram_bases[rt_index], rt_index);
|
|
}
|
|
std::sort(edram_bases_sorted, edram_bases_sorted + edram_bases_sorted_count);
|
|
// "As if it was 64bpp" (contribution of 32bpp render targets multiplied by 2,
|
|
// and clamping for 32bpp render targets divides this by 2) because 32bpp
|
|
// render targets can be combined with twice as long 64bpp render targets. An
|
|
// example is the 4541099D menu background (1-sample 1152x720, or 1200x720
|
|
// after rounding to tiles, with a 32bpp depth buffer at 0 requiring 675
|
|
// tiles, and a 64bpp color buffer at 675 requiring 1350 tiles, but the
|
|
// smallest distance between two render target bases is 675 tiles).
|
|
uint32_t rt_max_distance_tiles_at_64bpp = xenos::kEdramTileCount * 2;
|
|
if (cvars::mrt_edram_used_range_clamp_to_min) {
|
|
for (uint32_t i = 1; i < edram_bases_sorted_count; ++i) {
|
|
const std::pair<uint32_t, uint32_t>& rt_base_prev =
|
|
edram_bases_sorted[i - 1];
|
|
rt_max_distance_tiles_at_64bpp =
|
|
std::min(rt_max_distance_tiles_at_64bpp,
|
|
(edram_bases_sorted[i].first - rt_base_prev.first)
|
|
<< (((rts_are_64bpp >> rt_base_prev.second) & 1) ^ 1));
|
|
}
|
|
}
|
|
|
|
// Make sure all the needed render targets are created, and gather lengths of
|
|
// ranges used by each render target.
|
|
RenderTargetKey rt_keys[1 + xenos::kMaxColorRenderTargets];
|
|
RenderTarget* rts[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t rt_lengths_tiles[1 + xenos::kMaxColorRenderTargets];
|
|
uint32_t length_used_tiles_at_32bpp =
|
|
((height_used << uint32_t(msaa_samples >= xenos::MsaaSamples::k2X)) +
|
|
(xenos::kEdramTileHeightSamples - 1)) /
|
|
xenos::kEdramTileHeightSamples * pitch_tiles_at_32bpp;
|
|
for (uint32_t i = 0; i < edram_bases_sorted_count; ++i) {
|
|
const std::pair<uint32_t, uint32_t>& rt_base_index = edram_bases_sorted[i];
|
|
uint32_t rt_base = rt_base_index.first;
|
|
uint32_t rt_bit_index = rt_base_index.second;
|
|
RenderTargetKey& rt_key = rt_keys[rt_bit_index];
|
|
rt_key.base_tiles = rt_base;
|
|
rt_key.pitch_tiles_at_32bpp = pitch_tiles_at_32bpp;
|
|
rt_key.msaa_samples = msaa_samples;
|
|
rt_key.is_depth = rt_bit_index == 0;
|
|
rt_key.resource_format = resource_formats[rt_bit_index];
|
|
if (!interlock_barrier_only) {
|
|
RenderTarget* render_target = GetOrCreateRenderTarget(rt_key);
|
|
if (!render_target) {
|
|
return false;
|
|
}
|
|
rts[rt_bit_index] = render_target;
|
|
}
|
|
uint32_t rt_is_64bpp = (rts_are_64bpp >> rt_bit_index) & 1;
|
|
rt_lengths_tiles[i] = std::min(
|
|
std::min(length_used_tiles_at_32bpp << rt_is_64bpp,
|
|
rt_max_distance_tiles_at_64bpp >> (rt_is_64bpp ^ 1)),
|
|
((i + 1 < edram_bases_sorted_count) ? edram_bases_sorted[i + 1].first
|
|
: xenos::kEdramTileCount) -
|
|
rt_base);
|
|
}
|
|
|
|
if (interlock_barrier_only) {
|
|
// Because a full pixel shader interlock barrier may clear the ownership map
|
|
// (since it flushes all previous writes, and there's no need for another
|
|
// barrier if an overlap is encountered later between pre-barrier and
|
|
// post-barrier usages), check if any overlap requiring a barrier happens,
|
|
// and then insert the barrier if needed.
|
|
bool interlock_barrier_needed = false;
|
|
for (uint32_t i = 0; i < edram_bases_sorted_count; ++i) {
|
|
const std::pair<uint32_t, uint32_t>& rt_base_index =
|
|
edram_bases_sorted[i];
|
|
if (WouldOwnershipChangeRequireTransfers(rt_keys[rt_base_index.second],
|
|
rt_base_index.first,
|
|
rt_lengths_tiles[i])) {
|
|
interlock_barrier_needed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (interlock_barrier_needed) {
|
|
RequestPixelShaderInterlockBarrier();
|
|
}
|
|
}
|
|
|
|
// From now on ownership transfers should succeed for simplicity and
|
|
// consistency, even if they fail in the implementation (just ignore that and
|
|
// draw with whatever contents currently are in the render target in this
|
|
// case).
|
|
|
|
for (uint32_t i = 0; i < edram_bases_sorted_count; ++i) {
|
|
const std::pair<uint32_t, uint32_t>& rt_base_index = edram_bases_sorted[i];
|
|
uint32_t rt_bit_index = rt_base_index.second;
|
|
ChangeOwnership(
|
|
rt_keys[rt_bit_index], rt_base_index.first, rt_lengths_tiles[i],
|
|
interlock_barrier_only ? nullptr
|
|
: &last_update_transfers_[rt_bit_index]);
|
|
}
|
|
|
|
if (interlock_barrier_only) {
|
|
// No copying transfers or render target bindings - only needed the barrier.
|
|
return true;
|
|
}
|
|
|
|
// If everything succeeded, update the used render targets.
|
|
for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) {
|
|
last_update_used_render_targets_[i] =
|
|
(depth_and_color_rts_used_bits & (uint32_t(1) << i)) ? rts[i] : nullptr;
|
|
}
|
|
if (are_accumulated_render_targets_valid_) {
|
|
// Check if the only re-enabling a previously bound render target.
|
|
for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) {
|
|
RenderTarget* current_rt =
|
|
(depth_and_color_rts_used_bits & (uint32_t(1) << i)) ? rts[i]
|
|
: nullptr;
|
|
const RenderTarget* accumulated_rt =
|
|
last_update_accumulated_render_targets_[i];
|
|
if (!accumulated_rt) {
|
|
if (current_rt) {
|
|
// Binding a totally new render target - won't keep the existing
|
|
// render pass anyway, no much need to try to re-enable previously
|
|
// disabled render targets in other slots as well, even though that
|
|
// would be valid.
|
|
are_accumulated_render_targets_valid_ = false;
|
|
break;
|
|
}
|
|
// Append the new render target.
|
|
last_update_accumulated_render_targets_[i] = current_rt;
|
|
continue;
|
|
}
|
|
if (current_rt) {
|
|
if (current_rt != accumulated_rt) {
|
|
// Changing a render target in a slot.
|
|
are_accumulated_render_targets_valid_ = false;
|
|
break;
|
|
}
|
|
} else {
|
|
RenderTargetKey accumulated_rt_key = accumulated_rt->key();
|
|
if (accumulated_rt_key.pitch_tiles_at_32bpp != pitch_tiles_at_32bpp ||
|
|
accumulated_rt_key.msaa_samples != msaa_samples) {
|
|
// The previously bound render target is incompatible with the
|
|
// current surface info.
|
|
are_accumulated_render_targets_valid_ = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Make sure the same render target isn't bound into two different slots
|
|
// over time.
|
|
for (uint32_t i = 1; are_accumulated_render_targets_valid_ &&
|
|
i < 1 + xenos::kMaxColorRenderTargets;
|
|
++i) {
|
|
const RenderTarget* render_target =
|
|
last_update_accumulated_render_targets_[i];
|
|
if (!render_target) {
|
|
continue;
|
|
}
|
|
for (uint32_t j = 0; j < i; ++j) {
|
|
if (last_update_accumulated_render_targets_[j] == render_target) {
|
|
are_accumulated_render_targets_valid_ = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!are_accumulated_render_targets_valid_) {
|
|
std::memcpy(last_update_accumulated_render_targets_,
|
|
last_update_used_render_targets_,
|
|
sizeof(last_update_accumulated_render_targets_));
|
|
last_update_accumulated_color_targets_are_gamma_ = 0;
|
|
are_accumulated_render_targets_valid_ = true;
|
|
}
|
|
// Only update color space of render targets that actually matter here, don't
|
|
// disable gamma emulation (which may require ending the render pass) on the
|
|
// host, for example, if making a depth-only draw between color draws with a
|
|
// gamma target.
|
|
uint32_t color_rts_used_bits = depth_and_color_rts_used_bits >> 1;
|
|
// Ignore any render targets dropped before in this function for any reason.
|
|
color_rts_are_gamma &= color_rts_used_bits;
|
|
last_update_accumulated_color_targets_are_gamma_ =
|
|
(last_update_accumulated_color_targets_are_gamma_ &
|
|
~color_rts_used_bits) |
|
|
color_rts_are_gamma;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t RenderTargetCache::GetLastUpdateBoundRenderTargets(
|
|
bool distinguish_gamma_formats,
|
|
uint32_t* depth_and_color_formats_out) const {
|
|
if (GetPath() != Path::kHostRenderTargets) {
|
|
if (depth_and_color_formats_out) {
|
|
std::memset(depth_and_color_formats_out, 0,
|
|
sizeof(uint32_t) * (1 + xenos::kMaxColorRenderTargets));
|
|
}
|
|
return 0;
|
|
}
|
|
uint32_t rts_used = 0;
|
|
for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) {
|
|
const RenderTarget* render_target =
|
|
last_update_accumulated_render_targets_[i];
|
|
if (!render_target) {
|
|
if (depth_and_color_formats_out) {
|
|
depth_and_color_formats_out[i] = 0;
|
|
}
|
|
continue;
|
|
}
|
|
rts_used |= uint32_t(1) << i;
|
|
if (depth_and_color_formats_out) {
|
|
depth_and_color_formats_out[i] =
|
|
(distinguish_gamma_formats && i &&
|
|
(last_update_accumulated_color_targets_are_gamma_ &
|
|
(uint32_t(1) << (i - 1))))
|
|
? uint32_t(xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA)
|
|
: render_target->key().resource_format;
|
|
}
|
|
}
|
|
return rts_used;
|
|
}
|
|
|
|
uint32_t RenderTargetCache::GetRenderTargetHeight(
|
|
uint32_t pitch_tiles_at_32bpp, xenos::MsaaSamples msaa_samples) const {
|
|
if (!pitch_tiles_at_32bpp) {
|
|
return 0;
|
|
}
|
|
// Down to the end of EDRAM.
|
|
uint32_t tile_rows = (xenos::kEdramTileCount + (pitch_tiles_at_32bpp - 1)) /
|
|
pitch_tiles_at_32bpp;
|
|
// Clamp to the guest limit (tile padding should exceed it) and to the host
|
|
// limit (tile padding mustn't exceed it).
|
|
static_assert(
|
|
!(xenos::kTexture2DCubeMaxWidthHeight % xenos::kEdramTileHeightSamples),
|
|
"Maximum guest render target height is assumed to always be a multiple "
|
|
"of an EDRAM tile height");
|
|
uint32_t max_height_scaled =
|
|
std::min(xenos::kTexture2DCubeMaxWidthHeight * draw_resolution_scale_y(),
|
|
GetMaxRenderTargetHeight());
|
|
uint32_t msaa_samples_y_log2 =
|
|
uint32_t(msaa_samples >= xenos::MsaaSamples::k2X);
|
|
uint32_t tile_height_samples_scaled =
|
|
xenos::kEdramTileHeightSamples * draw_resolution_scale_y();
|
|
tile_rows = std::min(tile_rows, (max_height_scaled << msaa_samples_y_log2) /
|
|
tile_height_samples_scaled);
|
|
assert_not_zero(tile_rows);
|
|
return tile_rows * (xenos::kEdramTileHeightSamples >> msaa_samples_y_log2);
|
|
}
|
|
|
|
void RenderTargetCache::GetHostDepthStoreRectangleInfo(
|
|
const Transfer::Rectangle& transfer_rectangle,
|
|
xenos::MsaaSamples msaa_samples,
|
|
HostDepthStoreRectangleConstant& rectangle_constant_out,
|
|
uint32_t& group_count_x_out, uint32_t& group_count_y_out) const {
|
|
// Initialize to all bits zeroed.
|
|
HostDepthStoreRectangleConstant rectangle_constant;
|
|
// 8 pixels is the resolve granularity, both clearing and tile size are
|
|
// aligned to 8.
|
|
assert_zero(transfer_rectangle.x_pixels & 7);
|
|
assert_zero(transfer_rectangle.y_pixels & 7);
|
|
assert_zero(transfer_rectangle.width_pixels & 7);
|
|
assert_zero(transfer_rectangle.height_pixels & 7);
|
|
assert_not_zero(transfer_rectangle.width_pixels);
|
|
rectangle_constant.x_pixels_div_8 = transfer_rectangle.x_pixels >> 3;
|
|
rectangle_constant.y_pixels_div_8 = transfer_rectangle.y_pixels >> 3;
|
|
rectangle_constant.width_pixels_div_8_minus_1 =
|
|
(transfer_rectangle.width_pixels >> 3) - 1;
|
|
rectangle_constant_out = rectangle_constant;
|
|
// 1 thread group = 64x8 host samples.
|
|
uint32_t pixel_size_x = draw_resolution_scale_x()
|
|
<< uint32_t(msaa_samples >= xenos::MsaaSamples::k4X);
|
|
uint32_t pixel_size_y = draw_resolution_scale_y()
|
|
<< uint32_t(msaa_samples >= xenos::MsaaSamples::k2X);
|
|
group_count_x_out =
|
|
(transfer_rectangle.width_pixels * pixel_size_x + 63) >> 6;
|
|
group_count_y_out = (transfer_rectangle.height_pixels * pixel_size_y) >> 3;
|
|
}
|
|
|
|
void RenderTargetCache::GetResolveCopyRectanglesToDump(
|
|
uint32_t base, uint32_t row_length, uint32_t rows, uint32_t pitch,
|
|
std::vector<ResolveCopyDumpRectangle>& rectangles_out) const {
|
|
rectangles_out.clear();
|
|
assert_true(row_length <= pitch);
|
|
row_length = std::min(row_length, pitch);
|
|
if (!row_length || !rows) {
|
|
return;
|
|
}
|
|
uint32_t resolve_area_end = base + (rows - 1) * pitch + row_length;
|
|
// Collect render targets owning ranges within the specified rectangle. The
|
|
// first render target in the range may be before the lower_bound, only being
|
|
// in the range with its tail.
|
|
auto it = ownership_ranges_.lower_bound(base);
|
|
if (it != ownership_ranges_.cbegin()) {
|
|
auto it_pre = std::prev(it);
|
|
if (it_pre->second.end_tiles > base) {
|
|
it = it_pre;
|
|
}
|
|
}
|
|
for (; it != ownership_ranges_.cend(); ++it) {
|
|
uint32_t range_global_start = std::max(it->first, base);
|
|
if (range_global_start >= resolve_area_end) {
|
|
break;
|
|
}
|
|
RenderTargetKey rt_key = it->second.render_target;
|
|
if (rt_key.IsEmpty()) {
|
|
continue;
|
|
}
|
|
// Merge with other render target ranges with the same current ownership,
|
|
// but different depth ownership, since it's not relevant to resolving.
|
|
while (it != ownership_ranges_.cend()) {
|
|
auto it_next = std::next(it);
|
|
if (it_next == ownership_ranges_.cend() ||
|
|
it_next->first >= resolve_area_end ||
|
|
it_next->second.render_target != rt_key) {
|
|
break;
|
|
}
|
|
it = it_next;
|
|
}
|
|
|
|
uint32_t range_local_start = std::max(range_global_start, base) - base;
|
|
uint32_t range_local_end =
|
|
std::min(it->second.end_tiles, resolve_area_end) - base;
|
|
assert_true(range_local_start < range_local_end);
|
|
|
|
uint32_t rows_start = range_local_start / pitch;
|
|
uint32_t rows_end = (range_local_end + (pitch - 1)) / pitch;
|
|
uint32_t row_first_start = range_local_start - rows_start * pitch;
|
|
if (row_first_start >= row_length) {
|
|
// The first row starts within the pitch padding.
|
|
if (rows_start + 1 < rows_end) {
|
|
// Multiple rows - start at the second.
|
|
++rows_start;
|
|
row_first_start = 0;
|
|
} else {
|
|
// Single row - nothing to dump.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto it_rt = render_targets_.find(rt_key);
|
|
assert_true(it_rt != render_targets_.cend());
|
|
assert_not_null(it_rt->second);
|
|
// Don't include pitch padding in the last row.
|
|
rectangles_out.emplace_back(
|
|
it_rt->second, rows_start, rows_end - rows_start, row_first_start,
|
|
std::min(pitch - (rows_end * pitch - range_local_end), row_length));
|
|
}
|
|
}
|
|
|
|
bool RenderTargetCache::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) {
|
|
assert_true(GetPath() == Path::kHostRenderTargets);
|
|
|
|
uint32_t pitch_tiles_at_32bpp;
|
|
uint32_t base_offset_tiles_at_32bpp;
|
|
xenos::MsaaSamples msaa_samples;
|
|
if (resolve_info.IsClearingDepth()) {
|
|
pitch_tiles_at_32bpp = resolve_info.depth_edram_info.pitch_tiles;
|
|
base_offset_tiles_at_32bpp = resolve_info.depth_edram_info.base_tiles -
|
|
resolve_info.depth_original_base;
|
|
msaa_samples = resolve_info.depth_edram_info.msaa_samples;
|
|
} else if (resolve_info.IsClearingColor()) {
|
|
pitch_tiles_at_32bpp = resolve_info.color_edram_info.pitch_tiles;
|
|
base_offset_tiles_at_32bpp = resolve_info.color_edram_info.base_tiles -
|
|
resolve_info.color_original_base;
|
|
if (resolve_info.color_edram_info.format_is_64bpp) {
|
|
assert_zero(pitch_tiles_at_32bpp & 1);
|
|
pitch_tiles_at_32bpp >>= 1;
|
|
assert_zero(base_offset_tiles_at_32bpp & 1);
|
|
base_offset_tiles_at_32bpp >>= 1;
|
|
}
|
|
msaa_samples = resolve_info.color_edram_info.msaa_samples;
|
|
} else {
|
|
return false;
|
|
}
|
|
assert_true(msaa_samples <= xenos::MsaaSamples::k4X);
|
|
if (!pitch_tiles_at_32bpp) {
|
|
return false;
|
|
}
|
|
uint32_t msaa_samples_x_log2 =
|
|
uint32_t(msaa_samples >= xenos::MsaaSamples::k4X);
|
|
uint32_t msaa_samples_y_log2 =
|
|
uint32_t(msaa_samples >= xenos::MsaaSamples::k2X);
|
|
if (pitch_tiles_at_32bpp >
|
|
((xenos::kTexture2DCubeMaxWidthHeight << msaa_samples_x_log2) +
|
|
(xenos::kEdramTileWidthSamples - 1)) /
|
|
xenos::kEdramTileWidthSamples) {
|
|
XELOGE(
|
|
"Surface pitch in 80-sample groups {} at {}x MSAA larger than the "
|
|
"maximum texture width {} specified by the guest in a resolve",
|
|
pitch_tiles_at_32bpp, uint32_t(1) << uint32_t(msaa_samples),
|
|
xenos::kTexture2DCubeMaxWidthHeight);
|
|
return false;
|
|
}
|
|
uint32_t pitch_pixels =
|
|
pitch_tiles_at_32bpp *
|
|
(xenos::kEdramTileWidthSamples >> msaa_samples_x_log2);
|
|
uint32_t pitch_pixels_scaled = pitch_pixels * draw_resolution_scale_x();
|
|
uint32_t max_render_target_width = GetMaxRenderTargetWidth();
|
|
if (pitch_pixels_scaled > max_render_target_width) {
|
|
// TODO(Triang3l): If really needed for some game on some device, clamp the
|
|
// pitch the same way as explained in the comment in Update.
|
|
XELOGE(
|
|
"Surface pitch aligned to EDRAM tiles and resolution-scaled {} larger "
|
|
"than the maximum host render target width {} in a resolve",
|
|
pitch_pixels_scaled, max_render_target_width);
|
|
return false;
|
|
}
|
|
|
|
uint32_t render_target_height_pixels =
|
|
GetRenderTargetHeight(pitch_tiles_at_32bpp, msaa_samples);
|
|
uint32_t base_offset_rows_at_32bpp =
|
|
base_offset_tiles_at_32bpp / pitch_tiles_at_32bpp;
|
|
Transfer::Rectangle clear_rectangle;
|
|
clear_rectangle.x_pixels = std::min(
|
|
(base_offset_tiles_at_32bpp -
|
|
base_offset_rows_at_32bpp * pitch_tiles_at_32bpp) *
|
|
(xenos::kEdramTileWidthSamples >> msaa_samples_x_log2) +
|
|
(uint32_t(resolve_info.coordinate_info.edram_offset_x_div_8) << 3),
|
|
pitch_pixels);
|
|
clear_rectangle.y_pixels = std::min(
|
|
base_offset_rows_at_32bpp *
|
|
(xenos::kEdramTileHeightSamples >> msaa_samples_y_log2) +
|
|
(uint32_t(resolve_info.coordinate_info.edram_offset_y_div_8) << 3),
|
|
render_target_height_pixels);
|
|
clear_rectangle.width_pixels =
|
|
std::min(uint32_t(resolve_info.coordinate_info.width_div_8) << 3,
|
|
pitch_pixels - clear_rectangle.x_pixels);
|
|
clear_rectangle.height_pixels =
|
|
std::min(uint32_t(resolve_info.coordinate_info.height_div_8) << 3,
|
|
render_target_height_pixels - clear_rectangle.y_pixels);
|
|
if (!clear_rectangle.width_pixels || !clear_rectangle.height_pixels) {
|
|
// Outside the pitch / height (or initially specified as 0).
|
|
return false;
|
|
}
|
|
|
|
// Change ownership of the tiles containing the area to be cleared, so the
|
|
// up-to-date host render target for the cleared range will be the cleared
|
|
// one.
|
|
uint32_t clear_start_tiles_at_32bpp =
|
|
((clear_rectangle.y_pixels << msaa_samples_y_log2) /
|
|
xenos::kEdramTileHeightSamples) *
|
|
pitch_tiles_at_32bpp +
|
|
(clear_rectangle.x_pixels << msaa_samples_x_log2) /
|
|
xenos::kEdramTileWidthSamples;
|
|
uint32_t clear_length_tiles_at_32bpp =
|
|
(((clear_rectangle.y_pixels + clear_rectangle.height_pixels - 1)
|
|
<< msaa_samples_y_log2) /
|
|
xenos::kEdramTileHeightSamples) *
|
|
pitch_tiles_at_32bpp +
|
|
((clear_rectangle.x_pixels + clear_rectangle.width_pixels - 1)
|
|
<< msaa_samples_x_log2) /
|
|
xenos::kEdramTileWidthSamples +
|
|
1 - clear_start_tiles_at_32bpp;
|
|
uint32_t depth_clear_start_tiles =
|
|
resolve_info.IsClearingDepth()
|
|
? std::min(
|
|
resolve_info.depth_original_base + clear_start_tiles_at_32bpp,
|
|
xenos::kEdramTileCount)
|
|
: xenos::kEdramTileCount;
|
|
uint32_t color_clear_start_tiles =
|
|
resolve_info.IsClearingColor()
|
|
? std::min(resolve_info.color_original_base +
|
|
(clear_start_tiles_at_32bpp
|
|
<< resolve_info.color_edram_info.format_is_64bpp),
|
|
xenos::kEdramTileCount)
|
|
: xenos::kEdramTileCount;
|
|
uint32_t depth_clear_end_tiles =
|
|
std::min(depth_clear_start_tiles + clear_length_tiles_at_32bpp,
|
|
xenos::kEdramTileCount);
|
|
uint32_t color_clear_end_tiles =
|
|
std::min(color_clear_start_tiles +
|
|
(clear_length_tiles_at_32bpp
|
|
<< resolve_info.color_edram_info.format_is_64bpp),
|
|
xenos::kEdramTileCount);
|
|
// Prevent overlap.
|
|
if (depth_clear_start_tiles < color_clear_start_tiles) {
|
|
depth_clear_end_tiles =
|
|
std::min(depth_clear_end_tiles, color_clear_start_tiles);
|
|
} else {
|
|
color_clear_end_tiles =
|
|
std::min(color_clear_end_tiles, depth_clear_start_tiles);
|
|
}
|
|
|
|
RenderTargetKey depth_render_target_key;
|
|
RenderTarget* depth_render_target = nullptr;
|
|
if (depth_clear_start_tiles < depth_clear_end_tiles) {
|
|
depth_render_target_key.base_tiles = resolve_info.depth_original_base;
|
|
depth_render_target_key.pitch_tiles_at_32bpp = pitch_tiles_at_32bpp;
|
|
depth_render_target_key.msaa_samples = msaa_samples;
|
|
depth_render_target_key.is_depth = 1;
|
|
depth_render_target_key.resource_format =
|
|
resolve_info.depth_edram_info.format;
|
|
depth_render_target = GetOrCreateRenderTarget(depth_render_target_key);
|
|
if (!depth_render_target) {
|
|
depth_render_target_key = RenderTargetKey();
|
|
depth_clear_start_tiles = depth_clear_end_tiles;
|
|
}
|
|
}
|
|
RenderTargetKey color_render_target_key;
|
|
RenderTarget* color_render_target = nullptr;
|
|
if (color_clear_start_tiles < color_clear_end_tiles) {
|
|
color_render_target_key.base_tiles = resolve_info.color_original_base;
|
|
color_render_target_key.pitch_tiles_at_32bpp = pitch_tiles_at_32bpp;
|
|
color_render_target_key.msaa_samples = msaa_samples;
|
|
color_render_target_key.is_depth = 0;
|
|
color_render_target_key.resource_format = uint32_t(GetColorResourceFormat(
|
|
xenos::ColorRenderTargetFormat(resolve_info.color_edram_info.format)));
|
|
color_render_target = GetOrCreateRenderTarget(color_render_target_key);
|
|
if (!color_render_target) {
|
|
color_render_target_key = RenderTargetKey();
|
|
color_clear_start_tiles = color_clear_end_tiles;
|
|
}
|
|
}
|
|
if (depth_clear_start_tiles >= depth_clear_end_tiles &&
|
|
color_clear_start_tiles >= color_clear_end_tiles) {
|
|
// The region turned out to be outside the EDRAM, or there's complete
|
|
// overlap, shouldn't be happening. Or failed to create both render targets.
|
|
return false;
|
|
}
|
|
|
|
clear_rectangle_out = clear_rectangle;
|
|
depth_render_target_out = depth_render_target;
|
|
depth_transfers_out.clear();
|
|
if (depth_render_target) {
|
|
ChangeOwnership(depth_render_target_key, depth_clear_start_tiles,
|
|
depth_clear_end_tiles - depth_clear_start_tiles,
|
|
&depth_transfers_out, &clear_rectangle);
|
|
}
|
|
color_render_target_out = color_render_target;
|
|
color_transfers_out.clear();
|
|
if (color_render_target) {
|
|
ChangeOwnership(color_render_target_key, color_clear_start_tiles,
|
|
color_clear_end_tiles - color_clear_start_tiles,
|
|
&color_transfers_out, &clear_rectangle);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
RenderTargetCache::RenderTarget*
|
|
RenderTargetCache::PrepareFullEdram1280xRenderTargetForSnapshotRestoration(
|
|
xenos::ColorRenderTargetFormat color_format) {
|
|
assert_true(GetPath() == Path::kHostRenderTargets);
|
|
constexpr uint32_t kPitchTilesAt32bpp = 16;
|
|
constexpr uint32_t kWidth =
|
|
kPitchTilesAt32bpp * xenos::kEdramTileWidthSamples;
|
|
if (kWidth * draw_resolution_scale_x() > GetMaxRenderTargetWidth()) {
|
|
return nullptr;
|
|
}
|
|
// Same render target height is used for 32bpp and 64bpp to allow mixing them.
|
|
constexpr uint32_t kHeightTileRows =
|
|
xenos::kEdramTileCount / kPitchTilesAt32bpp;
|
|
static_assert(
|
|
kPitchTilesAt32bpp * kHeightTileRows == xenos::kEdramTileCount,
|
|
"Using width of the render target for EDRAM snapshot restoration that is "
|
|
"expected to result in the last row being fully utilized.");
|
|
constexpr uint32_t kHeight = kHeightTileRows * xenos::kEdramTileHeightSamples;
|
|
static_assert(
|
|
kHeight <= xenos::kTexture2DCubeMaxWidthHeight,
|
|
"Using width of the render target for EDRAM snapshot restoration that is "
|
|
"expect to fully cover the EDRAM without exceeding the maximum guest "
|
|
"render target height.");
|
|
if (kHeight * draw_resolution_scale_y() > GetMaxRenderTargetHeight()) {
|
|
return nullptr;
|
|
}
|
|
RenderTargetKey render_target_key;
|
|
render_target_key.pitch_tiles_at_32bpp = kPitchTilesAt32bpp;
|
|
render_target_key.resource_format =
|
|
uint32_t(GetColorResourceFormat(color_format));
|
|
RenderTarget* render_target = GetOrCreateRenderTarget(render_target_key);
|
|
if (!render_target) {
|
|
return nullptr;
|
|
}
|
|
// Change ownership, but don't transfer the contents - they will be replaced
|
|
// anyway.
|
|
ownership_ranges_.clear();
|
|
ownership_ranges_.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(uint32_t(0)),
|
|
std::forward_as_tuple(xenos::kEdramTileCount, render_target_key,
|
|
RenderTargetKey(), RenderTargetKey()));
|
|
return render_target;
|
|
}
|
|
|
|
void RenderTargetCache::PixelShaderInterlockFullEdramBarrierPlaced() {
|
|
assert_true(GetPath() == Path::kPixelShaderInterlock);
|
|
// Clear ownership - any overlap of data written before the barrier is safe.
|
|
OwnershipRange empty_range(xenos::kEdramTileCount, RenderTargetKey(),
|
|
RenderTargetKey(), RenderTargetKey());
|
|
if (ownership_ranges_.size() == 1) {
|
|
// Do not reallocate map elements if not needed (either nothing drawn since
|
|
// the last barrier, or all of the EDRAM is owned by one render target).
|
|
// The ownership map contains no gaps - the first element should always be
|
|
// at 0.
|
|
assert_true(!ownership_ranges_.begin()->first);
|
|
OwnershipRange& all_edram_range = ownership_ranges_.begin()->second;
|
|
assert_true(all_edram_range.end_tiles == xenos::kEdramTileCount);
|
|
all_edram_range = empty_range;
|
|
return;
|
|
}
|
|
ownership_ranges_.clear();
|
|
ownership_ranges_.emplace(0, empty_range);
|
|
}
|
|
|
|
RenderTargetCache::RenderTarget* RenderTargetCache::GetOrCreateRenderTarget(
|
|
RenderTargetKey key) {
|
|
assert_true(GetPath() == Path::kHostRenderTargets);
|
|
auto it_rt = render_targets_.find(key);
|
|
RenderTarget* render_target;
|
|
if (it_rt != render_targets_.end()) {
|
|
render_target = it_rt->second;
|
|
} else {
|
|
render_target = CreateRenderTarget(key);
|
|
uint32_t width = key.GetWidth();
|
|
uint32_t height =
|
|
GetRenderTargetHeight(key.pitch_tiles_at_32bpp, key.msaa_samples);
|
|
if (render_target) {
|
|
XELOGGPU(
|
|
"Created a {}x{} {}xMSAA {} render target with guest format {} at "
|
|
"EDRAM base {}",
|
|
width, height, uint32_t(1) << uint32_t(key.msaa_samples),
|
|
key.is_depth ? "depth" : "color", key.resource_format,
|
|
key.base_tiles);
|
|
} else {
|
|
XELOGE(
|
|
"Failed to create a {}x{} {}xMSAA {} render target with guest format "
|
|
"{} at EDRAM base {}",
|
|
width, height, uint32_t(1) << uint32_t(key.msaa_samples),
|
|
key.is_depth ? "depth" : "color", key.resource_format,
|
|
key.base_tiles);
|
|
}
|
|
// Insert even if failed to create, not to try to create again.
|
|
render_targets_.emplace(key, render_target);
|
|
}
|
|
return render_target;
|
|
}
|
|
|
|
bool RenderTargetCache::WouldOwnershipChangeRequireTransfers(
|
|
RenderTargetKey dest, uint32_t start_tiles, uint32_t length_tiles) const {
|
|
assert_true(start_tiles >= dest.base_tiles);
|
|
assert_true(length_tiles <= (xenos::kEdramTileCount - start_tiles));
|
|
if (length_tiles == 0) {
|
|
return false;
|
|
}
|
|
bool host_depth_encoding_different =
|
|
dest.is_depth && GetPath() == Path::kHostRenderTargets &&
|
|
IsHostDepthEncodingDifferent(dest.GetDepthFormat());
|
|
// The map contains consecutive ranges, merged if the adjacent ones are the
|
|
// same. Find the range starting at >= the start. A portion of the range
|
|
// preceding it may be intersecting the render target's range (or even fully
|
|
// contain it).
|
|
uint32_t end_tiles = start_tiles + length_tiles;
|
|
auto it = ownership_ranges_.lower_bound(start_tiles);
|
|
if (it != ownership_ranges_.begin()) {
|
|
auto it_pre = std::prev(it);
|
|
if (it_pre->second.end_tiles > start_tiles) {
|
|
it = it_pre;
|
|
}
|
|
}
|
|
for (; it != ownership_ranges_.end(); ++it) {
|
|
if (it->first >= end_tiles) {
|
|
// Outside the touched range already.
|
|
break;
|
|
}
|
|
if (it->second.IsOwnedBy(dest, host_depth_encoding_different)) {
|
|
// Already owned by the needed render target - no need to transfer
|
|
// anything.
|
|
continue;
|
|
}
|
|
RenderTargetKey transfer_source = it->second.render_target;
|
|
// Only perform the transfer when actually changing the latest owner, not
|
|
// just the latest host depth owner - the transfer source is expected to
|
|
// be different than the destination.
|
|
if (!transfer_source.IsEmpty() && transfer_source != dest) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RenderTargetCache::ChangeOwnership(
|
|
RenderTargetKey dest, uint32_t start_tiles, uint32_t length_tiles,
|
|
std::vector<Transfer>* transfers_append_out,
|
|
const Transfer::Rectangle* resolve_clear_cutout) {
|
|
assert_true(start_tiles >= dest.base_tiles);
|
|
assert_true(length_tiles <= (xenos::kEdramTileCount - start_tiles));
|
|
if (length_tiles == 0) {
|
|
return;
|
|
}
|
|
uint32_t dest_pitch_tiles = dest.GetPitchTiles();
|
|
bool dest_is_64bpp = dest.Is64bpp();
|
|
|
|
bool host_depth_encoding_different =
|
|
dest.is_depth && GetPath() == Path::kHostRenderTargets &&
|
|
IsHostDepthEncodingDifferent(dest.GetDepthFormat());
|
|
// The map contains consecutive ranges, merged if the adjacent ones are the
|
|
// same. Find the range starting at >= the start. A portion of the range
|
|
// preceding it may be intersecting the render target's range (or even fully
|
|
// contain it) - split it into the untouched head and the claimed tail if
|
|
// needed.
|
|
uint32_t end_tiles = start_tiles + length_tiles;
|
|
auto it = ownership_ranges_.lower_bound(start_tiles);
|
|
if (it != ownership_ranges_.begin()) {
|
|
auto it_pre = std::prev(it);
|
|
if (it_pre->second.end_tiles > start_tiles &&
|
|
!it_pre->second.IsOwnedBy(dest, host_depth_encoding_different)) {
|
|
// Different render target overlapping the range - split the head.
|
|
ownership_ranges_.emplace(start_tiles, it_pre->second);
|
|
it_pre->second.end_tiles = start_tiles;
|
|
// Let the next loop do the transfer and needed merging and splitting
|
|
// starting from the added tail.
|
|
it = std::next(it_pre);
|
|
}
|
|
}
|
|
while (it != ownership_ranges_.end()) {
|
|
if (it->first >= end_tiles) {
|
|
// Outside the touched range already.
|
|
break;
|
|
}
|
|
if (it->second.IsOwnedBy(dest, host_depth_encoding_different)) {
|
|
// Already owned by the needed render target - no need to transfer
|
|
// anything.
|
|
++it;
|
|
continue;
|
|
}
|
|
// Take over the current range. Handle the tail - may be outside the range
|
|
// (split in this case) or within it.
|
|
if (it->second.end_tiles > end_tiles) {
|
|
// Split the tail.
|
|
ownership_ranges_.emplace(end_tiles, it->second);
|
|
it->second.end_tiles = end_tiles;
|
|
}
|
|
if (transfers_append_out) {
|
|
RenderTargetKey transfer_source = it->second.render_target;
|
|
// Only perform the copying when actually changing the latest owner, not
|
|
// just the latest host depth owner - the transfer source is expected to
|
|
// be different than the destination.
|
|
if (!transfer_source.IsEmpty() && transfer_source != dest) {
|
|
uint32_t transfer_end_tiles = std::min(it->second.end_tiles, end_tiles);
|
|
if (!resolve_clear_cutout ||
|
|
Transfer::GetRangeRectangles(it->first, transfer_end_tiles,
|
|
dest.base_tiles, dest_pitch_tiles,
|
|
dest.msaa_samples, dest_is_64bpp,
|
|
nullptr, resolve_clear_cutout)) {
|
|
RenderTargetKey transfer_host_depth_source =
|
|
host_depth_encoding_different
|
|
? it->second.GetHostDepthRenderTarget(dest.GetDepthFormat())
|
|
: RenderTargetKey();
|
|
if (transfer_host_depth_source == transfer_source) {
|
|
// Same render target, don't provide a separate host depth source.
|
|
transfer_host_depth_source = RenderTargetKey();
|
|
}
|
|
if (!transfers_append_out->empty() &&
|
|
transfers_append_out->back().end_tiles == it->first &&
|
|
transfers_append_out->back().source->key() == transfer_source &&
|
|
((transfers_append_out->back().host_depth_source == nullptr) ==
|
|
transfer_host_depth_source.IsEmpty()) &&
|
|
(transfer_host_depth_source.IsEmpty() ||
|
|
transfers_append_out->back().host_depth_source->key() ==
|
|
transfer_host_depth_source)) {
|
|
// Extend the last transfer if, for example, transferring color, but
|
|
// host depth is different.
|
|
transfers_append_out->back().end_tiles = transfer_end_tiles;
|
|
} else {
|
|
auto transfer_source_rt_it = render_targets_.find(transfer_source);
|
|
if (transfer_source_rt_it != render_targets_.end()) {
|
|
assert_not_null(transfer_source_rt_it->second);
|
|
auto transfer_host_depth_source_rt_it =
|
|
!transfer_host_depth_source.IsEmpty()
|
|
? render_targets_.find(transfer_host_depth_source)
|
|
: render_targets_.end();
|
|
if (transfer_host_depth_source.IsEmpty() ||
|
|
transfer_host_depth_source_rt_it != render_targets_.end()) {
|
|
assert_false(transfer_host_depth_source_rt_it !=
|
|
render_targets_.end() &&
|
|
!transfer_host_depth_source_rt_it->second);
|
|
transfers_append_out->emplace_back(
|
|
it->first, transfer_end_tiles,
|
|
transfer_source_rt_it->second,
|
|
transfer_host_depth_source_rt_it != render_targets_.end()
|
|
? transfer_host_depth_source_rt_it->second
|
|
: nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Claim the current range.
|
|
it->second.render_target = dest;
|
|
if (host_depth_encoding_different) {
|
|
it->second.GetHostDepthRenderTarget(dest.GetDepthFormat()) = dest;
|
|
}
|
|
// Check if can merge with the next range after claiming.
|
|
std::map<uint32_t, OwnershipRange>::iterator it_next;
|
|
if (it != ownership_ranges_.end()) {
|
|
it_next = std::next(it);
|
|
if (it_next != ownership_ranges_.end() &&
|
|
it_next->second.AreOwnersSame(it->second)) {
|
|
// Merge with the next range.
|
|
it->second.end_tiles = it_next->second.end_tiles;
|
|
auto it_after = std::next(it_next);
|
|
ownership_ranges_.erase(it_next);
|
|
it_next = it_after;
|
|
}
|
|
} else {
|
|
it_next = ownership_ranges_.end();
|
|
}
|
|
// Check if can merge with the previous range after claiming and merging
|
|
// with the next (thus obtaining the correct end pointer).
|
|
if (it != ownership_ranges_.begin()) {
|
|
auto it_prev = std::prev(it);
|
|
if (it_prev->second.AreOwnersSame(it->second)) {
|
|
it_prev->second.end_tiles = it->second.end_tiles;
|
|
ownership_ranges_.erase(it);
|
|
}
|
|
}
|
|
it = it_next;
|
|
}
|
|
}
|
|
|
|
} // namespace gpu
|
|
} // namespace xe
|