Browse Source
Merge pull request #2278 from ReinUsesLisp/vc-texture-cache
Merge pull request #2278 from ReinUsesLisp/vc-texture-cache
video_core: Implement API agnostic view based texture cachepull/15/merge
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 974 additions and 0 deletions
-
2src/video_core/CMakeLists.txt
-
386src/video_core/texture_cache.cpp
-
586src/video_core/texture_cache.h
@ -0,0 +1,386 @@ |
|||
// Copyright 2019 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/alignment.h"
|
|||
#include "common/assert.h"
|
|||
#include "common/cityhash.h"
|
|||
#include "common/common_types.h"
|
|||
#include "core/core.h"
|
|||
#include "video_core/surface.h"
|
|||
#include "video_core/texture_cache.h"
|
|||
#include "video_core/textures/decoders.h"
|
|||
#include "video_core/textures/texture.h"
|
|||
|
|||
namespace VideoCommon { |
|||
|
|||
using VideoCore::Surface::SurfaceTarget; |
|||
|
|||
using VideoCore::Surface::ComponentTypeFromDepthFormat; |
|||
using VideoCore::Surface::ComponentTypeFromRenderTarget; |
|||
using VideoCore::Surface::ComponentTypeFromTexture; |
|||
using VideoCore::Surface::PixelFormatFromDepthFormat; |
|||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat; |
|||
using VideoCore::Surface::PixelFormatFromTextureFormat; |
|||
using VideoCore::Surface::SurfaceTargetFromTextureType; |
|||
|
|||
constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { |
|||
return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); |
|||
} |
|||
|
|||
SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, |
|||
const Tegra::Texture::FullTextureInfo& config) { |
|||
SurfaceParams params; |
|||
params.is_tiled = config.tic.IsTiled(); |
|||
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, |
|||
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, |
|||
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, |
|||
params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; |
|||
params.pixel_format = |
|||
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false); |
|||
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); |
|||
params.type = GetFormatType(params.pixel_format); |
|||
params.target = SurfaceTargetFromTextureType(config.tic.texture_type); |
|||
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); |
|||
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); |
|||
params.depth = config.tic.Depth(); |
|||
if (params.target == SurfaceTarget::TextureCubemap || |
|||
params.target == SurfaceTarget::TextureCubeArray) { |
|||
params.depth *= 6; |
|||
} |
|||
params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); |
|||
params.unaligned_height = config.tic.Height(); |
|||
params.num_levels = config.tic.max_mip_level + 1; |
|||
|
|||
params.CalculateCachedValues(); |
|||
return params; |
|||
} |
|||
|
|||
SurfaceParams SurfaceParams::CreateForDepthBuffer( |
|||
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, |
|||
u32 block_width, u32 block_height, u32 block_depth, |
|||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { |
|||
SurfaceParams params; |
|||
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; |
|||
params.block_width = 1 << std::min(block_width, 5U); |
|||
params.block_height = 1 << std::min(block_height, 5U); |
|||
params.block_depth = 1 << std::min(block_depth, 5U); |
|||
params.tile_width_spacing = 1; |
|||
params.pixel_format = PixelFormatFromDepthFormat(format); |
|||
params.component_type = ComponentTypeFromDepthFormat(format); |
|||
params.type = GetFormatType(params.pixel_format); |
|||
params.width = zeta_width; |
|||
params.height = zeta_height; |
|||
params.unaligned_height = zeta_height; |
|||
params.target = SurfaceTarget::Texture2D; |
|||
params.depth = 1; |
|||
params.num_levels = 1; |
|||
|
|||
params.CalculateCachedValues(); |
|||
return params; |
|||
} |
|||
|
|||
SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { |
|||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; |
|||
SurfaceParams params; |
|||
params.is_tiled = |
|||
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; |
|||
params.block_width = 1 << config.memory_layout.block_width; |
|||
params.block_height = 1 << config.memory_layout.block_height; |
|||
params.block_depth = 1 << config.memory_layout.block_depth; |
|||
params.tile_width_spacing = 1; |
|||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); |
|||
params.component_type = ComponentTypeFromRenderTarget(config.format); |
|||
params.type = GetFormatType(params.pixel_format); |
|||
if (params.is_tiled) { |
|||
params.width = config.width; |
|||
} else { |
|||
const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; |
|||
params.pitch = config.width; |
|||
params.width = params.pitch / bpp; |
|||
} |
|||
params.height = config.height; |
|||
params.depth = 1; |
|||
params.unaligned_height = config.height; |
|||
params.target = SurfaceTarget::Texture2D; |
|||
params.num_levels = 1; |
|||
|
|||
params.CalculateCachedValues(); |
|||
return params; |
|||
} |
|||
|
|||
SurfaceParams SurfaceParams::CreateForFermiCopySurface( |
|||
const Tegra::Engines::Fermi2D::Regs::Surface& config) { |
|||
SurfaceParams params{}; |
|||
params.is_tiled = !config.linear; |
|||
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, |
|||
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, |
|||
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, |
|||
params.tile_width_spacing = 1; |
|||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); |
|||
params.component_type = ComponentTypeFromRenderTarget(config.format); |
|||
params.type = GetFormatType(params.pixel_format); |
|||
params.width = config.width; |
|||
params.height = config.height; |
|||
params.unaligned_height = config.height; |
|||
// TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
|
|||
params.target = SurfaceTarget::Texture2D; |
|||
params.depth = 1; |
|||
params.num_levels = 1; |
|||
|
|||
params.CalculateCachedValues(); |
|||
return params; |
|||
} |
|||
|
|||
u32 SurfaceParams::GetMipWidth(u32 level) const { |
|||
return std::max(1U, width >> level); |
|||
} |
|||
|
|||
u32 SurfaceParams::GetMipHeight(u32 level) const { |
|||
return std::max(1U, height >> level); |
|||
} |
|||
|
|||
u32 SurfaceParams::GetMipDepth(u32 level) const { |
|||
return IsLayered() ? depth : std::max(1U, depth >> level); |
|||
} |
|||
|
|||
bool SurfaceParams::IsLayered() const { |
|||
switch (target) { |
|||
case SurfaceTarget::Texture1DArray: |
|||
case SurfaceTarget::Texture2DArray: |
|||
case SurfaceTarget::TextureCubeArray: |
|||
case SurfaceTarget::TextureCubemap: |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
u32 SurfaceParams::GetMipBlockHeight(u32 level) const { |
|||
// Auto block resizing algorithm from:
|
|||
// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
|
|||
if (level == 0) { |
|||
return block_height; |
|||
} |
|||
const u32 height{GetMipHeight(level)}; |
|||
const u32 default_block_height{GetDefaultBlockHeight(pixel_format)}; |
|||
const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height}; |
|||
u32 block_height = 16; |
|||
while (block_height > 1 && blocks_in_y <= block_height * 4) { |
|||
block_height >>= 1; |
|||
} |
|||
return block_height; |
|||
} |
|||
|
|||
u32 SurfaceParams::GetMipBlockDepth(u32 level) const { |
|||
if (level == 0) |
|||
return block_depth; |
|||
if (target != SurfaceTarget::Texture3D) |
|||
return 1; |
|||
|
|||
const u32 depth{GetMipDepth(level)}; |
|||
u32 block_depth = 32; |
|||
while (block_depth > 1 && depth * 2 <= block_depth) { |
|||
block_depth >>= 1; |
|||
} |
|||
if (block_depth == 32 && GetMipBlockHeight(level) >= 4) { |
|||
return 16; |
|||
} |
|||
return block_depth; |
|||
} |
|||
|
|||
std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { |
|||
std::size_t offset = 0; |
|||
for (u32 i = 0; i < level; i++) { |
|||
offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false); |
|||
} |
|||
return offset; |
|||
} |
|||
|
|||
std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { |
|||
std::size_t offset = 0; |
|||
for (u32 i = 0; i < level; i++) { |
|||
offset += GetInnerMipmapMemorySize(i, true, false, false); |
|||
} |
|||
return offset; |
|||
} |
|||
|
|||
std::size_t SurfaceParams::GetGuestLayerSize() const { |
|||
return GetInnerMemorySize(false, true, false); |
|||
} |
|||
|
|||
std::size_t SurfaceParams::GetHostLayerSize(u32 level) const { |
|||
return GetInnerMipmapMemorySize(level, true, IsLayered(), false); |
|||
} |
|||
|
|||
bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const { |
|||
if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) != |
|||
std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format, |
|||
view_params.component_type, view_params.type)) { |
|||
return false; |
|||
} |
|||
|
|||
const SurfaceTarget view_target{view_params.target}; |
|||
if (view_target == target) { |
|||
return true; |
|||
} |
|||
|
|||
switch (target) { |
|||
case SurfaceTarget::Texture1D: |
|||
case SurfaceTarget::Texture2D: |
|||
case SurfaceTarget::Texture3D: |
|||
return false; |
|||
case SurfaceTarget::Texture1DArray: |
|||
return view_target == SurfaceTarget::Texture1D; |
|||
case SurfaceTarget::Texture2DArray: |
|||
return view_target == SurfaceTarget::Texture2D; |
|||
case SurfaceTarget::TextureCubemap: |
|||
return view_target == SurfaceTarget::Texture2D || |
|||
view_target == SurfaceTarget::Texture2DArray; |
|||
case SurfaceTarget::TextureCubeArray: |
|||
return view_target == SurfaceTarget::Texture2D || |
|||
view_target == SurfaceTarget::Texture2DArray || |
|||
view_target == SurfaceTarget::TextureCubemap; |
|||
default: |
|||
UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target)); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
bool SurfaceParams::IsPixelFormatZeta() const { |
|||
return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && |
|||
pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; |
|||
} |
|||
|
|||
void SurfaceParams::CalculateCachedValues() { |
|||
guest_size_in_bytes = GetInnerMemorySize(false, false, false); |
|||
|
|||
// ASTC is uncompressed in software, in emulated as RGBA8
|
|||
if (IsPixelFormatASTC(pixel_format)) { |
|||
host_size_in_bytes = width * height * depth * 4; |
|||
} else { |
|||
host_size_in_bytes = GetInnerMemorySize(true, false, false); |
|||
} |
|||
|
|||
switch (target) { |
|||
case SurfaceTarget::Texture1D: |
|||
case SurfaceTarget::Texture2D: |
|||
case SurfaceTarget::Texture3D: |
|||
num_layers = 1; |
|||
break; |
|||
case SurfaceTarget::Texture1DArray: |
|||
case SurfaceTarget::Texture2DArray: |
|||
case SurfaceTarget::TextureCubemap: |
|||
case SurfaceTarget::TextureCubeArray: |
|||
num_layers = depth; |
|||
break; |
|||
default: |
|||
UNREACHABLE(); |
|||
} |
|||
} |
|||
|
|||
std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, |
|||
bool uncompressed) const { |
|||
const bool tiled{as_host_size ? false : is_tiled}; |
|||
const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; |
|||
const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; |
|||
const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)}; |
|||
const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)}; |
|||
const u32 depth{layer_only ? 1U : GetMipDepth(level)}; |
|||
return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height, |
|||
depth, GetMipBlockHeight(level), GetMipBlockDepth(level)); |
|||
} |
|||
|
|||
std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only, |
|||
bool uncompressed) const { |
|||
std::size_t size = 0; |
|||
for (u32 level = 0; level < num_levels; ++level) { |
|||
size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed); |
|||
} |
|||
if (!as_host_size && is_tiled) { |
|||
size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth); |
|||
} |
|||
return size; |
|||
} |
|||
|
|||
std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const { |
|||
std::map<u64, std::pair<u32, u32>> view_offset_map; |
|||
switch (target) { |
|||
case SurfaceTarget::Texture1D: |
|||
case SurfaceTarget::Texture2D: |
|||
case SurfaceTarget::Texture3D: { |
|||
constexpr u32 layer = 0; |
|||
for (u32 level = 0; level < num_levels; ++level) { |
|||
const std::size_t offset{GetGuestMipmapLevelOffset(level)}; |
|||
view_offset_map.insert({offset, {layer, level}}); |
|||
} |
|||
break; |
|||
} |
|||
case SurfaceTarget::Texture1DArray: |
|||
case SurfaceTarget::Texture2DArray: |
|||
case SurfaceTarget::TextureCubemap: |
|||
case SurfaceTarget::TextureCubeArray: { |
|||
const std::size_t layer_size{GetGuestLayerSize()}; |
|||
for (u32 level = 0; level < num_levels; ++level) { |
|||
const std::size_t level_offset{GetGuestMipmapLevelOffset(level)}; |
|||
for (u32 layer = 0; layer < num_layers; ++layer) { |
|||
const auto layer_offset{static_cast<std::size_t>(layer_size * layer)}; |
|||
const std::size_t offset{level_offset + layer_offset}; |
|||
view_offset_map.insert({offset, {layer, level}}); |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
default: |
|||
UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target)); |
|||
} |
|||
return view_offset_map; |
|||
} |
|||
|
|||
bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const { |
|||
return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) && |
|||
IsInBounds(view_params, layer, level); |
|||
} |
|||
|
|||
bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const { |
|||
return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level); |
|||
} |
|||
|
|||
bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const { |
|||
if (view_params.target != SurfaceTarget::Texture3D) { |
|||
return true; |
|||
} |
|||
return view_params.depth == GetMipDepth(level); |
|||
} |
|||
|
|||
bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const { |
|||
return layer + view_params.num_layers <= num_layers && |
|||
level + view_params.num_levels <= num_levels; |
|||
} |
|||
|
|||
std::size_t HasheableSurfaceParams::Hash() const { |
|||
return static_cast<std::size_t>( |
|||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); |
|||
} |
|||
|
|||
bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const { |
|||
return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, |
|||
height, depth, pitch, unaligned_height, num_levels, pixel_format, |
|||
component_type, type, target) == |
|||
std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, |
|||
rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, |
|||
rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type, |
|||
rhs.type, rhs.target); |
|||
} |
|||
|
|||
std::size_t ViewKey::Hash() const { |
|||
return static_cast<std::size_t>( |
|||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); |
|||
} |
|||
|
|||
bool ViewKey::operator==(const ViewKey& rhs) const { |
|||
return std::tie(base_layer, num_layers, base_level, num_levels) == |
|||
std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels); |
|||
} |
|||
|
|||
} // namespace VideoCommon
|
|||
@ -0,0 +1,586 @@ |
|||
// Copyright 2019 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <list> |
|||
#include <memory> |
|||
#include <set> |
|||
#include <tuple> |
|||
#include <type_traits> |
|||
#include <unordered_map> |
|||
|
|||
#include <boost/icl/interval_map.hpp> |
|||
#include <boost/range/iterator_range.hpp> |
|||
|
|||
#include "common/assert.h" |
|||
#include "common/common_types.h" |
|||
#include "core/memory.h" |
|||
#include "video_core/engines/fermi_2d.h" |
|||
#include "video_core/engines/maxwell_3d.h" |
|||
#include "video_core/gpu.h" |
|||
#include "video_core/rasterizer_interface.h" |
|||
#include "video_core/surface.h" |
|||
|
|||
namespace Core { |
|||
class System; |
|||
} |
|||
|
|||
namespace Tegra::Texture { |
|||
struct FullTextureInfo; |
|||
} |
|||
|
|||
namespace VideoCore { |
|||
class RasterizerInterface; |
|||
} |
|||
|
|||
namespace VideoCommon { |
|||
|
|||
class HasheableSurfaceParams { |
|||
public: |
|||
std::size_t Hash() const; |
|||
|
|||
bool operator==(const HasheableSurfaceParams& rhs) const; |
|||
|
|||
protected: |
|||
// Avoid creation outside of a managed environment. |
|||
HasheableSurfaceParams() = default; |
|||
|
|||
bool is_tiled; |
|||
u32 block_width; |
|||
u32 block_height; |
|||
u32 block_depth; |
|||
u32 tile_width_spacing; |
|||
u32 width; |
|||
u32 height; |
|||
u32 depth; |
|||
u32 pitch; |
|||
u32 unaligned_height; |
|||
u32 num_levels; |
|||
VideoCore::Surface::PixelFormat pixel_format; |
|||
VideoCore::Surface::ComponentType component_type; |
|||
VideoCore::Surface::SurfaceType type; |
|||
VideoCore::Surface::SurfaceTarget target; |
|||
}; |
|||
|
|||
class SurfaceParams final : public HasheableSurfaceParams { |
|||
public: |
|||
/// Creates SurfaceCachedParams from a texture configuration. |
|||
static SurfaceParams CreateForTexture(Core::System& system, |
|||
const Tegra::Texture::FullTextureInfo& config); |
|||
|
|||
/// Creates SurfaceCachedParams for a depth buffer configuration. |
|||
static SurfaceParams CreateForDepthBuffer( |
|||
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, |
|||
u32 block_width, u32 block_height, u32 block_depth, |
|||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); |
|||
|
|||
/// Creates SurfaceCachedParams from a framebuffer configuration. |
|||
static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); |
|||
|
|||
/// Creates SurfaceCachedParams from a Fermi2D surface configuration. |
|||
static SurfaceParams CreateForFermiCopySurface( |
|||
const Tegra::Engines::Fermi2D::Regs::Surface& config); |
|||
|
|||
bool IsTiled() const { |
|||
return is_tiled; |
|||
} |
|||
|
|||
u32 GetBlockWidth() const { |
|||
return block_width; |
|||
} |
|||
|
|||
u32 GetTileWidthSpacing() const { |
|||
return tile_width_spacing; |
|||
} |
|||
|
|||
u32 GetWidth() const { |
|||
return width; |
|||
} |
|||
|
|||
u32 GetHeight() const { |
|||
return height; |
|||
} |
|||
|
|||
u32 GetDepth() const { |
|||
return depth; |
|||
} |
|||
|
|||
u32 GetPitch() const { |
|||
return pitch; |
|||
} |
|||
|
|||
u32 GetNumLevels() const { |
|||
return num_levels; |
|||
} |
|||
|
|||
VideoCore::Surface::PixelFormat GetPixelFormat() const { |
|||
return pixel_format; |
|||
} |
|||
|
|||
VideoCore::Surface::ComponentType GetComponentType() const { |
|||
return component_type; |
|||
} |
|||
|
|||
VideoCore::Surface::SurfaceTarget GetTarget() const { |
|||
return target; |
|||
} |
|||
|
|||
VideoCore::Surface::SurfaceType GetType() const { |
|||
return type; |
|||
} |
|||
|
|||
std::size_t GetGuestSizeInBytes() const { |
|||
return guest_size_in_bytes; |
|||
} |
|||
|
|||
std::size_t GetHostSizeInBytes() const { |
|||
return host_size_in_bytes; |
|||
} |
|||
|
|||
u32 GetNumLayers() const { |
|||
return num_layers; |
|||
} |
|||
|
|||
/// Returns the width of a given mipmap level. |
|||
u32 GetMipWidth(u32 level) const; |
|||
|
|||
/// Returns the height of a given mipmap level. |
|||
u32 GetMipHeight(u32 level) const; |
|||
|
|||
/// Returns the depth of a given mipmap level. |
|||
u32 GetMipDepth(u32 level) const; |
|||
|
|||
/// Returns true if these parameters are from a layered surface. |
|||
bool IsLayered() const; |
|||
|
|||
/// Returns the block height of a given mipmap level. |
|||
u32 GetMipBlockHeight(u32 level) const; |
|||
|
|||
/// Returns the block depth of a given mipmap level. |
|||
u32 GetMipBlockDepth(u32 level) const; |
|||
|
|||
/// Returns the offset in bytes in guest memory of a given mipmap level. |
|||
std::size_t GetGuestMipmapLevelOffset(u32 level) const; |
|||
|
|||
/// Returns the offset in bytes in host memory (linear) of a given mipmap level. |
|||
std::size_t GetHostMipmapLevelOffset(u32 level) const; |
|||
|
|||
/// Returns the size of a layer in bytes in guest memory. |
|||
std::size_t GetGuestLayerSize() const; |
|||
|
|||
/// Returns the size of a layer in bytes in host memory for a given mipmap level. |
|||
std::size_t GetHostLayerSize(u32 level) const; |
|||
|
|||
/// Returns true if another surface can be familiar with this. This is a loosely defined term |
|||
/// that reflects the possibility of these two surface parameters potentially being part of a |
|||
/// bigger superset. |
|||
bool IsFamiliar(const SurfaceParams& view_params) const; |
|||
|
|||
/// Returns true if the pixel format is a depth and/or stencil format. |
|||
bool IsPixelFormatZeta() const; |
|||
|
|||
/// Creates a map that redirects an address difference to a layer and mipmap level. |
|||
std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const; |
|||
|
|||
/// Returns true if the passed surface view parameters is equal or a valid subset of this. |
|||
bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const; |
|||
|
|||
private: |
|||
/// Calculates values that can be deduced from HasheableSurfaceParams. |
|||
void CalculateCachedValues(); |
|||
|
|||
/// Returns the size of a given mipmap level. |
|||
std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, |
|||
bool uncompressed) const; |
|||
|
|||
/// Returns the size of all mipmap levels and aligns as needed. |
|||
std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const; |
|||
|
|||
/// Returns true if the passed view width and height match the size of this params in a given |
|||
/// mipmap level. |
|||
bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const; |
|||
|
|||
/// Returns true if the passed view depth match the size of this params in a given mipmap level. |
|||
bool IsDepthValid(const SurfaceParams& view_params, u32 level) const; |
|||
|
|||
/// Returns true if the passed view layers and mipmap levels are in bounds. |
|||
bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const; |
|||
|
|||
std::size_t guest_size_in_bytes; |
|||
std::size_t host_size_in_bytes; |
|||
u32 num_layers; |
|||
}; |
|||
|
|||
struct ViewKey { |
|||
std::size_t Hash() const; |
|||
|
|||
bool operator==(const ViewKey& rhs) const; |
|||
|
|||
u32 base_layer{}; |
|||
u32 num_layers{}; |
|||
u32 base_level{}; |
|||
u32 num_levels{}; |
|||
}; |
|||
|
|||
} // namespace VideoCommon |
|||
|
|||
namespace std { |
|||
|
|||
template <> |
|||
struct hash<VideoCommon::SurfaceParams> { |
|||
std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { |
|||
return k.Hash(); |
|||
} |
|||
}; |
|||
|
|||
template <> |
|||
struct hash<VideoCommon::ViewKey> { |
|||
std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept { |
|||
return k.Hash(); |
|||
} |
|||
}; |
|||
|
|||
} // namespace std |
|||
|
|||
namespace VideoCommon { |
|||
|
|||
template <typename TView, typename TExecutionContext> |
|||
class SurfaceBase { |
|||
static_assert(std::is_trivially_copyable_v<TExecutionContext>); |
|||
|
|||
public: |
|||
virtual void LoadBuffer() = 0; |
|||
|
|||
virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0; |
|||
|
|||
virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0; |
|||
|
|||
TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) { |
|||
if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) { |
|||
// It can't be a view if it's in a prior address. |
|||
return {}; |
|||
} |
|||
|
|||
const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)}; |
|||
const auto it{view_offset_map.find(relative_offset)}; |
|||
if (it == view_offset_map.end()) { |
|||
// Couldn't find an aligned view. |
|||
return {}; |
|||
} |
|||
const auto [layer, level] = it->second; |
|||
|
|||
if (!params.IsViewValid(view_params, layer, level)) { |
|||
return {}; |
|||
} |
|||
|
|||
return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels()); |
|||
} |
|||
|
|||
VAddr GetCpuAddr() const { |
|||
ASSERT(is_registered); |
|||
return cpu_addr; |
|||
} |
|||
|
|||
u8* GetHostPtr() const { |
|||
ASSERT(is_registered); |
|||
return host_ptr; |
|||
} |
|||
|
|||
CacheAddr GetCacheAddr() const { |
|||
ASSERT(is_registered); |
|||
return cache_addr; |
|||
} |
|||
|
|||
std::size_t GetSizeInBytes() const { |
|||
return params.GetGuestSizeInBytes(); |
|||
} |
|||
|
|||
void MarkAsModified(bool is_modified_) { |
|||
is_modified = is_modified_; |
|||
} |
|||
|
|||
const SurfaceParams& GetSurfaceParams() const { |
|||
return params; |
|||
} |
|||
|
|||
TView* GetView(VAddr view_addr, const SurfaceParams& view_params) { |
|||
TView* view{TryGetView(view_addr, view_params)}; |
|||
ASSERT(view != nullptr); |
|||
return view; |
|||
} |
|||
|
|||
void Register(VAddr cpu_addr_, u8* host_ptr_) { |
|||
ASSERT(!is_registered); |
|||
is_registered = true; |
|||
cpu_addr = cpu_addr_; |
|||
host_ptr = host_ptr_; |
|||
cache_addr = ToCacheAddr(host_ptr_); |
|||
} |
|||
|
|||
void Register(VAddr cpu_addr_) { |
|||
Register(cpu_addr_, Memory::GetPointer(cpu_addr_)); |
|||
} |
|||
|
|||
void Unregister() { |
|||
ASSERT(is_registered); |
|||
is_registered = false; |
|||
} |
|||
|
|||
bool IsRegistered() const { |
|||
return is_registered; |
|||
} |
|||
|
|||
protected: |
|||
explicit SurfaceBase(const SurfaceParams& params) |
|||
: params{params}, view_offset_map{params.CreateViewOffsetMap()} {} |
|||
|
|||
~SurfaceBase() = default; |
|||
|
|||
virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0; |
|||
|
|||
bool IsModified() const { |
|||
return is_modified; |
|||
} |
|||
|
|||
const SurfaceParams params; |
|||
|
|||
private: |
|||
TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) { |
|||
const ViewKey key{base_layer, num_layers, base_level, num_levels}; |
|||
const auto [entry, is_cache_miss] = views.try_emplace(key); |
|||
auto& view{entry->second}; |
|||
if (is_cache_miss) { |
|||
view = CreateView(key); |
|||
} |
|||
return view.get(); |
|||
} |
|||
|
|||
const std::map<u64, std::pair<u32, u32>> view_offset_map; |
|||
|
|||
VAddr cpu_addr{}; |
|||
u8* host_ptr{}; |
|||
CacheAddr cache_addr{}; |
|||
bool is_modified{}; |
|||
bool is_registered{}; |
|||
std::unordered_map<ViewKey, std::unique_ptr<TView>> views; |
|||
}; |
|||
|
|||
template <typename TSurface, typename TView, typename TExecutionContext> |
|||
class TextureCache { |
|||
static_assert(std::is_trivially_copyable_v<TExecutionContext>); |
|||
using ResultType = std::tuple<TView*, TExecutionContext>; |
|||
using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>; |
|||
using IntervalType = typename IntervalMap::interval_type; |
|||
|
|||
public: |
|||
void InvalidateRegion(CacheAddr addr, std::size_t size) { |
|||
for (TSurface* surface : GetSurfacesInRegion(addr, size)) { |
|||
if (!surface->IsRegistered()) { |
|||
// Skip duplicates |
|||
continue; |
|||
} |
|||
Unregister(surface); |
|||
} |
|||
} |
|||
|
|||
ResultType GetTextureSurface(TExecutionContext exctx, |
|||
const Tegra::Texture::FullTextureInfo& config) { |
|||
auto& memory_manager{system.GPU().MemoryManager()}; |
|||
const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())}; |
|||
if (!cpu_addr) { |
|||
return {{}, exctx}; |
|||
} |
|||
const auto params{SurfaceParams::CreateForTexture(system, config)}; |
|||
return GetSurfaceView(exctx, *cpu_addr, params, true); |
|||
} |
|||
|
|||
ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) { |
|||
const auto& regs{system.GPU().Maxwell3D().regs}; |
|||
if (!regs.zeta.Address() || !regs.zeta_enable) { |
|||
return {{}, exctx}; |
|||
} |
|||
|
|||
auto& memory_manager{system.GPU().MemoryManager()}; |
|||
const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())}; |
|||
if (!cpu_addr) { |
|||
return {{}, exctx}; |
|||
} |
|||
|
|||
const auto depth_params{SurfaceParams::CreateForDepthBuffer( |
|||
system, regs.zeta_width, regs.zeta_height, regs.zeta.format, |
|||
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, |
|||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; |
|||
return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents); |
|||
} |
|||
|
|||
ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index, |
|||
bool preserve_contents) { |
|||
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); |
|||
|
|||
const auto& regs{system.GPU().Maxwell3D().regs}; |
|||
if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || |
|||
regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { |
|||
return {{}, exctx}; |
|||
} |
|||
|
|||
auto& memory_manager{system.GPU().MemoryManager()}; |
|||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; |
|||
const auto cpu_addr{memory_manager.GpuToCpuAddress( |
|||
config.Address() + config.base_layer * config.layer_stride * sizeof(u32))}; |
|||
if (!cpu_addr) { |
|||
return {{}, exctx}; |
|||
} |
|||
|
|||
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), |
|||
preserve_contents); |
|||
} |
|||
|
|||
ResultType GetFermiSurface(TExecutionContext exctx, |
|||
const Tegra::Engines::Fermi2D::Regs::Surface& config) { |
|||
const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())}; |
|||
ASSERT(cpu_addr); |
|||
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config), |
|||
true); |
|||
} |
|||
|
|||
TSurface* TryFindFramebufferSurface(const u8* host_ptr) const { |
|||
const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))}; |
|||
return it != registered_surfaces.end() ? *it->second.begin() : nullptr; |
|||
} |
|||
|
|||
protected: |
|||
TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) |
|||
: system{system}, rasterizer{rasterizer} {} |
|||
|
|||
~TextureCache() = default; |
|||
|
|||
virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, |
|||
const SurfaceParams& params, bool preserve_contents, |
|||
const std::vector<TSurface*>& overlaps) = 0; |
|||
|
|||
virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0; |
|||
|
|||
void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) { |
|||
surface->Register(cpu_addr, host_ptr); |
|||
registered_surfaces.add({GetSurfaceInterval(surface), {surface}}); |
|||
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1); |
|||
} |
|||
|
|||
void Unregister(TSurface* surface) { |
|||
registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}}); |
|||
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1); |
|||
surface->Unregister(); |
|||
} |
|||
|
|||
TSurface* GetUncachedSurface(const SurfaceParams& params) { |
|||
if (TSurface* surface = TryGetReservedSurface(params); surface) |
|||
return surface; |
|||
// No reserved surface available, create a new one and reserve it |
|||
auto new_surface{CreateSurface(params)}; |
|||
TSurface* surface{new_surface.get()}; |
|||
ReserveSurface(params, std::move(new_surface)); |
|||
return surface; |
|||
} |
|||
|
|||
Core::System& system; |
|||
|
|||
private: |
|||
ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params, |
|||
bool preserve_contents) { |
|||
const auto host_ptr{Memory::GetPointer(cpu_addr)}; |
|||
const auto cache_addr{ToCacheAddr(host_ptr)}; |
|||
const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())}; |
|||
if (overlaps.empty()) { |
|||
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); |
|||
} |
|||
|
|||
if (overlaps.size() == 1) { |
|||
if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view) |
|||
return {view, exctx}; |
|||
} |
|||
|
|||
TView* fast_view; |
|||
std::tie(fast_view, exctx) = |
|||
TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps); |
|||
|
|||
for (TSurface* surface : overlaps) { |
|||
if (!fast_view) { |
|||
// Flush even when we don't care about the contents, to preserve memory not written |
|||
// by the new surface. |
|||
exctx = surface->FlushBuffer(exctx); |
|||
} |
|||
Unregister(surface); |
|||
} |
|||
|
|||
if (fast_view) { |
|||
return {fast_view, exctx}; |
|||
} |
|||
|
|||
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); |
|||
} |
|||
|
|||
ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, |
|||
const SurfaceParams& params, bool preserve_contents) { |
|||
TSurface* new_surface{GetUncachedSurface(params)}; |
|||
Register(new_surface, cpu_addr, host_ptr); |
|||
if (preserve_contents) { |
|||
exctx = LoadSurface(exctx, new_surface); |
|||
} |
|||
return {new_surface->GetView(cpu_addr, params), exctx}; |
|||
} |
|||
|
|||
TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) { |
|||
surface->LoadBuffer(); |
|||
exctx = surface->UploadTexture(exctx); |
|||
surface->MarkAsModified(false); |
|||
return exctx; |
|||
} |
|||
|
|||
std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const { |
|||
if (size == 0) { |
|||
return {}; |
|||
} |
|||
const IntervalType interval{cache_addr, cache_addr + size}; |
|||
|
|||
std::vector<TSurface*> surfaces; |
|||
for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) { |
|||
surfaces.push_back(*pair.second.begin()); |
|||
} |
|||
return surfaces; |
|||
} |
|||
|
|||
void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) { |
|||
surface_reserve[params].push_back(std::move(surface)); |
|||
} |
|||
|
|||
TSurface* TryGetReservedSurface(const SurfaceParams& params) { |
|||
auto search{surface_reserve.find(params)}; |
|||
if (search == surface_reserve.end()) { |
|||
return {}; |
|||
} |
|||
for (auto& surface : search->second) { |
|||
if (!surface->IsRegistered()) { |
|||
return surface.get(); |
|||
} |
|||
} |
|||
return {}; |
|||
} |
|||
|
|||
IntervalType GetSurfaceInterval(TSurface* surface) const { |
|||
return IntervalType::right_open(surface->GetCacheAddr(), |
|||
surface->GetCacheAddr() + surface->GetSizeInBytes()); |
|||
} |
|||
|
|||
VideoCore::RasterizerInterface& rasterizer; |
|||
|
|||
IntervalMap registered_surfaces; |
|||
|
|||
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have |
|||
/// previously been used. This is to prevent surfaces from being constantly created and |
|||
/// destroyed when used with different surface parameters. |
|||
std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve; |
|||
}; |
|||
|
|||
} // namespace VideoCommon |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue