texture_cache: Implement rendering to 3D textures

This allows rendering to 3D textures with more than one slice.
Applications are allowed to render to more than one slice of a texture
using gl_Layer from a VTG shader.

This also requires reworking how 3D texture collisions are handled, for
now, this commit allows rendering to slices but not to miplevels. When a
render target attempts to write to a mipmap, we fallback to the previous
implementation (copying or flushing as needed).

- Fixes color correction 3D textures on UE4 games (rainbow effects).
- Allows Xenoblade games to render to 3D textures directly.
This commit is contained in:
ReinUsesLisp
2020-05-29 23:32:41 -03:00
parent 2293e8a11a
commit c95c254f3e
10 changed files with 196 additions and 144 deletions

View File

@ -248,12 +248,11 @@ void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager,
// Use an extra temporal buffer
auto& tmp_buffer = staging_cache.GetBuffer(1);
// Special case for 3D Texture Segments
const bool must_read_current_data =
params.block_depth > 0 && params.target == VideoCore::Surface::SurfaceTarget::Texture2D;
tmp_buffer.resize(guest_memory_size);
host_ptr = tmp_buffer.data();
if (must_read_current_data) {
if (params.target == SurfaceTarget::Texture3D) {
// Special case for 3D texture segments
memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size);
}

View File

@ -217,8 +217,8 @@ public:
}
bool IsProtected() const {
// Only 3D Slices are to be protected
return is_target && params.block_depth > 0;
// Only 3D slices are to be protected
return is_target && params.target == SurfaceTarget::Texture3D;
}
bool IsRenderTarget() const {
@ -250,6 +250,11 @@ public:
return GetView(ViewParams(overview_params.target, 0, num_layers, 0, params.num_levels));
}
TView Emplace3DView(u32 slice, u32 depth, u32 base_level, u32 num_levels) {
return GetView(ViewParams(VideoCore::Surface::SurfaceTarget::Texture3D, slice, depth,
base_level, num_levels));
}
std::optional<TView> EmplaceIrregularView(const SurfaceParams& view_params,
const GPUVAddr view_addr,
const std::size_t candidate_size, const u32 mipmap,
@ -272,8 +277,8 @@ public:
std::optional<TView> EmplaceView(const SurfaceParams& view_params, const GPUVAddr view_addr,
const std::size_t candidate_size) {
if (params.target == SurfaceTarget::Texture3D ||
(params.num_levels == 1 && !params.is_layered) ||
view_params.target == SurfaceTarget::Texture3D) {
view_params.target == SurfaceTarget::Texture3D ||
(params.num_levels == 1 && !params.is_layered)) {
return {};
}
const auto layer_mipmap{GetLayerMipmap(view_addr)};

View File

@ -215,10 +215,19 @@ SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::siz
params.num_levels = 1;
params.emulated_levels = 1;
const bool is_layered = config.layers > 1 && params.block_depth == 0;
params.is_layered = is_layered;
params.depth = is_layered ? config.layers.Value() : 1;
params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
if (config.memory_layout.is_3d != 0) {
params.depth = config.layers.Value();
params.is_layered = false;
params.target = SurfaceTarget::Texture3D;
} else if (config.layers > 1) {
params.depth = config.layers.Value();
params.is_layered = true;
params.target = SurfaceTarget::Texture2DArray;
} else {
params.depth = 1;
params.is_layered = false;
params.target = SurfaceTarget::Texture2D;
}
return params;
}

View File

@ -508,12 +508,12 @@ private:
return RecycleStrategy::Flush;
}
// 3D Textures decision
if (params.block_depth > 1 || params.target == SurfaceTarget::Texture3D) {
if (params.target == SurfaceTarget::Texture3D) {
return RecycleStrategy::Flush;
}
for (const auto& s : overlaps) {
const auto& s_params = s->GetSurfaceParams();
if (s_params.block_depth > 1 || s_params.target == SurfaceTarget::Texture3D) {
if (s_params.target == SurfaceTarget::Texture3D) {
return RecycleStrategy::Flush;
}
}
@ -726,76 +726,60 @@ private:
* @param params The parameters on the new surface.
* @param gpu_addr The starting address of the new surface.
* @param cpu_addr The starting address of the new surface on physical memory.
* @param preserve_contents Indicates that the new surface should be loaded from memory or
* left blank.
*/
std::optional<std::pair<TSurface, TView>> Manage3DSurfaces(VectorSurface& overlaps,
const SurfaceParams& params,
const GPUVAddr gpu_addr,
const VAddr cpu_addr,
bool preserve_contents) {
if (params.target == SurfaceTarget::Texture3D) {
bool failed = false;
if (params.num_levels > 1) {
// We can't handle mipmaps in 3D textures yet, better fallback to LLE approach
return std::nullopt;
}
TSurface new_surface = GetUncachedSurface(gpu_addr, params);
bool modified = false;
for (auto& surface : overlaps) {
const SurfaceParams& src_params = surface->GetSurfaceParams();
if (src_params.target != SurfaceTarget::Texture2D) {
failed = true;
break;
}
if (src_params.height != params.height) {
failed = true;
break;
}
if (src_params.block_depth != params.block_depth ||
src_params.block_height != params.block_height) {
failed = true;
break;
}
const u32 offset = static_cast<u32>(surface->GetCpuAddr() - cpu_addr);
const auto offsets = params.GetBlockOffsetXYZ(offset);
const auto z = std::get<2>(offsets);
modified |= surface->IsModified();
const CopyParams copy_params(0, 0, 0, 0, 0, z, 0, 0, params.width, params.height,
1);
ImageCopy(surface, new_surface, copy_params);
}
if (failed) {
return std::nullopt;
}
for (const auto& surface : overlaps) {
Unregister(surface);
}
new_surface->MarkAsModified(modified, Tick());
Register(new_surface);
auto view = new_surface->GetMainView();
return {{std::move(new_surface), view}};
} else {
for (const auto& surface : overlaps) {
if (!surface->MatchTarget(params.target)) {
if (overlaps.size() == 1 && surface->GetCpuAddr() == cpu_addr) {
if (Settings::IsGPULevelExtreme()) {
return std::nullopt;
}
Unregister(surface);
return InitializeSurface(gpu_addr, params, preserve_contents);
}
return std::nullopt;
}
if (surface->GetCpuAddr() != cpu_addr) {
continue;
}
if (surface->MatchesStructure(params) == MatchStructureResult::FullMatch) {
return {{surface, surface->GetMainView()}};
}
}
return InitializeSurface(gpu_addr, params, preserve_contents);
GPUVAddr gpu_addr, VAddr cpu_addr) {
if (params.num_levels > 1) {
// We can't handle mipmaps in 3D textures yet, better fallback to LLE approach
return std::nullopt;
}
if (overlaps.size() == 1) {
const auto& surface = overlaps[0];
const SurfaceParams& overlap_params = surface->GetSurfaceParams();
// Don't attempt to render to textures with more than one level for now
// The texture has to be to the right or the sample address if we want to render to it
if (overlap_params.num_levels == 1 && cpu_addr >= surface->GetCpuAddr()) {
const u32 offset = static_cast<u32>(cpu_addr - surface->GetCpuAddr());
const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset));
if (slice < overlap_params.depth) {
auto view = surface->Emplace3DView(slice, params.depth, 0, 1);
return std::make_pair(std::move(surface), std::move(view));
}
}
}
if (params.depth == 1) {
return std::nullopt;
}
TSurface new_surface = GetUncachedSurface(gpu_addr, params);
bool modified = false;
for (auto& surface : overlaps) {
const SurfaceParams& src_params = surface->GetSurfaceParams();
if (src_params.height != params.height ||
src_params.block_depth != params.block_depth ||
src_params.block_height != params.block_height) {
return std::nullopt;
}
modified |= surface->IsModified();
const u32 offset = static_cast<u32>(surface->GetCpuAddr() - cpu_addr);
const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset));
const u32 width = params.width;
const u32 height = params.height;
const CopyParams copy_params(0, 0, 0, 0, 0, slice, 0, 0, width, height, 1);
ImageCopy(surface, new_surface, copy_params);
}
for (const auto& surface : overlaps) {
Unregister(surface);
}
new_surface->MarkAsModified(modified, Tick());
Register(new_surface);
auto view = new_surface->GetMainView();
return std::make_pair(std::move(new_surface), std::move(view));
}
/**
@ -873,10 +857,9 @@ private:
}
}
// Check if it's a 3D texture
if (params.block_depth > 0) {
auto surface =
Manage3DSurfaces(overlaps, params, gpu_addr, cpu_addr, preserve_contents);
// Manage 3D textures
if (params.target == SurfaceTarget::Texture3D) {
auto surface = Manage3DSurfaces(overlaps, params, gpu_addr, cpu_addr);
if (surface) {
return *surface;
}